Skip to content

Commit

Permalink
feat: ajouter une nouvelle contribution
Browse files Browse the repository at this point in the history
  • Loading branch information
m-maillot committed Jan 9, 2025
1 parent 9a53b92 commit 1c475cc
Show file tree
Hide file tree
Showing 13 changed files with 268 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export const generateMetadata = (
contribution: DocumentElasticWithSource<ContributionDocumentJson>,
breadcrumbs: Breadcrumb[]
): ContributionMetadata => {
if (breadcrumbs.length === 0) {
throw new Error(
`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.`
);
}

if (contribution.idcc === "0000") {
return generateGenericMetadata(contribution);
}
Expand All @@ -25,12 +31,6 @@ export const generateMetadata = (
);
}

if (breadcrumbs.length === 0) {
throw new Error(
`Contribution ${contribution.questionIndex} - ${contribution.questionName} (${contribution.id}) must be themed`
);
}

return generateCustomMetadata(
contribution,
breadcrumbs[breadcrumbs.length - 1],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
useQuestionListQuery,
} from "./QuestionList.query";
import { QuestionRow } from "./QuestionRow";
import { useRouter } from "next/router";

export const countAnswersWithStatus = (
answers: QueryQuestionAnswer[] | undefined,
Expand All @@ -29,6 +30,7 @@ export const countAnswersWithStatus = (
};

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

const { rows } = useQuestionListQuery({
Expand All @@ -53,6 +55,14 @@ export const QuestionList = (): JSX.Element => {
}}
data-testid="contributions-list-search"
/>
<Button
variant="contained"
onClick={() => {
router.push(`/contributions/questions/creation`);
}}
>
Créer une nouvelle contribution
</Button>
</Stack>

<TableContainer component={Paper}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,39 @@ import { SnackBar } from "../../../utils/SnackBar";
import { LoadingButton } from "../../../button/LoadingButton";

type EditQuestionProps = {
question: QuestionBase;
question?: QuestionBase;
messages: Message[];
onUpsert: (props: QuestionFormData) => Promise<void>;
defaultOrder?: number;
onSubmit: (props: QuestionFormData) => Promise<void>;
};

const formDataSchema = z.object({
message_id: questionRelationSchema.shape.message_id.or(z.literal("")),
content: questionRelationSchema.shape.content,
seo_title: questionRelationSchema.shape.seo_title,
order: questionRelationSchema.shape.order,
});

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

export const Form = ({
question,
messages,
onUpsert,
defaultOrder,
onSubmit,
}: EditQuestionProps): JSX.Element => {
const { control, watch, handleSubmit } = useForm<QuestionFormData>({
resolver: zodResolver(formDataSchema),
shouldFocusError: true,
defaultValues: {
content: question.content,
message_id: question.message_id ?? "",
seo_title: question.seo_title ?? "",
content: question?.content ?? "",
message_id: question?.message_id ?? "",
seo_title: question?.seo_title ?? "",
order: question?.order ?? defaultOrder ?? -1,
},
});
const [message, setMessage] = useState<Message | undefined>(undefined);
const watchMessageId = watch("message_id", question.message_id);
const watchMessageId = watch("message_id", question?.message_id);
const [submitting, setSubmit] = useState<boolean>(false);

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

const onSubmit = async (formData: QuestionFormData) => {
const onSubmitForm = async (formData: QuestionFormData) => {
setSubmit(true);
try {
await onUpsert(formData);
await onSubmit(formData);
setSnack({ open: true, severity: "success", message: "Sauvegardé" });
} catch (e: any) {
setSnack({
Expand All @@ -87,14 +91,31 @@ export const Form = ({

return (
<Stack mt={4} spacing={2}>
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit(onSubmitForm)}>
<Stack spacing={4}>
<FormTextField
name="order"
type="number"
control={control}
hintText={
<>
Cette valeur est indicative. Vous pouvez modifier cette valeur
mais elle ne doit pas déjà être utilisée.{" "}
<b>
Il est recommandé de ne pas modifier la valeur par défaut.
</b>
</>
}
label="Ordre"
fullWidth
disabled={question !== undefined}
/>
<FormTextField
name="content"
control={control}
label="Nom de la question"
fullWidth
disabled
disabled={question !== undefined}
/>
<FormTextField
name="seo_title"
Expand Down Expand Up @@ -245,7 +266,7 @@ export const Form = ({
loading={submitting}
type="submit"
>
Sauvegarder
{question ? "Sauvegarder" : "Créer"}
</LoadingButton>
</Stack>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Form";
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Form, QuestionFormData } from "../common";
import { useQuestionCreationMutation } from "./question.mutation";
import { useQuestionCreationDataQuery } from "./question.query";
import { useRouter } from "next/router";
import { Link, Stack, Typography } from "@mui/material";
import SentimentVeryDissatisfiedIcon from "@mui/icons-material/SentimentVeryDissatisfied";

export const NewQuestion = (): JSX.Element => {
const router = useRouter();
const { data } = useQuestionCreationDataQuery();
const create = useQuestionCreationMutation();

const onCreate = async (formData: QuestionFormData) => {
console.log("On create new contrib");
if (!data) {
throw new Error("Missing agreement list to create a new question");
}
const result = await create({
order: formData.order,
seo_title: formData.seo_title ? formData.seo_title : undefined,
content: formData.content,
message_id: formData.message_id ? formData.message_id : undefined, // use to transform empty string sent by the form to undefined
answers: {
data: data.agreementIds.map((item) => ({
display_date: "01/01/2025",
agreement_id: item.id,
content_type: item.unextended ? "NOTHING" : undefined,
})),
},
});

router.push(
`/contributions/questions/${result.insert_contribution_questions_one.id}`
);
};
if (!data) {
return (
<Stack alignItems="center" spacing={2}>
<SentimentVeryDissatisfiedIcon color="error" sx={{ fontSize: 70 }} />
<Typography variant="h5" component="h3" color="error">
Une erreur est survenue
</Typography>
<Link href={"/contributions"}>Retour à la liste des contributions</Link>
</Stack>
);
}
return (
<>
<Form
messages={data.messages}
onSubmit={onCreate}
defaultOrder={data.nextOrder}
/>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { gql, useMutation } from "urql";

import { Answer, Question } from "../../type";

const questionCreationMutation = gql`
mutation CreateQuestion($question: contribution_questions_insert_input!) {
insert_contribution_questions_one(object: $question) {
id
}
}
`;

type MutationProps = {
question: Pick<Question, "content" | "message_id" | "seo_title" | "order"> & {
answers: {
data: {
display_date: Answer["displayDate"];
agreement_id: Answer["agreementId"];
content_type?: Answer["contentType"];
}[];
};
};
};

type Result = {
insert_contribution_questions_one: {
id: string;
};
};

export type MutationResult = (
props: MutationProps["question"]
) => Promise<Result>;

export const useQuestionCreationMutation = (): MutationResult => {
const [, executeUpdate] = useMutation<Result, MutationProps>(
questionCreationMutation
);
const resultFunction = async (question: MutationProps["question"]) => {
const result = await executeUpdate({ question });
if (result.error) {
throw new Error(result.error.message);
}
if (!result.data) {
throw new Error(
"No data returned from 'useQuestionCreationMutation' mutation"
);
}
return result.data;
};
return resultFunction;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { CombinedError, gql, useQuery } from "urql";
import { Message } from "../../type";

export const contributionQuestionCreationDataQuery = gql`
query SelectQuestionCreationData {
messages: contribution_question_messages {
contentAgreement
contentLegal
contentNotHandled
contentNotHandledWithoutLegal
contentAgreementWithoutLegal
id
label
}
agreements: agreement_agreements(where: { isSupported: { _eq: true } }) {
id
unextended
}
maxOrder: contribution_questions(order_by: { order: desc }, limit: 1) {
order
}
}
`;

type QueryOutput = {
messages: Message[];
agreements: { id: string; unextended: boolean }[];
maxOrder: [{ order: number }];
};

export type QueryResult = {
messages: Message[];
agreementIds: { id: string; unextended: boolean }[];
nextOrder: number;
};

type Result = {
data?: QueryResult;
error?: CombinedError;
fetching: boolean;
};

export const useQuestionCreationDataQuery = (): Result => {
const [{ data, error, fetching }] = useQuery<QueryOutput>({
query: contributionQuestionCreationDataQuery,
requestPolicy: "cache-and-network",
});

let formattedData: Result["data"] = undefined;
if (data) {
formattedData = {
messages: data.messages,
agreementIds: data.agreements
.flatMap((item) => item)
.concat([{ id: "0000", unextended: false }]),
nextOrder: data.maxOrder[0].order + 1,
};
}
return {
data: formattedData,
fetching,
error,
};
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";

import { Form, QuestionFormData } from "./Form";
import { Form, QuestionFormData } from "../common";
import { useQuestionUpdateMutation } from "./Question.mutation";
import { useQuestionQuery } from "./Question.query";

Expand Down Expand Up @@ -29,7 +29,7 @@ export const EditQuestion = ({
<Form
question={data.question}
messages={data.messages}
onUpsert={onUpdate}
onSubmit={onUpdate}
/>
)}
</>
Expand Down
2 changes: 1 addition & 1 deletion targets/frontend/src/components/contributions/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export const questionBaseSchema = z.object({
required_error: "Une question doit être renseignée",
})
.min(1, "Une question doit être renseignée"),
order: z.number(),
order: z.coerce.number(),
message_id: z.string().uuid().optional(),
seo_title: z.string().optional(),
});
Expand Down
2 changes: 1 addition & 1 deletion targets/frontend/src/components/forms/TextField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type FormTextFieldProps = CommonFormProps & {
labelFixed?: boolean;
id?: string;
type?: React.InputHTMLAttributes<unknown>["type"];
hintText?: string;
hintText?: string | React.ReactElement;
placeholder?: string;
};

Expand Down
Loading

0 comments on commit 1c475cc

Please sign in to comment.