Skip to content

Commit 8901636

Browse files
committed
feat: ajouter une nouvelle contribution
1 parent 9a53b92 commit 8901636

File tree

13 files changed

+267
-28
lines changed

13 files changed

+267
-28
lines changed

targets/export-elasticsearch/src/ingester/contributions/generateMetadata.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ export const generateMetadata = (
1111
contribution: DocumentElasticWithSource<ContributionDocumentJson>,
1212
breadcrumbs: Breadcrumb[]
1313
): ContributionMetadata => {
14+
if (breadcrumbs.length === 0) {
15+
throw new Error(
16+
`Merci d'assigner un thème à la contribution ${contribution.questionIndex} - ${contribution.questionName} (${contribution.id}). Cette opération est disponible dans le menu Vérifications -> Contenus sans thèmes.`
17+
);
18+
}
19+
1420
if (contribution.idcc === "0000") {
1521
return generateGenericMetadata(contribution);
1622
}
@@ -25,12 +31,6 @@ export const generateMetadata = (
2531
);
2632
}
2733

28-
if (breadcrumbs.length === 0) {
29-
throw new Error(
30-
`Contribution ${contribution.questionIndex} - ${contribution.questionName} (${contribution.id}) must be themed`
31-
);
32-
}
33-
3434
return generateCustomMetadata(
3535
contribution,
3636
breadcrumbs[breadcrumbs.length - 1],

targets/frontend/src/components/contributions/questionList/QuestionList.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
useQuestionListQuery,
1818
} from "./QuestionList.query";
1919
import { QuestionRow } from "./QuestionRow";
20+
import { useRouter } from "next/router";
2021

