From 2b9b989d24e718b031a671127f279d46627a29c3 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Manningham <marc-antoine.m@outlook.com> Date: Tue, 12 Nov 2024 00:18:39 -0500 Subject: [PATCH] feat: add download csv button --- .../src/components/forms/ParticipantForm.tsx | 454 ++++++++++-------- 1 file changed, 246 insertions(+), 208 deletions(-) diff --git a/client/src/components/forms/ParticipantForm.tsx b/client/src/components/forms/ParticipantForm.tsx index 0a2a8c1..7e204e4 100644 --- a/client/src/components/forms/ParticipantForm.tsx +++ b/client/src/components/forms/ParticipantForm.tsx @@ -17,6 +17,34 @@ interface ParticipantRowProps { refetch: () => void } +function jsonToCsv(items: ParticipantPreview[]) { + if (items.length === 0) { + return "" + } + const headers = Object.keys(items[0] as any) + const csvRows = [ + headers + .filter((header) => { + return header != "contain_cv" && header != "id" + }) + .join(","), + ] + + items.forEach((item: any) => { + const values = headers + .filter((header) => { + return header != "contain_cv" && header != "id" + }) + .map((header) => { + const escaped = ("" + item[header]).replace(/"/g, '""') + return `"${escaped}"` + }) + csvRows.push(values.join(",")) + }) + + return csvRows.join("\n") +} + function ParticipantRow(props: ParticipantRowProps) { const p = props.participant const [isModalOpen, setIsModalOpen] = createSignal(false) @@ -76,13 +104,6 @@ function ParticipantRow(props: ParticipantRowProps) { ) } -async function fetchWrapper() { - console.log("fetching") - const participants = await fetchParticipants() - console.log(participants) - return participants -} - function getGivableRole() { const role = localStorage.getItem("role") if (role == "organizer") { @@ -121,231 +142,162 @@ function getGivableRole() { } export default function ParticipantForm() { - const [user, { refetch }] = createResource(fetchWrapper) + const [user, { refetch }] = createResource(fetchParticipants) const [_form, { Form, Field }] = createForm<MinimalParticipant>() const onSubmit = async (data: MinimalParticipant) => { - console.log("submitting") await submitMinimalParticipant(data) - console.log("refetching") refetch() } + const download = () => { + const csv = jsonToCsv(user()) + const blob = new Blob([csv], { type: "text/csv" }) + const url = window.URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = "participants.csv" + a.click() + window.URL.revokeObjectURL(url) + } return ( - <Form onSubmit={onSubmit}> - <table class="min-w-full border border-gray-300 bg-white"> - <thead> - <tr class="bg-gray-100"> - <th class="border-b border-gray-300 p-2 text-center"> - Prénom - </th> - <th class="border-b border-gray-300 p-2 text-center"> - Nom - </th> - <th class="border-b border-gray-300 p-2 text-center"> - Courriel - </th> - <th class="border-b border-gray-300 p-2 text-center"> - Compétition - </th> - <th class="border-b border-gray-300 p-2 text-center"> - Rôle - </th> - {localStorage.getItem("role") === "organizer" && ( + <div class="flex flex-col gap-4"> + <button + onclick={download} + class="w-64 rounded bg-blue-500 p-2 text-white hover:bg-blue-700" + > + Télécharger CSV + </button> + <Form onSubmit={onSubmit}> + <table class="min-w-full border border-gray-300 bg-white"> + <thead> + <tr class="bg-gray-100"> <th class="border-b border-gray-300 p-2 text-center"> - Université + Prénom + </th> + <th class="border-b border-gray-300 p-2 text-center"> + Nom + </th> + <th class="border-b border-gray-300 p-2 text-center"> + Courriel + </th> + <th class="border-b border-gray-300 p-2 text-center"> + Compétition + </th> + <th class="border-b border-gray-300 p-2 text-center"> + Rôle </th> - )} - <th class="border-b border-gray-300 p-2 text-center"> - Actions - </th> - </tr> - </thead> - <tbody> - <For each={user()}> - {(participant: ParticipantPreview) => ( - <ParticipantRow - participant={participant} - refetch={refetch} - /> - )} - </For> - {localStorage.getItem("role") !== "volunteer" && ( - <tr class="border-b border-gray-200"> - <td class="p-2"> - <Field name="first_name"> - {(field, props) => ( - <TextInput - {...props} - value={field.value} - error={field.error} - class="w-48" - type="text" - placeholder="Prénom" - required - /> - )} - </Field> - </td> - <td class="p-2"> - <Field name="last_name"> - {(field, props) => ( - <TextInput - {...props} - value={field.value} - error={field.error} - class="w-48" - type="text" - placeholder="Nom" - required - /> - )} - </Field> - </td> - <td class="p-2"> - <Field name="email"> - {(field, props) => ( - <TextInput - {...props} - value={field.value} - error={field.error} - type="email" - placeholder="exemple@courriel.com" - required - /> - )} - </Field> - </td> - <td class="p-2"> - <Field name="competition"> - {(field, props) => ( - <Select - {...props} - value={field.value} - error={field.error} - options={[ - { - label: "Aucune", - value: "none", - }, - { - label: "Conception Senior", - value: "conception_senior", - }, - { - label: "Conception Junior", - value: "conception_junior", - }, - { - label: "Débats oratoires", - value: "debats_oratoires", - }, - { - label: "Reingénierie", - value: "reingenierie", - }, - { - label: "Génie Conseil", - value: "genie_conseil", - }, - { - label: "Communication Scientifique", - value: "communication_scientifique", - }, - { - label: "Programmation", - value: "programmation", - }, - { - label: "Conception innovatrice", - value: "conception_innovatrice", - }, - { - label: "Cycle supérieur", - value: "cycle_superieur", - }, - ]} - required - /> - )} - </Field> - </td> - <td class="p-2"> - <Field name="role"> - {(field, props) => ( - <Select - {...props} - value={field.value} - error={field.error} - options={getGivableRole()} - required - /> - )} - </Field> - </td> {localStorage.getItem("role") === "organizer" && ( + <th class="border-b border-gray-300 p-2 text-center"> + Université + </th> + )} + <th class="border-b border-gray-300 p-2 text-center"> + Actions + </th> + </tr> + </thead> + <tbody> + <For each={user()}> + {(participant: ParticipantPreview) => ( + <ParticipantRow + participant={participant} + refetch={refetch} + /> + )} + </For> + {localStorage.getItem("role") !== "volunteer" && ( + <tr class="border-b border-gray-200"> + <td class="p-2"> + <Field name="first_name"> + {(field, props) => ( + <TextInput + {...props} + value={field.value} + error={field.error} + class="w-48" + type="text" + placeholder="Prénom" + required + /> + )} + </Field> + </td> <td class="p-2"> - <Field name="university"> + <Field name="last_name"> + {(field, props) => ( + <TextInput + {...props} + value={field.value} + error={field.error} + class="w-48" + type="text" + placeholder="Nom" + required + /> + )} + </Field> + </td> + <td class="p-2"> + <Field name="email"> + {(field, props) => ( + <TextInput + {...props} + value={field.value} + error={field.error} + type="email" + placeholder="exemple@courriel.com" + required + /> + )} + </Field> + </td> + <td class="p-2"> + <Field name="competition"> {(field, props) => ( <Select {...props} - value="none" + value={field.value} error={field.error} options={[ { - label: "UQAC", - value: "uqac", - }, - { - label: "UQAR", - value: "uqar", - }, - { - label: "UQAT", - value: "uqat", - }, - { - label: "UQO", - value: "uqo", - }, - { - label: "UQTR", - value: "uqtr", + label: "Aucune", + value: "none", }, { - label: "McGill", - value: "mcgill", + label: "Conception Senior", + value: "conception_senior", }, { - label: "McGill Macdonald", - value: "mcgill_macdonald", + label: "Conception Junior", + value: "conception_junior", }, { - label: "Concordia", - value: "concordia", + label: "Débats oratoires", + value: "debats_oratoires", }, { - label: "ETS", - value: "ets", + label: "Reingénierie", + value: "reingenierie", }, { - label: "PolyMTL", - value: "polymtl", + label: "Génie Conseil", + value: "genie_conseil", }, { - label: "ULaval", - value: "ulaval", + label: "Communication Scientifique", + value: "communication_scientifique", }, { - label: "Drummondville", - value: "drummond", + label: "Programmation", + value: "programmation", }, { - label: "UDS", - value: "uds", + label: "Conception innovatrice", + value: "conception_innovatrice", }, { - label: "Aucune", - value: "none", + label: "Cycle supérieur", + value: "cycle_superieur", }, ]} required @@ -353,16 +305,102 @@ export default function ParticipantForm() { )} </Field> </td> - )} - <td class="p-2"> - <button class="rounded bg-green-500 p-1 font-bold text-white hover:bg-green-700"> - <PlusCircle class="h-8 w-8"></PlusCircle> - </button> - </td> - </tr> - )} - </tbody> - </table> - </Form> + <td class="p-2"> + <Field name="role"> + {(field, props) => ( + <Select + {...props} + value={field.value} + error={field.error} + options={getGivableRole()} + required + /> + )} + </Field> + </td> + {localStorage.getItem("role") === + "organizer" && ( + <td class="p-2"> + <Field name="university"> + {(field, props) => ( + <Select + {...props} + value="none" + error={field.error} + options={[ + { + label: "UQAC", + value: "uqac", + }, + { + label: "UQAR", + value: "uqar", + }, + { + label: "UQAT", + value: "uqat", + }, + { + label: "UQO", + value: "uqo", + }, + { + label: "UQTR", + value: "uqtr", + }, + { + label: "McGill", + value: "mcgill", + }, + { + label: "McGill Macdonald", + value: "mcgill_macdonald", + }, + { + label: "Concordia", + value: "concordia", + }, + { + label: "ETS", + value: "ets", + }, + { + label: "PolyMTL", + value: "polymtl", + }, + { + label: "ULaval", + value: "ulaval", + }, + { + label: "Drummondville", + value: "drummond", + }, + { + label: "UDS", + value: "uds", + }, + { + label: "Aucune", + value: "none", + }, + ]} + required + /> + )} + </Field> + </td> + )} + <td class="p-2"> + <button class="rounded bg-green-500 p-1 font-bold text-white hover:bg-green-700"> + <PlusCircle class="h-8 w-8"></PlusCircle> + </button> + </td> + </tr> + )} + </tbody> + </table> + </Form> + </div> ) } -- GitLab