Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/save contact button #256

Merged
merged 8 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Warnings:

- You are about to drop the `projet_sourcing_projet` table. If the table is not empty, all the data it contains will be lost.

*/
-- DropForeignKey
ALTER TABLE "projet_sourcing_projet" DROP CONSTRAINT "projet_sourcing_projet_created_by_fkey";

-- DropForeignKey
ALTER TABLE "projet_sourcing_projet" DROP CONSTRAINT "projet_sourcing_projet_projet_id_fkey";

-- DropForeignKey
ALTER TABLE "projet_sourcing_projet" DROP CONSTRAINT "projet_sourcing_projet_sourced_projet_id_fkey";

-- DropTable
DROP TABLE "projet_sourcing_projet";

-- CreateTable
CREATE TABLE "projet_sourcing_contact" (
"id" SERIAL NOT NULL,
"projet_id" INTEGER NOT NULL,
"sourced_user_projet_id" INTEGER NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" TEXT NOT NULL,

CONSTRAINT "projet_sourcing_contact_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "projet_sourcing_contact_projet_id_sourced_user_projet_id_key" ON "projet_sourcing_contact"("projet_id", "sourced_user_projet_id");

-- AddForeignKey
ALTER TABLE "projet_sourcing_contact" ADD CONSTRAINT "projet_sourcing_contact_projet_id_fkey" FOREIGN KEY ("projet_id") REFERENCES "projet"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "projet_sourcing_contact" ADD CONSTRAINT "projet_sourcing_contact_sourced_user_projet_id_fkey" FOREIGN KEY ("sourced_user_projet_id") REFERENCES "user_projet"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "projet_sourcing_contact" ADD CONSTRAINT "projet_sourcing_contact_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
98 changes: 49 additions & 49 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,32 @@ model Account {
}

model User {
id String @id @default(cuid())
email String @unique
id String @id @default(cuid())
email String @unique
emailVerified DateTime?
image String?
accounts Account[]
agentconnect_info Json?
nom String?
prenom String?
poste String?
created_at DateTime @default(now())
updated_at DateTime? @updatedAt
collectivites_created collectivite[] @relation(name: "collectivite_creator")
created_at DateTime @default(now())
updated_at DateTime? @updatedAt
collectivites_created collectivite[] @relation(name: "collectivite_creator")
projets_created projet[]
estimations_created estimation[] @relation(name: "estimation_creator")
estimations_deleted estimation[] @relation(name: "estimation_deleter")
projets_deleted projet[] @relation(name: "projet_deleter")
collectivites user_collectivite[] @relation(name: "collectivite_belonging")
projets user_projet[] @relation(name: "projet_access")
selection_fiches_solutions Json @default("[]")
selection_fiches_diagnostic Int[] @default([])
estimations_created estimation[] @relation(name: "estimation_creator")
estimations_deleted estimation[] @relation(name: "estimation_deleter")
projets_deleted projet[] @relation(name: "projet_deleter")
collectivites user_collectivite[] @relation(name: "collectivite_belonging")
projets user_projet[] @relation(name: "projet_access")
selection_fiches_solutions Json @default("[]")
selection_fiches_diagnostic Int[] @default([])
canal_acquisition String?
user_projet_deleted user_projet[] @relation(name: "user_projet_deleter")
user_projet_deleted user_projet[] @relation(name: "user_projet_deleter")
discardedInformation String[]
conversations conversation[]
Analytics Analytics[]
sourcing_contacts projet_sourcing_projet[]
sourcing_contacts_created projet_sourcing_contact[]
}

model VerificationToken {
Expand Down Expand Up @@ -99,22 +99,23 @@ enum InvitationStatus {
}

model user_projet {
id Int @id @default(autoincrement())
email_address String?
role RoleProjet
projet_id Int
projet projet @relation(fields: [projet_id], references: [id])
user_id String?
user User? @relation(name: "projet_access", fields: [user_id], references: [id])
created_at DateTime @default(now())
invitation_token String? @default(uuid())
invitation_status InvitationStatus
deleted_at DateTime?
deleted_by String?
deleter User? @relation(name: "user_projet_deleter", fields: [deleted_by], references: [id])
email email[]
nb_views Int? @default(0)
last_viewed_at DateTime?
id Int @id @default(autoincrement())
email_address String?
role RoleProjet
projet_id Int
projet projet @relation(fields: [projet_id], references: [id])
user_id String?
user User? @relation(name: "projet_access", fields: [user_id], references: [id])
created_at DateTime @default(now())
invitation_token String? @default(uuid())
invitation_status InvitationStatus
deleted_at DateTime?
deleted_by String?
deleter User? @relation(name: "user_projet_deleter", fields: [deleted_by], references: [id])
email email[]
nb_views Int? @default(0)
last_viewed_at DateTime?
sourced_by_projets projet_sourcing_contact[] @relation("sourced_by_projets")

@@unique([user_id, projet_id])
}
Expand Down Expand Up @@ -157,11 +158,11 @@ model user_collectivite {
}

model projet {
id Int @id
id Int @id
created_by String
creator User @relation(fields: [created_by], references: [id])
created_at DateTime @default(now())
updated_at DateTime? @updatedAt
creator User @relation(fields: [created_by], references: [id])
created_at DateTime @default(now())
updated_at DateTime? @updatedAt
nom String
type_espace String?
adresse String?
Expand All @@ -172,29 +173,28 @@ model projet {
fiches_diagnostic_id Int[]
estimations estimation[]
collectiviteId Int
collectivite collectivite @relation(fields: [collectiviteId], references: [id])
collectivite collectivite @relation(fields: [collectiviteId], references: [id])
recommandations_viewed_by String[]
deleted_at DateTime?
deleted_by String?
deleter User? @relation(name: "projet_deleter", fields: [deleted_by], references: [id])
deleter User? @relation(name: "projet_deleter", fields: [deleted_by], references: [id])
users user_projet[]
is_public Boolean?
sourcing_cms Json[]
sourcing_projets projet_sourcing_projet[] @relation("sourcing_projet")
sourced_by_projets projet_sourcing_projet[] @relation("sourced_by_projets")
sourcing_user_projets projet_sourcing_contact[] @relation("sourcing_projet")
}

model projet_sourcing_projet {
id Int @id @default(autoincrement())
projet projet @relation("sourcing_projet", fields: [projet_id], references: [id])
projet_id Int
sourced_projet projet @relation("sourced_by_projets", fields: [sourced_projet_id], references: [id])
sourced_projet_id Int
created_at DateTime @default(now())
created_by String
creator User @relation(fields: [created_by], references: [id])

@@unique([projet_id, sourced_projet_id])
model projet_sourcing_contact {
id Int @id @default(autoincrement())
projet projet @relation("sourcing_projet", fields: [projet_id], references: [id])
projet_id Int
sourced_user_projet user_projet @relation("sourced_by_projets", fields: [sourced_user_projet_id], references: [id])
sourced_user_projet_id Int
created_at DateTime @default(now())
created_by String
creator User @relation(fields: [created_by], references: [id])

@@unique([projet_id, sourced_user_projet_id])
}

model estimation {
Expand Down
3 changes: 1 addition & 2 deletions src/actions/estimation/add-aide-in-estimation-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@ export const addAideInEstimationAction = async (
return { type: "error", message: "ESTIMATION_DOESNT_EXIST" };
}
const permission = new PermissionManager(session);
const canUpdateProjet = permission.canEditProject(estimation.projet_id);

if (!canUpdateProjet) {
if (!(await permission.canEditProject(estimation.projet_id))) {
return { type: "error", message: "PROJET_UPDATE_UNAUTHORIZED" };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const updateRecommandationsViewedByUser = async (
return { type: "error", message: "UNAUTHENTICATED", projet: null };
}
const permission = new PermissionManager(session);
if (!permission.canEditProject(+projetId) || !permission.canUpdateUser(userId)) {
if (!(await permission.canEditProject(+projetId)) || !permission.canUpdateUser(userId)) {
return { type: "error", message: "UNAUTHORIZED", projet: null };
}

Expand Down
45 changes: 45 additions & 0 deletions src/actions/projets/update-rex-contact-in-projet-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use server";
import { auth } from "@/src/lib/next-auth/auth";
import { ResponseAction } from "../actions-types";
import { customCaptureException } from "@/src/lib/sentry/sentryCustomMessage";
import { ProjetWithRelations } from "@/src/lib/prisma/prismaCustomTypes";
import { PermissionManager } from "@/src/helpers/permission-manager";
import { getProjetWithRelationsById, updateSourcingCmsProjet } from "@/src/lib/prisma/prismaProjetQueries";
import isEqual from "lodash/isEqual";
import { RexContactId } from "@/src/components/sourcing/types";

export const updateRexContactInProjetAction = async (
projetId: number,
rexContactId: RexContactId,
typeUpdate: "add" | "delete",
): Promise<ResponseAction<{ projet?: ProjetWithRelations | null }>> => {
const session = await auth();
if (!session) {
return { type: "error", message: "UNAUTHENTICATED" };
}

try {
const permission = new PermissionManager(session);

if (projetId && !(await permission.canEditProject(projetId))) {
return { type: "error", message: "PROJET_UPDATE_UNAUTHORIZED" };
}
let projetToUpdate = await getProjetWithRelationsById(projetId);
if (!projetToUpdate) {
return { type: "error", message: "PROJET_UPDATE_UNAUTHORIZED" };
}

let newSourcingCms = (projetToUpdate.sourcing_cms as RexContactId[]).filter(
(savedContact) => !isEqual(savedContact, rexContactId),
);
if (typeUpdate === "add") {
newSourcingCms = [...newSourcingCms, rexContactId];
}
projetToUpdate = await updateSourcingCmsProjet(projetId, newSourcingCms);

return { type: "success", projet: projetToUpdate };
} catch (e) {
customCaptureException("Error in updateRexContactInProjetAction DB call", e);
return { type: "error", message: "TECHNICAL_ERROR" };
}
};
41 changes: 41 additions & 0 deletions src/actions/projets/update-user-contact-in-projet-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"use server";
import { auth } from "@/src/lib/next-auth/auth";
import { ResponseAction } from "../actions-types";
import { customCaptureException } from "@/src/lib/sentry/sentryCustomMessage";
import { ProjetWithRelations } from "@/src/lib/prisma/prismaCustomTypes";
import { PermissionManager } from "@/src/helpers/permission-manager";
import { getProjetWithRelationsById } from "@/src/lib/prisma/prismaProjetQueries";
import { addContactToProjet, deleteContactFromProjet } from "@/src/lib/prisma/prisma-projet-sourcing-contact-queries";
import { getUserProjetById } from "@/src/lib/prisma/prisma-user-projet-queries";

export const updateUserContactInProjetAction = async (
projetId: number,
userProjetId: number,
typeUpdate: "add" | "delete",
): Promise<ResponseAction<{ projet?: ProjetWithRelations | null }>> => {
const session = await auth();
if (!session) {
return { type: "error", message: "UNAUTHENTICATED" };
}
try {
const permission = new PermissionManager(session);
if (projetId && !(await permission.canEditProject(projetId))) {
return { type: "error", message: "PROJET_UPDATE_UNAUTHORIZED" };
}
let projetToUpdate = await getProjetWithRelationsById(projetId);
const userProjetToUse = await getUserProjetById(userProjetId);
if (!projetToUpdate || !userProjetToUse) {
return { type: "error", message: "PROJET_UPDATE_UNAUTHORIZED" };
}
if (typeUpdate === "add") {
await addContactToProjet(projetId, userProjetId, session.user.id);
} else {
await deleteContactFromProjet(projetId, userProjetId);
}
const updatedProjet = await getProjetWithRelationsById(projetId);
return { type: "success", projet: updatedProjet };
} catch (e) {
customCaptureException("Error in updateUserContactInProjetAction DB call", e);
return { type: "error", message: "TECHNICAL_ERROR" };
}
};
6 changes: 4 additions & 2 deletions src/components/common/protected-espace-projet-url.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { useIsLecteur } from "@/src/hooks/use-is-lecteur";
import Button from "@codegouvfr/react-dsfr/Button";
import { useRouter } from "next/navigation";
import { PropsWithChildren } from "react";
import { useProjetsStore } from "@/src/stores/projets/provider";

export const ProtectedEspaceProjetUrl = ({ children }: PropsWithChildren) => {
const isLecteur = useIsLecteur();
const currentProjet = useProjetsStore((state) => state.getCurrentProjet());
const isLecteur = useIsLecteur(currentProjet?.id);
const { back, push } = useRouter();

if (isLecteur) {
if (!currentProjet || isLecteur) {
return (
<div className="fr-container pt-8">
<h1 className="text-xl">{"Vous n'êtes pas autorisé à consulter cette page."}</h1>
Expand Down
8 changes: 2 additions & 6 deletions src/components/espace-projet/banner/banner-projet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@ import clsx from "clsx";
import { BannerProjetButtons } from "./banner-projet-buttons";
import { Suspense } from "react";
import { BannerProjetSkeleton } from "./banner-projet-skeleton";
import { getCurrentUserRole } from "@/src/components/partage/helpers";
import { useUserStore } from "@/src/stores/user/provider";
import { LecteurModeLabel } from "@/src/components/common/lecteur-mode-label";
import { RoleProjet } from "@prisma/client";
import { useIsLecteur } from "@/src/hooks/use-is-lecteur";

export default function BannerProjet({ className }: { className?: string }) {
const currentProjet = useProjetsStore((state) => state.getCurrentProjet());
const currentUserId = useUserStore((state) => state.userInfos?.id);
const isLecteur =
(currentProjet && getCurrentUserRole(currentProjet.users, currentUserId) !== RoleProjet.ADMIN) ?? false;
const isLecteur = useIsLecteur(currentProjet?.id);

return (
<div className={`bg-dsfr-background-alt-blue-france py-4 ${className} min-h-[7rem]`}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const PartageOverviewMemberStatusInvited = ({ member }: { member: UserPro
const [isPending, startTransition] = useTransition();

const userProjetId = member.id;
const isLecteur = useIsLecteur();
const isLecteur = useIsLecteur(member.projet_id);

const handleResendInvitation = () => {
startTransition(async () => {
Expand Down
20 changes: 13 additions & 7 deletions src/components/sourcing/contacts/sourcing-contact-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ import { getSourcingContactTypeLabel } from "../helpers";
import Image from "next/image";
import { CopyField } from "../../common/copy-field";
import { SourcingContact } from "@/src/components/sourcing/types";
import { SourcingContactSaveButton } from "@/src/components/sourcing/contacts/sourcing-contact-save-button";
import { useIsLecteur } from "@/src/hooks/use-is-lecteur";

type SourcingContactCardProps = {
contact: SourcingContact;
projetId?: number | null;
};

export const SourcingContactCard = ({ contact }: SourcingContactCardProps) => {
const type = getSourcingContactTypeLabel(contact.type_de_contact, false);
const sousType = getSourcingContactTypeLabel(contact.sous_type_de_contact, true);
export const SourcingContactCard = ({ contact, projetId }: SourcingContactCardProps) => {
const type = getSourcingContactTypeLabel(contact.typeContact, false);
const sousType = getSourcingContactTypeLabel(contact.sousTypeContact, true);

const shoudDisplaySaveButton = !useIsLecteur(projetId);

return (
<div className="mb-4 overflow-hidden rounded-2xl border-[1px] border-dsfr-border-default-grey p-6">
<div className="mb-6">
<p className="fr-badge fr-badge--info fr-badge--sm fr-badge--no-icon mb-2 !text-pfmv-navy">
{contact.type_de_contact === "collectivite" && (
<div className="mb-6 flex flex-row items-center justify-between">
<div className="fr-badge fr-badge--info fr-badge--sm fr-badge--no-icon !max-w-[116px] !text-pfmv-navy">
{contact.typeContact === "collectivite" && (
<Image
src="/images/sourcing/sourcing-label-collectivite.svg"
className="mr-1"
Expand All @@ -25,7 +30,8 @@ export const SourcingContactCard = ({ contact }: SourcingContactCardProps) => {
/>
)}
{type}
</p>
</div>
{projetId && shoudDisplaySaveButton && <SourcingContactSaveButton contact={contact} projetId={projetId} />}
</div>
<div>
<h3 className="mb-1 text-lg font-bold">{sousType}</h3>
Expand Down
Loading
Loading