2122
export const countAnswersWithStatus = (
2223
answers: QueryQuestionAnswer[] | undefined,
@@ -29,6 +30,7 @@ export const countAnswersWithStatus = (
2930
};
3031

3132
export const QuestionList = (): JSX.Element => {
33+
const router = useRouter();
3234
const [search, setSearch] = useState<string | undefined>();
3335

3436
const { rows } = useQuestionListQuery({
@@ -53,6 +55,14 @@ export const QuestionList = (): JSX.Element => {
5355
}}
5456
data-testid="contributions-list-search"
5557
/>
58+
<Button
59+
variant="contained"
60+
onClick={() => {
61+
router.push(`/contributions/questions/creation`);
62+
}}
63+
>
64+
Créer une nouvelle contribution
65+
</Button>
5666
</Stack>
5767

5868
<TableContainer component={Paper}>

targets/frontend/src/components/contributions/questions/edit/Form.tsx renamed to targets/frontend/src/components/contributions/questions/common/Form.tsx

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,35 +22,39 @@ import { SnackBar } from "../../../utils/SnackBar";
2222
import { LoadingButton } from "../../../button/LoadingButton";
2323

2424
type EditQuestionProps = {
25-
question: QuestionBase;
25+
question?: QuestionBase;
2626
messages: Message[];
27-
onUpsert: (props: QuestionFormData) => Promise<void>;
27+
defaultOrder?: number;
28+
onSubmit: (props: QuestionFormData) => Promise<void>;
2829
};
2930

3031
const formDataSchema = z.object({
3132
message_id: questionRelationSchema.shape.message_id.or(z.literal("")),
3233
content: questionRelationSchema.shape.content,
3334
seo_title: questionRelationSchema.shape.seo_title,
35+
order: questionRelationSchema.shape.order,
3436
});
3537

3638
export type QuestionFormData = z.infer<typeof formDataSchema>;
3739

3840
export const Form = ({
3941
question,
4042
messages,
41-
onUpsert,
43+
defaultOrder,
44+
onSubmit,
4245
}: EditQuestionProps): JSX.Element => {
4346
const { control, watch, handleSubmit } = useForm<QuestionFormData>({
4447
resolver: zodResolver(formDataSchema),
4548
shouldFocusError: true,
4649
defaultValues: {
47-
content: question.content,
48-
message_id: question.message_id ?? "",
49-
seo_title: question.seo_title ?? "",
50+
content: question?.content ?? "",
51+
message_id: question?.message_id ?? "",
52+
seo_title: question?.seo_title ?? "",
53+
order: question?.order ?? defaultOrder ?? -1,
5054
},
5155
});
5256
const [message, setMessage] = useState<Message | undefined>(undefined);
53-
const watchMessageId = watch("message_id", question.message_id);
57+
const watchMessageId = watch("message_id", question?.message_id);
5458
const [submitting, setSubmit] = useState<boolean>(false);
5559

5660
const [snack, setSnack] = useState<{
@@ -70,10 +74,10 @@ export const Form = ({
7074
}
7175
}, [watchMessageId, messages]);
7276

73-
const onSubmit = async (formData: QuestionFormData) => {
77+
const onSubmitForm = async (formData: QuestionFormData) => {
7478
setSubmit(true);
7579
try {
76-
await onUpsert(formData);
80+
await onSubmit(formData);
7781
setSnack({ open: true, severity: "success", message: "Sauvegardé" });
7882
} catch (e: any) {
7983
setSnack({
@@ -87,14 +91,31 @@ export const Form = ({
8791

8892
return (
8993
<Stack mt={4} spacing={2}>
90-
<form onSubmit={handleSubmit(onSubmit)}>
94+
<form onSubmit={handleSubmit(onSubmitForm)}>
9195
<Stack spacing={4}>
96+
<FormTextField
97+
name="order"
98+
type="number"
99+
control={control}
100+
hintText={
101+
<>
102+
Cette valeur est indicative. Vous pouvez modifier cette valeur
103+
mais elle ne doit pas déjà être utilisée.{" "}
104+
<b>
105+
Il est recommandé de ne pas modifier la valeur par défaut.
106+
</b>
107+
</>
108+
}
109+
label="Ordre"
110+
fullWidth
111+
disabled={question !== undefined}
112+
/>
92113
<FormTextField
93114
name="content"
94115
control={control}
95116
label="Nom de la question"
96117
fullWidth
97-
disabled
118+
disabled={question !== undefined}
98119
/>
99120
<FormTextField
100121
name="seo_title"
@@ -245,7 +266,7 @@ export const Form = ({
245266
loading={submitting}
246267
type="submit"
247268
>
248-
Sauvegarder
269+
{question ? "Sauvegarder" : "Créer"}
249270
</LoadingButton>
250271
</Stack>
251272
</Stack>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./Form";
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Form, QuestionFormData } from "../common";
2+
import { useQuestionCreationMutation } from "./question.mutation";
3+
import { useQuestionCreationDataQuery } from "./question.query";
4+
import { useRouter } from "next/router";
5+
import { Link, Stack, Typography } from "@mui/material";
6+
import SentimentVeryDissatisfiedIcon from "@mui/icons-material/SentimentVeryDissatisfied";
7+
8+
export const NewQuestion = (): JSX.Element => {
9+
const router = useRouter();
10+
const { data } = useQuestionCreationDataQuery();
11+
const create = useQuestionCreationMutation();
12+
13+
const onCreate = async (formData: QuestionFormData) => {
14+
if (!data) {
15+
throw new Error("Missing agreement list to create a new question");
16+
}
17+
const result = await create({
18+
order: formData.order,
19+
seo_title: formData.seo_title ? formData.seo_title : undefined,
20+
content: formData.content,
21+
message_id: formData.message_id ? formData.message_id : undefined, // use to transform empty string sent by the form to undefined
22+
answers: {
23+
data: data.agreementIds.map((item) => ({
24+
display_date: "01/01/2025",
25+
agreement_id: item.id,
26+
content_type: item.unextended ? "NOTHING" : undefined,
27+
})),
28+
},
29+
});
30+
31+
router.push(
32+
`/contributions/questions/${result.insert_contribution_questions_one.id}`
33+
);
34+
};
35+
if (!data) {
36+
return (
37+
<Stack alignItems="center" spacing={2}>
38+
<SentimentVeryDissatisfiedIcon color="error" sx={{ fontSize: 70 }} />
39+
<Typography variant="h5" component="h3" color="error">
40+
Une erreur est survenue
41+
</Typography>
42+
<Link href={"/contributions"}>Retour à la liste des contributions</Link>
43+
</Stack>
44+
);
45+
}
46+
return (
47+
<>
48+
<Form
49+
messages={data.messages}
50+
onSubmit={onCreate}
51+
defaultOrder={data.nextOrder}
52+
/>
53+
</>
54+
);
55+
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { gql, useMutation } from "urql";
2+
3+
import { Answer, Question } from "../../type";
4+
5+
const questionCreationMutation = gql`
6+
mutation CreateQuestion($question: contribution_questions_insert_input!) {
7+
insert_contribution_questions_one(object: $question) {
8+
id
9+
}
10+
}
11+
`;
12+
13+
type MutationProps = {
14+
question: Pick<Question, "content" | "message_id" | "seo_title" | "order"> & {
15+
answers: {
16+
data: {
17+
display_date: Answer["displayDate"];
18+
agreement_id: Answer["agreementId"];
19+
content_type?: Answer["contentType"];
20+
}[];
21+
};
22+
};
23+
};
24+
25+
type Result = {
26+
insert_contribution_questions_one: {
27+
id: string;
28+
};
29+
};
30+
31+
export type MutationResult = (
32+
props: MutationProps["question"]
33+
) => Promise<Result>;
34+
35+
export const useQuestionCreationMutation = (): MutationResult => {
36+
const [, executeUpdate] = useMutation<Result, MutationProps>(
37+
questionCreationMutation
38+
);
39+
const resultFunction = async (question: MutationProps["question"]) => {
40+
const result = await executeUpdate({ question });
41+
if (result.error) {
42+
throw new Error(result.error.message);
43+
}
44+
if (!result.data) {
45+
throw new Error(
46+
"No data returned from 'useQuestionCreationMutation' mutation"
47+
);
48+
}
49+
return result.data;
50+
};
51+
return resultFunction;
52+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { CombinedError, gql, useQuery } from "urql";
2+
import { Message } from "../../type";
3+
4+
export const contributionQuestionCreationDataQuery = gql`
5+
query SelectQuestionCreationData {
6+
messages: contribution_question_messages {
7+
contentAgreement
8+
contentLegal
9+
contentNotHandled
10+
contentNotHandledWithoutLegal
11+
contentAgreementWithoutLegal
12+
id
13+
label
14+
}
15+
agreements: agreement_agreements(where: { isSupported: { _eq: true } }) {
16+
id
17+
unextended
18+
}
19+
maxOrder: contribution_questions(order_by: { order: desc }, limit: 1) {
20+
order
21+
}
22+
}
23+
`;
24+
25+
type QueryOutput = {
26+
messages: Message[];
27+
agreements: { id: string; unextended: boolean }[];
28+
maxOrder: [{ order: number }];
29+
};
30+
31+
export type QueryResult = {
32+
messages: Message[];
33+
agreementIds: { id: string; unextended: boolean }[];
34+
nextOrder: number;
35+
};
36+
37+
type Result = {
38+
data?: QueryResult;
39+
error?: CombinedError;
40+
fetching: boolean;
41+
};
42+
43+
export const useQuestionCreationDataQuery = (): Result => {
44+
const [{ data, error, fetching }] = useQuery<QueryOutput>({
45+
query: contributionQuestionCreationDataQuery,
46+
requestPolicy: "cache-and-network",
47+
});
48+
49+
let formattedData: Result["data"] = undefined;
50+
if (data) {
51+
formattedData = {
52+
messages: data.messages,
53+
agreementIds: data.agreements
54+
.flatMap((item) => item)
55+
.concat([{ id: "0000", unextended: false }]),
56+
nextOrder: data.maxOrder[0].order + 1,
57+
};
58+
}
59+
return {
60+
data: formattedData,
61+
fetching,
62+
error,
63+
};
64+
};

targets/frontend/src/components/contributions/questions/edit/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react";
22

3-
import { Form, QuestionFormData } from "./Form";
3+
import { Form, QuestionFormData } from "../common";
44
import { useQuestionUpdateMutation } from "./Question.mutation";
55
import { useQuestionQuery } from "./Question.query";
66

@@ -29,7 +29,7 @@ export const EditQuestion = ({
2929
<Form
3030
question={data.question}
3131
messages={data.messages}
32-
onUpsert={onUpdate}
32+
onSubmit={onUpdate}
3333
/>
3434
)}
3535
</>

targets/frontend/src/components/contributions/type.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export const questionBaseSchema = z.object({
154154
required_error: "Une question doit être renseignée",
155155
})
156156
.min(1, "Une question doit être renseignée"),
157-
order: z.number(),
157+
order: z.coerce.number(),
158158
message_id: z.string().uuid().optional(),
159159
seo_title: z.string().optional(),
160160
});

targets/frontend/src/components/forms/TextField/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type FormTextFieldProps = CommonFormProps & {
1616
labelFixed?: boolean;
1717
id?: string;
1818
type?: React.InputHTMLAttributes<unknown>["type"];
19-
hintText?: string;
19+
hintText?: string | React.ReactElement;
2020
placeholder?: string;
2121
};
2222

0 commit comments

Comments
 (0)