diff --git a/components/api-depot/chef-de-file.tsx b/components/api-depot/chef-de-file.tsx index 6cd3a1c..a1838c0 100644 --- a/components/api-depot/chef-de-file.tsx +++ b/components/api-depot/chef-de-file.tsx @@ -4,12 +4,13 @@ import { getEPCI, getDepartement, getCommune } from "@/lib/cog"; import CopyToClipBoard from "@/components/copy-to-clipboard"; import { - ChefDeFileApiDepotType, - PerimeterType, + ChefDeFile as ChefDeFileApiDepot, + Perimeter, TypePerimeterEnum, -} from "types/api-depot"; +} from "types/api-depot.types"; +import Checkbox from "@codegouvfr/react-dsfr/Checkbox"; -function getPerimeters(perimetre: PerimeterType[]) { +function getPerimeters(perimetre: Perimeter[]) { return perimetre ? perimetre.map((p) => { const { type, code } = p; @@ -31,20 +32,20 @@ function getPerimeters(perimetre: PerimeterType[]) { : null; } -interface ChefDeFileProps extends ChefDeFileApiDepotType { +interface ChefDeFileProps extends ChefDeFileApiDepot { hasChefDeFile: boolean; } const ChefDeFile = ({ hasChefDeFile, - _id, + id, nom, email, isEmailPublic, - perimetre, - signataireCharte, + perimeters, + isSignataireCharte, }: ChefDeFileProps) => { - const perimeters = getPerimeters(perimetre); + const perimetersString = getPerimeters(perimeters); return (

Chef de file

@@ -53,41 +54,40 @@ const ChefDeFile = ({

{nom}

- + -
- - -
-
- -
- + -
diff --git a/components/api-depot/client-item.tsx b/components/api-depot/client-item.tsx index 19dcb8e..1481d08 100644 --- a/components/api-depot/client-item.tsx +++ b/components/api-depot/client-item.tsx @@ -1,17 +1,14 @@ import Badge from "@codegouvfr/react-dsfr/Badge"; import Button from "@codegouvfr/react-dsfr/Button"; +import Checkbox from "@codegouvfr/react-dsfr/Checkbox"; import Link from "next/link"; +import { ChefDeFile, Client, Mandataire } from "types/api-depot.types"; import { PartenaireDeLaCharte } from "../../server/lib/partenaire-de-la-charte/entity"; -import { - ChefDeFileApiDepotType, - ClientApiDepotType, - MandataireApiDepotType, -} from "types/api-depot"; interface ClientItemProps { - client: ClientApiDepotType; - mandataire: MandataireApiDepotType; - chefDeFile: ChefDeFileApiDepotType; + client: Client; + mandataire: Mandataire; + chefDeFile: ChefDeFile; partenaires: PartenaireDeLaCharte[]; isDemo: boolean; } @@ -29,21 +26,29 @@ const ClientItem = ({ {chefDeFile ? chefDeFile.nom : "-"} {client.authorizationStrategy} - - @@ -71,7 +76,7 @@ const ClientItem = ({ passHref href={{ pathname: "/api-depot/client/client-form", - query: { clientId: client._id, demo: isDemo ? 1 : 0 }, + query: { clientId: client.id, demo: isDemo ? 1 : 0 }, }} > -
- - - ) : ( -
-
- -
-
- -
-
- - )} - - ) -} - -MandataireForm.defaultProps = { - mandataires: [] -} - -MandataireForm.propTypes = { - selectedMandataire: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.object, - ]), - mandataires: PropTypes.array, - onSelect: PropTypes.func.isRequired -} - -export default MandataireForm diff --git a/components/api-depot/client/client-form/mandataire-form.tsx b/components/api-depot/client/client-form/mandataire-form.tsx new file mode 100644 index 0000000..d77c3d1 --- /dev/null +++ b/components/api-depot/client/client-form/mandataire-form.tsx @@ -0,0 +1,127 @@ +import { useEffect, useMemo, useState } from "react"; +import PropTypes from "prop-types"; +import Button from "@codegouvfr/react-dsfr/Button"; +import Input from "@codegouvfr/react-dsfr/Input"; + +import { checkEmail } from "@/lib/util/email"; + +import SelectInput from "@/components/select-input"; +import { Mandataire } from "types/api-depot.types"; + +interface MandataireFormProps { + selectedMandataire: string; + mandataires: Mandataire[]; + onSelect: (value: string) => void; + onCreate: (value: Mandataire) => void; +} + +const MandataireForm = ({ + selectedMandataire, + onSelect, + onCreate, + mandataires = [], +}: MandataireFormProps) => { + const [nom, setNom] = useState(""); + const [email, setEmail] = useState(""); + const [isEmailValid, setIsEmailValid] = useState(null); + const [isCreateFormOpen, setIsCreateFormOpen] = useState(false); + + const mandatairesOptions = useMemo( + () => + mandataires.map((m) => ({ + label: m.nom + " (" + m.email + ")", + value: m.id, + })), + [mandataires] + ); + + const handleCreationForm = (event, isOpen) => { + event.preventDefault(); + setIsCreateFormOpen(isOpen); + onSelect(null); + }; + + useEffect(() => { + if (email) { + const isValid = checkEmail(email); + setIsEmailValid(isValid); + } else { + setIsEmailValid(null); + } + }, [email]); + + useEffect(() => { + if (nom && email && isEmailValid) { + onCreate({ nom, email }); + } + }, [nom, email, isEmailValid]); + + return ( +
+ {isCreateFormOpen ? ( +
+ + +
+
+ setNom(e.target.value), + }} + /> +
+
+ setEmail(e.target.value), + }} + /> +
+
+ +
+
+ +
+
+
+ ) : ( +
+
+ +
+
+ +
+
+ )} +
+ ); +}; + +export default MandataireForm; diff --git a/components/api-depot/client/client-form/perimeter-list.tsx b/components/api-depot/client/client-form/perimeter-list.tsx index a757a3e..c92d00d 100644 --- a/components/api-depot/client/client-form/perimeter-list.tsx +++ b/components/api-depot/client/client-form/perimeter-list.tsx @@ -2,7 +2,10 @@ import { Dispatch, SetStateAction, useCallback } from "react"; import Button from "@codegouvfr/react-dsfr/Button"; import Perimeter from "@/components/api-depot/client/client-form/perimeter"; -import { PerimeterType, TypePerimeterEnum } from "types/api-depot"; +import { + Perimeter as PerimeterType, + TypePerimeterEnum, +} from "types/api-depot.types"; interface PerimeterListProps { perimeters: PerimeterType[]; diff --git a/components/api-depot/client/client-form/perimeter.tsx b/components/api-depot/client/client-form/perimeter.tsx index 5021a40..e752b1f 100644 --- a/components/api-depot/client/client-form/perimeter.tsx +++ b/components/api-depot/client/client-form/perimeter.tsx @@ -7,9 +7,11 @@ import allCommunes from "@etalab/decoupage-administratif/data/communes.json"; import AutocompleteInput from "@/components/autocomplete-input"; import SelectInput from "@/components/select-input"; -import { PerimeterType, TypePerimeterEnum } from "types/api-depot"; +import { + Perimeter as PerimeterType, + TypePerimeterEnum, +} from "types/api-depot.types"; import { Tooltip } from "@codegouvfr/react-dsfr/Tooltip"; -import Badge from "@codegouvfr/react-dsfr/Badge"; const typeOptions = [ { label: "EPCI", value: "epci" }, diff --git a/components/api-depot/client/client-header.tsx b/components/api-depot/client/client-header.tsx index c0f4ca8..9c9f112 100644 --- a/components/api-depot/client/client-header.tsx +++ b/components/api-depot/client/client-header.tsx @@ -3,11 +3,12 @@ import Button from "@codegouvfr/react-dsfr/Button"; import Badge from "@codegouvfr/react-dsfr/Badge"; import CopyToClipBoard from "@/components/copy-to-clipboard"; -import { ClientApiDepotType } from "types/api-depot"; +import { Client } from "types/api-depot.types"; +import Checkbox from "@codegouvfr/react-dsfr/Checkbox"; import { PartenaireDeLaCharte } from "../../../server/lib/partenaire-de-la-charte/entity"; interface ClientHeaderProps { - client: ClientApiDepotType; + client: Client; partenaire: PartenaireDeLaCharte; } @@ -18,9 +19,34 @@ const ClientHeader = ({ client, partenaire }: ClientHeaderProps) => (

{client.nom}

- + + + + {partenaire ? ( <>

Partenaire de la charte

diff --git a/components/api-depot/clients-list.tsx b/components/api-depot/clients-list.tsx index 021ce7b..b0466ac 100644 --- a/components/api-depot/clients-list.tsx +++ b/components/api-depot/clients-list.tsx @@ -1,15 +1,10 @@ -import PropTypes from "prop-types"; import { useEffect, useState } from "react"; import Alert from "@codegouvfr/react-dsfr/Alert"; import { getChefsDeFile, getClients, getMandataires } from "@/lib/api-depot"; import ClientItem from "@/components/api-depot/client-item"; -import { - ChefDeFileApiDepotType, - ClientApiDepotType, - MandataireApiDepotType, -} from "types/api-depot"; +import { ChefDeFile, Client, Mandataire } from "types/api-depot.types"; import { getPartenairesDeLaCharte } from "@/lib/partenaires-de-la-charte"; import { PartenaireDeLaCharte } from "../../server/lib/partenaire-de-la-charte/entity"; @@ -19,9 +14,9 @@ interface ClientsListProps { const ClientsList = ({ isDemo = false }: ClientsListProps) => { const [data, setData] = useState<{ - clients: ClientApiDepotType[]; - mandataires: MandataireApiDepotType[]; - chefsDeFile: ChefDeFileApiDepotType[]; + clients: Client[]; + mandataires: Mandataire[]; + chefsDeFile: ChefDeFile[]; partenaires: PartenaireDeLaCharte[]; } | null>(null); const [error, setError] = useState(null); @@ -84,17 +79,17 @@ const ClientsList = ({ isDemo = false }: ClientsListProps) => { {data?.clients.map((client) => ( _id === client.mandataire + ({ id }) => id === client.mandataireId )} chefDeFile={data.chefsDeFile.find( - ({ _id }) => _id === client.chefDeFile + ({ id }) => id === client.chefDeFileId )} partenaires={data.partenaires.filter( ({ apiDepotClientId }) => - apiDepotClientId?.includes(client._id) + apiDepotClientId?.includes(client.id) )} isDemo={isDemo} /> diff --git a/components/api-depot/mandataire.js b/components/api-depot/mandataire.js deleted file mode 100644 index e0e817c..0000000 --- a/components/api-depot/mandataire.js +++ /dev/null @@ -1,26 +0,0 @@ -import PropTypes from 'prop-types' - -import CopyToClipBoard from '@/components/copy-to-clipboard' - -const Mandataire = ({_id, nom, email}) => ( -
-

Mandataire

-
-
-
-

{nom}

- - -
-
-
-
-) - -Mandataire.propTypes = { - _id: PropTypes.string.isRequired, - nom: PropTypes.string.isRequired, - email: PropTypes.string.isRequired -} - -export default Mandataire diff --git a/components/api-depot/mandataire.tsx b/components/api-depot/mandataire.tsx new file mode 100644 index 0000000..d28af98 --- /dev/null +++ b/components/api-depot/mandataire.tsx @@ -0,0 +1,19 @@ +import CopyToClipBoard from "@/components/copy-to-clipboard"; +import { Mandataire as MandataireType } from "../../types/api-depot.types"; + +const Mandataire = ({ id, nom, email }: MandataireType) => ( +
+

Mandataire

+
+
+
+

{nom}

+ + +
+
+
+
+); + +export default Mandataire; diff --git a/components/bal-widget/bal-widget-config-form.tsx b/components/bal-widget/bal-widget-config-form.tsx index fa7d1e9..0ae2acd 100644 --- a/components/bal-widget/bal-widget-config-form.tsx +++ b/components/bal-widget/bal-widget-config-form.tsx @@ -9,7 +9,7 @@ import { BalWidget } from "../../server/lib/bal-widget/entity"; import { MultiStringInput } from "../multi-string-input"; import { MultiLinkInput } from "../multi-link-input"; import { MultiSelectInput } from "../multi-select-input"; -import { ClientApiDepotType } from "types/api-depot"; +import { Client } from "types/api-depot.types"; import { SourceMoissoneurType } from "types/moissoneur"; import { getClients } from "@/lib/api-depot"; import { getSources } from "@/lib/api-moissonneur-bal"; @@ -50,9 +50,7 @@ export const BALWidgetConfigForm = ({ }: BALWidgetConfigFormProps) => { const tabRef = useRef(null); const [selectedTabId, setSelectedTabId] = useState("communes"); - const [apiDepotClients, setApiDepotClients] = useState( - [] - ); + const [apiDepotClients, setApiDepotClients] = useState([]); const [harvestSources, setHarvestSources] = useState( [] ); @@ -181,7 +179,7 @@ export const BALWidgetConfigForm = ({ label="Clients API Dépôt caducs" value={formData.communes?.outdatedApiDepotClients || []} options={apiDepotClients.map((client) => ({ - value: client._id, + value: client.id, label: client.nom, }))} placeholder="Sélectionner les clients API Dépôt caducs" diff --git a/components/communes/revisions-item-api-depot.tsx b/components/communes/revisions-item-api-depot.tsx index c3228fa..dcf7a50 100644 --- a/components/communes/revisions-item-api-depot.tsx +++ b/components/communes/revisions-item-api-depot.tsx @@ -1,27 +1,27 @@ import Link from "next/link"; import { Badge } from "@codegouvfr/react-dsfr/Badge"; -import type { RevisionApiDepotType } from "../../types/api-depot"; +import type { Revision } from "../../types/api-depot.types"; import { formatDate } from "@/lib/util/date"; import MongoId from "@/components/mongo-id"; import Tooltip from "@/components/tooltip"; -import TypeCLientBadge from "../type-client-badge"; +import TypeClientBadge from "../type-client-badge"; export const RevisionItemApiDepot = ({ - _id, + id, status, - current = false, + isCurrent = false, client, validation, createdAt, - publishedAt = "", -}: RevisionApiDepotType) => ( - + publishedAt = null, +}: Revision) => ( + - + - + @@ -33,7 +33,7 @@ export const RevisionItemApiDepot = ({ type="checkbox" id="checkbox" name="checkbox" - checked={current} + checked={isCurrent} disabled /> @@ -58,7 +58,7 @@ export const RevisionItemApiDepot = ({ target="_blank" passHref href={{ - pathname: `${process.env.NEXT_PUBLIC_API_DEPOT_URL}/revisions/${_id}/files/bal/download`, + pathname: `${process.env.NEXT_PUBLIC_API_DEPOT_URL}/revisions/${id}/files/bal/download`, }} > Télécharger diff --git a/components/partenaires-de-la-charte/partenaire-form.tsx b/components/partenaires-de-la-charte/partenaire-form.tsx index b0d5d79..5265b65 100644 --- a/components/partenaires-de-la-charte/partenaire-form.tsx +++ b/components/partenaires-de-la-charte/partenaire-form.tsx @@ -12,7 +12,7 @@ import { CommuneInput } from "../commune-input"; import SelectInput from "@/components/select-input"; import { capitalize } from "@/lib/util/string"; import { getClients } from "@/lib/api-depot"; -import { ClientApiDepotType } from "types/api-depot"; +import { Client } from "types/api-depot.types"; import { OrganizationMoissoneurType } from "types/moissoneur"; import { getOrganizations } from "@/lib/api-moissonneur-bal"; import { PartenaireDeLaCharteDTO } from "../../server/lib/partenaire-de-la-charte/dto"; @@ -120,7 +120,7 @@ export const PartenaireForm = ({ useEffect(() => { async function fetchOptions() { - let clients: ClientApiDepotType[] = []; + let clients: Client[] = []; let organisations: OrganizationMoissoneurType[] = []; try { @@ -129,7 +129,7 @@ export const PartenaireForm = ({ } catch {} setOptionClients( - clients.map(({ _id, nom }) => ({ value: _id, label: nom })) + clients.map(({ id, nom }) => ({ value: id, label: nom })) ); setOptionOrganizations( organisations.map(({ id, name }) => ({ value: id, label: name })) diff --git a/components/type-client-badge.tsx b/components/type-client-badge.tsx index 295ad60..7768fb4 100644 --- a/components/type-client-badge.tsx +++ b/components/type-client-badge.tsx @@ -1,10 +1,8 @@ import { Badge } from "@codegouvfr/react-dsfr/Badge"; -import { UpdateStatusEnum } from "../types/moissoneur"; -import Tooltip from "./tooltip"; -import { ClientApiDepotType } from "types/api-depot"; import Link from "next/link"; import { useMemo } from "react"; +import { PublicClient } from "types/api-depot.types"; const ClientIdToBadge = { "mes-adresses": { @@ -21,24 +19,24 @@ const ClientIdToBadge = { }, }; -type TypeCLientBadgeProps = { - client: ClientApiDepotType; +type TypeClientBadgeProps = { + client: PublicClient; }; -const TypeCLientBadge = ({ client }: TypeCLientBadgeProps) => { +const TypeClientBadge = ({ client }: TypeClientBadgeProps) => { const getBadge = useMemo(() => { if ( - client.id === "mes-adresses" || - client.id === "moissonneur-bal" || - client.id === "formulaire-publication" + client.legacyId === "mes-adresses" || + client.legacyId === "moissonneur-bal" || + client.legacyId === "formulaire-publication" ) { return ( - {ClientIdToBadge[client.id].text} + {ClientIdToBadge[client.legacyId].text} ); } @@ -48,7 +46,7 @@ const TypeCLientBadge = ({ client }: TypeCLientBadgeProps) => { API DEPOT ); - }, [client.id]); + }, [client.legacyId]); return ( { passHref href={{ pathname: "/api-depot/client", - query: { clientId: client._id }, + query: { clientId: client.id }, }} > {getBadge} @@ -64,4 +62,4 @@ const TypeCLientBadge = ({ client }: TypeCLientBadgeProps) => { ); }; -export default TypeCLientBadge; +export default TypeClientBadge; diff --git a/lib/api-depot.js b/lib/api-depot.ts similarity index 57% rename from lib/api-depot.js rename to lib/api-depot.ts index 07ae0b2..d5feca3 100644 --- a/lib/api-depot.js +++ b/lib/api-depot.ts @@ -1,29 +1,44 @@ +import { + ChefDeFile, + Client, + Mandataire, + Revision, +} from "types/api-depot.types"; + const NEXT_PUBLIC_BAL_ADMIN_URL = process.env.NEXT_PUBLIC_BAL_ADMIN_URL || "http://localhost:3000"; const PROXY_API_DEPOT_URL = NEXT_PUBLIC_BAL_ADMIN_URL + "/api/proxy-api-depot"; const PROXY_API_DEPOT_DEMO_URL = NEXT_PUBLIC_BAL_ADMIN_URL + "/api/proxy-api-depot-demo"; -async function processResponse(response) { - if (!response.ok) { - const body = await response.json(); - throw new Error(body.message); +async function processResponse(res: Response) { + if (!res.ok) { + const error = await res.json(); + throw new Error(error.message); } - return response.json(); + try { + return await res.json(); + } catch { + return res; + } } -function getProxyURL(isDemo) { +function getProxyURL(isDemo: boolean = false): string { return isDemo ? PROXY_API_DEPOT_DEMO_URL : PROXY_API_DEPOT_URL; } -export async function getClient(clientId, isDemo) { +export async function getClient(clientId, isDemo): Promise { const response = await fetch(`${getProxyURL(isDemo)}/clients/${clientId}`); return processResponse(response); } -export async function updateClient(clientId, body, isDemo) { +export async function updateClient( + clientId: string, + body: Partial, + isDemo: boolean = false +): Promise { const response = await fetch(`${getProxyURL(isDemo)}/clients/${clientId}`, { method: "PUT", headers: { "content-type": "application/json" }, @@ -33,7 +48,10 @@ export async function updateClient(clientId, body, isDemo) { return processResponse(response); } -export async function createClient(body, isDemo) { +export async function createClient( + body: Partial, + isDemo: boolean = false +): Promise { const response = await fetch(`${getProxyURL(isDemo)}/clients`, { method: "POST", headers: { "content-type": "application/json" }, @@ -43,13 +61,16 @@ export async function createClient(body, isDemo) { return processResponse(response); } -export async function getClients(isDemo) { +export async function getClients(isDemo: boolean = false): Promise { const response = await fetch(`${getProxyURL(isDemo)}/clients`); return processResponse(response); } -export async function createMandataire(body, isDemo) { +export async function createMandataire( + body: Partial, + isDemo: boolean = false +): Promise { const response = await fetch(`${getProxyURL(isDemo)}/mandataires`, { method: "POST", headers: { "content-type": "application/json" }, @@ -59,7 +80,10 @@ export async function createMandataire(body, isDemo) { return processResponse(response); } -export async function getMandataire(mandataireId, isDemo) { +export async function getMandataire( + mandataireId: string, + isDemo: boolean = false +): Promise { const response = await fetch( `${getProxyURL(isDemo)}/mandataires/${mandataireId}` ); @@ -67,13 +91,18 @@ export async function getMandataire(mandataireId, isDemo) { return processResponse(response); } -export async function getMandataires(isDemo) { +export async function getMandataires( + isDemo: boolean = false +): Promise { const response = await fetch(`${getProxyURL(isDemo)}/mandataires`); return processResponse(response); } -export async function createChefDeFile(body, isDemo) { +export async function createChefDeFile( + body: Partial, + isDemo: boolean = false +): Promise { const response = await fetch(`${getProxyURL(isDemo)}/chefs-de-file`, { method: "POST", headers: { "content-type": "application/json" }, @@ -83,7 +112,11 @@ export async function createChefDeFile(body, isDemo) { return processResponse(response); } -export async function updateChefDeFile(chefDeFileId, body, isDemo) { +export async function updateChefDeFile( + chefDeFileId: string, + body: Partial, + isDemo: boolean = false +): Promise { const response = await fetch( `${getProxyURL(isDemo)}/chefs-de-file/${chefDeFileId}`, { @@ -96,7 +129,10 @@ export async function updateChefDeFile(chefDeFileId, body, isDemo) { return processResponse(response); } -export async function getChefDeFile(chefDeFileId, isDemo) { +export async function getChefDeFile( + chefDeFileId: string, + isDemo: boolean = false +): Promise { const response = await fetch( `${getProxyURL(isDemo)}/chefs-de-file/${chefDeFileId}` ); @@ -104,13 +140,18 @@ export async function getChefDeFile(chefDeFileId, isDemo) { return processResponse(response); } -export async function getChefsDeFile(isDemo) { +export async function getChefsDeFile( + isDemo: boolean = false +): Promise { const response = await fetch(`${getProxyURL(isDemo)}/chefs-de-file`); return processResponse(response); } -export async function getStatFirstPublicationEvolution({ from, to }, isDemo) { +export async function getStatFirstPublicationEvolution( + { from, to }: { from: string; to: string }, + isDemo: boolean = false +) { const res = await fetch( `${getProxyURL(isDemo)}/stats/firsts-publications?from=${from}&to=${to}` ); @@ -120,7 +161,10 @@ export async function getStatFirstPublicationEvolution({ from, to }, isDemo) { } } -export async function getStatPublications({ from, to }, isDemo) { +export async function getStatPublications( + { from, to }: { from: string; to: string }, + isDemo: boolean = false +) { const res = await fetch( `${getProxyURL(isDemo)}/stats/publications?from=${from}&to=${to}` ); @@ -130,7 +174,10 @@ export async function getStatPublications({ from, to }, isDemo) { } } -export async function getAllRevisionByCommune(codeCommune, isDemo = false) { +export async function getAllRevisionByCommune( + codeCommune: string, + isDemo: boolean = false +): Promise { const response = await fetch( `${getProxyURL(isDemo)}/communes/${codeCommune}/revisions` ); @@ -138,9 +185,12 @@ export async function getAllRevisionByCommune(codeCommune, isDemo = false) { return processResponse(response); } -export async function validateHabilitation(habilitationId) { +export async function validateHabilitation( + habilitationId: string, + isDemo: boolean = false +) { const response = await fetch( - `${getProxyURL(false)}/habilitations/${habilitationId}/validate`, + `${getProxyURL(isDemo)}/habilitations/${habilitationId}/validate`, { method: "PUT", headers: { "content-type": "application/json" }, diff --git a/lib/api-mes-adresses.tsx b/lib/api-mes-adresses.ts similarity index 100% rename from lib/api-mes-adresses.tsx rename to lib/api-mes-adresses.ts diff --git a/pages/api-depot/client/client-form.tsx b/pages/api-depot/client/client-form.tsx index 813fc26..7239bf6 100644 --- a/pages/api-depot/client/client-form.tsx +++ b/pages/api-depot/client/client-form.tsx @@ -5,16 +5,16 @@ import { ToggleSwitch } from "@codegouvfr/react-dsfr/ToggleSwitch"; import { useRouter } from "next/router"; import { - ClientApiDepotAuthorizationStrategyEnum, - type ChefDeFileApiDepotType, - type MandataireApiDepotType, -} from "types/api-depot"; + AuthorizationStrategyEnum, + ChefDeFile, + Client, + Mandataire, +} from "types/api-depot.types"; import { createClient, updateClient, createMandataire, - createChefDeFile, getChefsDeFile, getClient, getMandataires, @@ -29,26 +29,27 @@ import ChefDeFileForm from "@/components/api-depot/client/client-form/chef-de-fi const authorizationStrategyOptions = [ { label: "Chef de file", - value: ClientApiDepotAuthorizationStrategyEnum.CHEF_DE_FILE, + value: AuthorizationStrategyEnum.CHEF_DE_FILE, }, { label: "Habilitation", - value: ClientApiDepotAuthorizationStrategyEnum.HABILITATION, + value: AuthorizationStrategyEnum.HABILITATION, }, ]; -const newClientFormData = { +const newClientFormData: Partial = { nom: "", - isModeRelax: false, + isRelaxMode: false, isActive: false, - mandataire: "", - chefDeFile: "", + mandataireId: null, + mandataire: null, + chefDeFileId: null, }; const ClientForm = () => { const router = useRouter(); - const { clientId, demo } = router.query; - const isDemo = demo === "1"; + const clientId: string = router.query?.clientId as string; + const isDemo: boolean = router.query?.demo === "1"; const [authorizationStrategy, setAuthorizationStrategy] = useState( authorizationStrategyOptions[0].value ); @@ -58,30 +59,29 @@ const ClientForm = () => { const [isformChefDeFileOpen, setIsformChefDeFileOpen] = useState(false); const [initialChefDeFileForm, setInitialChefDeFileForm] = - useState(null); + useState(null); const [chefsDeFileOptions, setChefsDeFileOptions] = - useState(null); + useState(null); const [mandatairesOptions, setMandatairesOptions] = - useState(null); - const [formData, setFormData] = useState(null); + useState(null); + const [formData, setFormData] = useState>(null); useEffect(() => { async function fetchData() { setIsLoading(true); - const mandataires: MandataireApiDepotType[] = - await getMandataires(isDemo); + const mandataires: Mandataire[] = await getMandataires(isDemo); setMandatairesOptions(mandataires); - const chefsDeFile: ChefDeFileApiDepotType[] = - await getChefsDeFile(isDemo); + const chefsDeFile: ChefDeFile[] = await getChefsDeFile(isDemo); setChefsDeFileOptions(chefsDeFile); if (clientId) { - const client = await getClient(clientId, isDemo); + const client: Client = await getClient(clientId, isDemo); + setAuthorizationStrategy(client.authorizationStrategy); setFormData({ nom: client.nom, - isModeRelax: Boolean(client.options?.relaxMode), - isActive: client.active, - mandataire: client.mandataire, - chefDeFile: client.chefDeFile, + isRelaxMode: client.isRelaxMode, + isActive: client.isActive, + mandataireId: client.mandataireId, + chefDeFileId: client.chefDeFileId, }); } else { setFormData(newClientFormData); @@ -109,42 +109,52 @@ const ClientForm = () => { const handleSumit = async (event) => { event.preventDefault(); - const { nom, isModeRelax, isActive, mandataire, chefDeFile } = formData; - const body: any = { + const { nom, - options: { relaxMode: isModeRelax }, - active: isActive, - mandataire: undefined, + isRelaxMode, + isActive, + mandataireId, + mandataire, + chefDeFileId, + } = formData; + const body: Partial = { + nom, + isRelaxMode, + isActive, + authorizationStrategy, }; + if ( - chefDeFile && - authorizationStrategy === - ClientApiDepotAuthorizationStrategyEnum.CHEF_DE_FILE + authorizationStrategy === AuthorizationStrategyEnum.CHEF_DE_FILE && + chefDeFileId ) { - body.chefDeFile = chefDeFile; + body.chefDeFileId = chefDeFileId; } try { - // Gestion du mandataire sélectionné ou créé - if (typeof mandataire === "object") { - const newMandataire = await createMandataire(mandataire, isDemo); - body.mandataire = newMandataire._id; - } else { - body.mandataire = mandataire; + if (mandataireId) { + body.mandataireId = mandataireId; + } else if (mandataire) { + const newMandataire: Mandataire = await createMandataire( + mandataire, + isDemo + ); + body.mandataireId = newMandataire.id; } - let _clientId = clientId; - if (_clientId) { - await updateClient(_clientId, body, isDemo); + if (clientId) { + await updateClient(clientId, body, isDemo); + await router.push({ + pathname: "/api-depot/client", + query: { clientId, demo: isDemo ? 1 : 0 }, + }); } else { const response = await createClient(body, isDemo); - _clientId = response._id; + await router.push({ + pathname: "/api-depot/client", + query: { clientId: response.id, demo: isDemo ? 1 : 0 }, + }); } - - await router.push({ - pathname: "/api-depot/client", - query: { clientId: _clientId, demo: isDemo ? 1 : 0 }, - }); } catch (error: unknown) { setSubmitError((error as any).message || ""); } @@ -155,14 +165,13 @@ const ClientForm = () => { return; } - const { nom, mandataire, chefDeFile } = formData; + const { nom, mandataireId, mandataire, chefDeFileId } = formData; - let isFormValid = nom && mandataire; + let isFormValid: any = nom && (mandataireId || mandataire); if ( - authorizationStrategy === - ClientApiDepotAuthorizationStrategyEnum.CHEF_DE_FILE && - !chefDeFile + authorizationStrategy === AuthorizationStrategyEnum.CHEF_DE_FILE && + !chefDeFileId ) { isFormValid = false; } @@ -171,16 +180,14 @@ const ClientForm = () => { }, [formData, authorizationStrategy]); const closeFormChefDeFile = async () => { - const chefsDeFile: ChefDeFileApiDepotType[] = await getChefsDeFile(isDemo); + const chefsDeFile: ChefDeFile[] = await getChefsDeFile(isDemo); setChefsDeFileOptions(chefsDeFile); setInitialChefDeFileForm(null); setIsformChefDeFileOpen(false); }; const openFormChefDeFile = (initialId: string = null) => { - setInitialChefDeFileForm( - chefsDeFileOptions.find((c) => c._id == initialId) - ); + setInitialChefDeFileForm(chefsDeFileOptions.find((c) => c.id == initialId)); setIsformChefDeFileOpen(true); }; @@ -208,8 +215,8 @@ const ClientForm = () => { { value={authorizationStrategy} options={authorizationStrategyOptions} handleChange={(value) => - setAuthorizationStrategy( - value as ClientApiDepotAuthorizationStrategyEnum - ) + setAuthorizationStrategy(value as AuthorizationStrategyEnum) } /> {authorizationStrategy === - ClientApiDepotAuthorizationStrategyEnum.CHEF_DE_FILE && ( + AuthorizationStrategyEnum.CHEF_DE_FILE && ( <> {isformChefDeFileOpen ? ( closeFormChefDeFile()} /> ) : ( <>
@@ -255,7 +261,7 @@ const ClientForm = () => { diff --git a/pages/communes/[code].tsx b/pages/communes/[code].tsx index a6038d0..61e41f6 100644 --- a/pages/communes/[code].tsx +++ b/pages/communes/[code].tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useMemo, useCallback } from "react"; import { toast } from "react-toastify"; import type { RevisionMoissoneurType } from "../../types/moissoneur"; -import type { RevisionApiDepotType } from "../../types/api-depot"; +import type { Revision as RevisionApiDepot } from "../../types/api-depot.types"; import type { BaseLocaleType } from "../../types/mes-adresses"; import { getCommune } from "@/lib/cog"; @@ -32,7 +32,7 @@ type CommuneSourcePageProps = { const CommuneSource = ({ code }: CommuneSourcePageProps) => { const [bals, setBals] = useState([]); const [initialRevisionsApiDepot, setInitialRevisionsApiDepot] = useState< - RevisionApiDepotType[] + RevisionApiDepot[] >([]); const [initialRevisionsMoissonneur, setInitialRevisionsMoissonneur] = useState([]); @@ -93,7 +93,7 @@ const CommuneSource = ({ code }: CommuneSourcePageProps) => { useEffect(() => { const fetchData = async () => { - const initialRevisionsApiDepot: RevisionApiDepotType[] = + const initialRevisionsApiDepot: RevisionApiDepot[] = await getAllRevisionByCommune(code); const initialRevisionsMoissonneur: RevisionMoissoneurType[] = await getRevisionsByCommune(code); diff --git a/types/api-depot.types.ts b/types/api-depot.types.ts new file mode 100644 index 0000000..ff1600b --- /dev/null +++ b/types/api-depot.types.ts @@ -0,0 +1,176 @@ +// HABILITATION + +export enum StatusHabilitationEnum { + ACCEPTED = "accepted", + PENDING = "pending", + REJECTED = "rejected", +} + +export enum TypeStrategyEnum { + EMAIL = "email", + FRANCECONNECT = "franceconnect", + INTERNAL = "internal", +} + +export type Mandat = { + nomMarital: string; + nomNaissance: string; + prenom: string; +}; + +export type Strategy = { + type: TypeStrategyEnum; + // EMAIL + pinCode?: string; + pinCodeExpiration?: Date | null; + createdAt?: Date | null; + remainingAttempts?: number; + // FRANCECONNECT + mandat?: Mandat; + authenticationError?: string; +}; + +export type Habilitation = { + id?: string; + clientId?: string; + codeCommune: string; + emailCommune: string; + franceconnectAuthenticationUrl?: string; + status: StatusHabilitationEnum; + strategy?: Strategy | null; + expiresAt?: Date; + acceptedAt?: Date; + rejectedAt?: Date; + createdAt?: Date; + updatedAt?: Date; +}; + +// FILE + +export enum TypeFileEnum { + BAL = "bal", +} +export type File = { + id?: string; + revisionId?: string; + size?: number; + hash?: string; + type?: TypeFileEnum; + createdAt?: Date; +}; + +export enum StatusRevisionEnum { + PENDING = "pending", + PUBLISHED = "published", +} + +// REVISION + +export type ParseError = { + type: string; + code: string; + message: string; + row: number; +}; + +export type Validation = { + valid: boolean; + validatorVersion?: string; + parseErrors?: ParseError[]; + errors?: string[]; + warnings?: string[]; + infos?: string[]; + rowsCount?: number; +}; + +export type Context = { + nomComplet?: string; + organisation?: string; + extras?: Record | null; +}; + +export type PublicClient = { + id: string; + legacyId?: string; + nom: string; + mandataire: string; + chefDeFile?: string; + chefDeFileEmail?: string; +}; + +export type Revision = { + id?: string; + clientId?: string; + codeCommune: string; + isReady: boolean; + isCurrent: boolean; + status: StatusRevisionEnum; + context?: Context; + validation?: Validation | null; + habilitation?: Habilitation | null; + files?: File[]; + client?: PublicClient; + publishedAt?: Date; + createdAt: Date; + updatedAt: Date; +}; + +// CHEF DE FILE + +export enum TypePerimeterEnum { + COMMUNE = "commune", + DEPARTEMENT = "departement", + EPCI = "epci", +} + +export type Perimeter = { + id?: string; + chefDeFileId?: string; + type: TypePerimeterEnum; + code: string; +}; + +export type ChefDeFile = { + id?: string; + nom?: string; + email?: string; + isEmailPublic?: boolean; + isSignataireCharte?: boolean; + perimeters?: Perimeter[]; + createdAt?: Date; + updatedAt?: Date; +}; + +// MANDATAIRE + +export type Mandataire = { + id?: string; + nom: string; + email: string; + createdAt?: Date; + updatedAt?: Date; +}; + +// CLIENT + +export enum AuthorizationStrategyEnum { + INTERNAL = "internal", + CHEF_DE_FILE = "chef-de-file", + HABILITATION = "habilitation", +} + +export type Client = { + id?: string; + mandataireId?: string; + chefDeFileId?: string; + legacyId: string; + nom: string; + isActive: boolean; + isRelaxMode: boolean; + token?: string; + authorizationStrategy: AuthorizationStrategyEnum; + createdAt: Date; + updatedAt: Date; + mandataire?: Mandataire; + chefDeFile?: ChefDeFile; +}; diff --git a/types/moissoneur.ts b/types/moissoneur.ts index 8cfe5eb..bdcfc1f 100644 --- a/types/moissoneur.ts +++ b/types/moissoneur.ts @@ -1,5 +1,5 @@ +import { Perimeter } from "./api-depot.types"; import { PartenaireDeLaCharte } from "../server/lib/partenaire-de-la-charte/entity"; -import { PerimeterType } from "./api-depot"; export type PageHarvests = { offset: number; @@ -96,7 +96,7 @@ export interface OrganizationMoissoneurType { name?: string; page?: string; logo?: string; - perimeters?: PerimeterType[]; + perimeters?: Perimeter[]; updatedAt?: Date; createdAt?: Date; deletedAt?: Date;