Skip to content

Commit

Permalink
feat: disable edit on national clauses + remove clause
Browse files Browse the repository at this point in the history
  • Loading branch information
ledouxm committed Jul 8, 2024
1 parent 81ebe72 commit af64a67
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 66 deletions.
243 changes: 179 additions & 64 deletions packages/frontend/src/features/menu/ClauseMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { Divider, Flex, Stack, styled } from "#styled-system/jsx";
import { Fragment } from "react/jsx-runtime";
import { MenuTitle } from "./MenuTitle";
import type { ModalContentProps } from "./MenuButton";
import { useEffect, useState } from "react";
import { ReactNode, useEffect, useState } from "react";
import Button from "@codegouvfr/react-dsfr/Button";
import { css } from "#styled-system/css";
import { Clause_v2 } from "@cr-vif/electric-client/frontend";
import Input from "@codegouvfr/react-dsfr/Input";
import { FormProvider, useFieldArray, useForm, useFormContext } from "react-hook-form";
import { useMutation } from "@tanstack/react-query";
import Select from "@codegouvfr/react-dsfr/Select";
import { v4 } from "uuid";

export const ClauseMenu = ({ isNational, ...props }: { isNational: boolean } & ModalContentProps) => {
const user = useUser()!;
Expand All @@ -31,6 +33,14 @@ export const ClauseMenu = ({ isNational, ...props }: { isNational: boolean } & M

if (!clausesQuery.updatedAt) return <Spinner />;

if (isNational)
return (
<>
<ClauseTitle isNational={isNational} buttons={null} {...props} />
<ClauseList clauses={(clausesQuery.results as any) ?? []} isEditing={false} />
</>
);

return (
<ClauseForm
clauses={clausesQuery.results?.map((c) => ({ ...c, text: c.text?.replaceAll("\\n", "\n") ?? "" })) ?? []}
Expand All @@ -54,13 +64,17 @@ const getDiff = (baseClauses: Clause_v2[], modifiedClauses: Clause_v2[]) => {

return { newClauses: newClauses, updatedClauses: updatedClauses, deletedClauses };
};
type Mode = "view" | "add" | "edit";

const ClauseForm = ({
clauses,
isNational,
...props
}: { clauses: Clause_v2[]; isNational: boolean } & ModalContentProps) => {
const [isEditing, setIsEditing] = useState(false);
const [mode, setMode] = useState<Mode>("view");

const isEditing = mode === "edit";
const isAdding = mode === "add";

const form = useForm<Form>({
defaultValues: {
Expand All @@ -75,6 +89,7 @@ const ClauseForm = ({
const { fields } = useFieldArray({
name: "clauses",
control: form.control,
keyName: "_id",
});

const applyDiffMutation = useMutation(
Expand All @@ -87,7 +102,7 @@ const ClauseForm = ({
}
},
{
onSuccess: () => setIsEditing(false),
onSuccess: () => setMode("view"),
},
);

Expand All @@ -97,70 +112,107 @@ const ClauseForm = ({
};

const fieldsWithIndex = fields.map((field, index) => ({ ...field, _index: index }));
const groupedByKey = groupBy(fieldsWithIndex, "key");

const buttons = isEditing ? (
<>
<Button
className={css({
"&::before": {
ml: "0 !important",
mr: { base: "0 !important", lg: "8px !important" },
},
})}
disabled={applyDiffMutation.isLoading}
iconId="ri-save-fill"
priority="primary"
type="submit"
>
<styled.span hideBelow="lg">Enregistrer</styled.span>
</Button>
</>
) : (
const buttons =
isEditing || isAdding ? (
<>
<Button
className={css({
"&::before": {
ml: "0 !important",
mr: { base: "0 !important", lg: "8px !important" },
},
})}
disabled={applyDiffMutation.isLoading}
iconId="ri-save-fill"
priority="primary"
type="submit"
nativeButtonProps={{
form: isEditing ? "edit-form" : "add-form",
}}
>
<styled.span hideBelow="lg">Enregistrer</styled.span>
</Button>
</>
) : (
<>
<Button
className={css({
"&::before": {
ml: "0 !important",
mr: { base: "0 !important", lg: "8px !important" },
},
})}
nativeButtonProps={{ type: "button" }}
iconId="ri-pencil-fill"
priority="secondary"
onClick={(e) => {
e.preventDefault();
setMode("edit");
}}
>
<styled.span hideBelow="lg">Modifier</styled.span>
</Button>
<Button
className={css({
"&::before": {
ml: "0 !important",
mr: { base: "0 !important", lg: "8px !important" },
},
})}
nativeButtonProps={{ type: "button" }}
iconId="ri-add-fill"
priority="secondary"
onClick={(e) => {
e.preventDefault();
setMode("add");
}}
>
<styled.span hideBelow="lg">Ajouter</styled.span>
</Button>
</>
);

return (
<>
<Button
className={css({
"&::before": {
ml: "0 !important",
mr: { base: "0 !important", lg: "8px !important" },
},
})}
nativeButtonProps={{ type: "button" }}
iconId="ri-pencil-fill"
priority="secondary"
onClick={(e) => {
e.preventDefault();
setIsEditing((isEditing) => !isEditing);
}}
>
<styled.span hideBelow="lg">Modifier</styled.span>
</Button>
<ClauseTitle isNational={isNational} buttons={buttons} {...props} />
{isAdding ? <ClauseAdd onSuccess={() => setMode("view")} isNational={isNational} /> : null}
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} id="edit-form">
<ClauseList clauses={fieldsWithIndex} isEditing={isEditing} />
</form>
</FormProvider>
</>
);
};

const ClauseTitle = ({
isNational,
buttons,
...props
}: { isNational: boolean; buttons?: ReactNode; isEditing?: boolean } & ModalContentProps) => (
<MenuTitle {...props} buttons={buttons}>
Clauses {isNational ? "nationales" : "départementales"}
</MenuTitle>
);

const ClauseList = ({ clauses, isEditing }: { clauses: ClauseWithIndex[]; isEditing: boolean }) => {
const groupedByKey = groupBy(clauses, "key");

return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<MenuTitle {...props} buttons={<Flex>{buttons}</Flex>}>
Clauses {isNational ? "nationales" : "départementales"}
</MenuTitle>
<Stack>
{Object.entries(groupedByKey).map(([key, clauses], index) => (
<Fragment key={key}>
<Stack gap="16px">
<styled.h2 fontSize="24px" fontWeight="bold">
{(clauseNameMap as any)[key] ?? key}
</styled.h2>
{clauses.map((clause) => (
<DivOrTextarea key={clause.id} clause={clause} isEditing={isEditing} />
))}
</Stack>
{index < Object.keys(groupedByKey).length - 1 && <Divider height="1px" my="16px" color="#C1C1FB" />}
</Fragment>
))}
</Stack>
</form>
</FormProvider>
<Stack>
{Object.entries(groupedByKey).map(([key, clauses], index) => (
<Fragment key={key}>
<Stack gap="24px">
<styled.h2 fontSize="20px">{(clauseNameMap as any)[key] ?? key}</styled.h2>
{clauses.map((clause) => (
<DivOrTextarea key={clause.id} clause={clause} isEditing={isEditing ?? false} />
))}
</Stack>
{index < Object.keys(groupedByKey).length - 1 && <Divider height="1px" my="16px" color="#C1C1FB" />}
</Fragment>
))}
</Stack>
);
};

Expand Down Expand Up @@ -191,11 +243,32 @@ const ClauseView = ({ clause }: { clause: ClauseWithIndex }) => {
const ClauseEdit = ({ clause }: { clause: ClauseWithIndex }) => {
const form = useFormContext<Form>();

const deleteClauseMutation = useMutation(
async () => {
await db.clause_v2.delete({ where: { id: clause.id } });
},
{
onSuccess: () => {},
},
);

return (
<Flex flexDir="column">
<styled.div color="text-action-high-blue-france" fontWeight="bold">
{clause.value}
</styled.div>
<Flex justifyContent="space-between" alignItems="center" w="100%">
<styled.div color="text-action-high-blue-france" fontWeight="bold">
{clause.value}
</styled.div>
<Button
iconId="ri-delete-bin-fill"
disabled={deleteClauseMutation.isLoading}
onClick={() => deleteClauseMutation.mutate()}
priority="secondary"
type="button"
size="small"
>
<styled.span hideBelow="lg">Supprimer</styled.span>
</Button>
</Flex>

<Input
label=""
Expand All @@ -209,6 +282,48 @@ const ClauseEdit = ({ clause }: { clause: ClauseWithIndex }) => {
);
};

const ClauseAdd = ({ onSuccess, isNational }: { onSuccess: () => void; isNational: boolean }) => {
const user = useUser()!;
const form = useForm<Clause_v2>({
defaultValues: { key: "", value: "", text: "", id: v4(), udap_id: isNational ? "ALL" : user.udap_id },
});
const keyOptions = Object.entries(clauseNameMap)
.map(([key, label]) => ({ value: key, label }))
.filter(({ value }) => {
const isNationalClause = nationalClauses.includes(value);
return isNational ? isNationalClause : !isNationalClause;
});

const addClauseMutation = useMutation(
async (clause: Clause_v2) => {
await db.clause_v2.create({ data: clause });
},
{
onSuccess,
},
);

return (
<form onSubmit={form.handleSubmit((data) => addClauseMutation.mutate(data))} id="add-form">
<Stack mb="48px">
<Select label="Catégorie" nativeSelectProps={form.register("key")}>
{keyOptions.map(({ value, label }) => (
<option key={value} value={value}>
{label}
</option>
))}
</Select>

<Input label="Intitulé" nativeInputProps={form.register("value")} />

<Input label="Texte" textArea nativeTextAreaProps={{ rows: 6, ...form.register("text") }} />
</Stack>
</form>
);
};

const nationalClauses = ["type-espace", "decision"];

const clauseNameMap = {
"type-espace": "Type d'espace",
decision: "Décision",
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/src/features/menu/MenuTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export const MenuTitle = ({
>
{children}
</styled.span>
<styled.div pl={{ base: "0", lg: "10px" }} fontSize="20px" fontWeight="bold" nowrap>
<Flex gap="16px" pl={{ base: "0", lg: "10px" }} fontSize="20px" fontWeight="bold" nowrap>
{buttons}
</styled.div>
</Flex>
<button
className="fr-btn--close fr-btn"
title="Fermer"
Expand Down

0 comments on commit af64a67

Please sign in to comment.