From 7c91f6c69b5ab9327f16fff54e1cf466d7de4f83 Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Thu, 16 Jan 2025 14:51:53 +0100 Subject: [PATCH 01/14] Init module PolyMobility & creating pages --- fresh.gen.ts | 9 +++++++++ routes/(apps)/mobility/(_props)/props.ts | 9 +++++++-- .../mobility/partials/(admin)/mobility.tsx | 17 +++++++++++++++++ .../mobility/partials/(admin)/students.tsx | 17 +++++++++++++++++ routes/(apps)/mobility/partials/overview.tsx | 17 +++++++++++++++++ 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 routes/(apps)/mobility/partials/(admin)/mobility.tsx create mode 100644 routes/(apps)/mobility/partials/(admin)/students.tsx create mode 100644 routes/(apps)/mobility/partials/overview.tsx diff --git a/fresh.gen.ts b/fresh.gen.ts index 62756f9..73d132d 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -4,7 +4,10 @@ import * as $_apps_layout from "./routes/(apps)/_layout.tsx"; import * as $_apps_mobility_index from "./routes/(apps)/mobility/index.tsx"; +import * as $_apps_mobility_partials_admin_mobility from "./routes/(apps)/mobility/partials/(admin)/mobility.tsx"; +import * as $_apps_mobility_partials_admin_students from "./routes/(apps)/mobility/partials/(admin)/students.tsx"; import * as $_apps_mobility_partials_index from "./routes/(apps)/mobility/partials/index.tsx"; +import * as $_apps_mobility_partials_overview from "./routes/(apps)/mobility/partials/overview.tsx"; import * as $_apps_notes_index from "./routes/(apps)/notes/index.tsx"; import * as $_apps_notes_partials_admin_courses from "./routes/(apps)/notes/partials/(admin)/courses.tsx"; import * as $_apps_notes_partials_admin_students from "./routes/(apps)/notes/partials/(admin)/students.tsx"; @@ -26,8 +29,14 @@ const manifest = { routes: { "./routes/(apps)/_layout.tsx": $_apps_layout, "./routes/(apps)/mobility/index.tsx": $_apps_mobility_index, + "./routes/(apps)/mobility/partials/(admin)/mobility.tsx": + $_apps_mobility_partials_admin_mobility, + "./routes/(apps)/mobility/partials/(admin)/students.tsx": + $_apps_mobility_partials_admin_students, "./routes/(apps)/mobility/partials/index.tsx": $_apps_mobility_partials_index, + "./routes/(apps)/mobility/partials/overview.tsx": + $_apps_mobility_partials_overview, "./routes/(apps)/notes/index.tsx": $_apps_notes_index, "./routes/(apps)/notes/partials/(admin)/courses.tsx": $_apps_notes_partials_admin_courses, diff --git a/routes/(apps)/mobility/(_props)/props.ts b/routes/(apps)/mobility/(_props)/props.ts index f9c0b54..a5fe572 100644 --- a/routes/(apps)/mobility/(_props)/props.ts +++ b/routes/(apps)/mobility/(_props)/props.ts @@ -3,8 +3,13 @@ import { AppProperties } from "$root/defaults/interfaces.ts"; const properties: AppProperties = { name: "PolyMobility", icon: "flight_takeoff", - hint: "Gestionnaire de mobilité", - pages: {}, + hint: "Student mobility management", + pages: { + index: "Homepage", + overview: "Mobility overview", + mobility: "Mobility management", + students: "Students management", + }, adminOnly: [], }; diff --git a/routes/(apps)/mobility/partials/(admin)/mobility.tsx b/routes/(apps)/mobility/partials/(admin)/mobility.tsx new file mode 100644 index 0000000..a73c859 --- /dev/null +++ b/routes/(apps)/mobility/partials/(admin)/mobility.tsx @@ -0,0 +1,17 @@ +import { Partial } from "$fresh/runtime.ts"; +import { RouteConfig } from "$fresh/server.ts"; + +type ModulesProps = Record; + +export const config: RouteConfig = { + skipAppWrapper: true, + skipInheritedLayouts: true, +}; + +export default function Modules(_props: ModulesProps) { + return ( + + mobility + + ); +} diff --git a/routes/(apps)/mobility/partials/(admin)/students.tsx b/routes/(apps)/mobility/partials/(admin)/students.tsx new file mode 100644 index 0000000..a73c859 --- /dev/null +++ b/routes/(apps)/mobility/partials/(admin)/students.tsx @@ -0,0 +1,17 @@ +import { Partial } from "$fresh/runtime.ts"; +import { RouteConfig } from "$fresh/server.ts"; + +type ModulesProps = Record; + +export const config: RouteConfig = { + skipAppWrapper: true, + skipInheritedLayouts: true, +}; + +export default function Modules(_props: ModulesProps) { + return ( + + mobility + + ); +} diff --git a/routes/(apps)/mobility/partials/overview.tsx b/routes/(apps)/mobility/partials/overview.tsx new file mode 100644 index 0000000..a73c859 --- /dev/null +++ b/routes/(apps)/mobility/partials/overview.tsx @@ -0,0 +1,17 @@ +import { Partial } from "$fresh/runtime.ts"; +import { RouteConfig } from "$fresh/server.ts"; + +type ModulesProps = Record; + +export const config: RouteConfig = { + skipAppWrapper: true, + skipInheritedLayouts: true, +}; + +export default function Modules(_props: ModulesProps) { + return ( + + mobility + + ); +} From a24f8b35c28839b307ca5ea8b22b1e44eeb7f470 Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Fri, 17 Jan 2025 10:40:49 +0100 Subject: [PATCH 02/14] Temporary student manager --- fresh.gen.ts | 12 +- .../mobility/(_islands)/SaveStudents.tsx | 82 ++++++++++++ .../mobility/(_islands)/UploadStudents.tsx | 123 ++++++++++++++++++ routes/(apps)/mobility/(_props)/props.ts | 2 +- .../mobility/partials/(admin)/students.tsx | 17 --- routes/(apps)/mobility/partials/students.tsx | 20 +++ 6 files changed, 235 insertions(+), 21 deletions(-) create mode 100644 routes/(apps)/mobility/(_islands)/SaveStudents.tsx create mode 100644 routes/(apps)/mobility/(_islands)/UploadStudents.tsx delete mode 100644 routes/(apps)/mobility/partials/(admin)/students.tsx create mode 100644 routes/(apps)/mobility/partials/students.tsx diff --git a/fresh.gen.ts b/fresh.gen.ts index 73d132d..1cfe548 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -5,9 +5,9 @@ import * as $_apps_layout from "./routes/(apps)/_layout.tsx"; import * as $_apps_mobility_index from "./routes/(apps)/mobility/index.tsx"; import * as $_apps_mobility_partials_admin_mobility from "./routes/(apps)/mobility/partials/(admin)/mobility.tsx"; -import * as $_apps_mobility_partials_admin_students from "./routes/(apps)/mobility/partials/(admin)/students.tsx"; import * as $_apps_mobility_partials_index from "./routes/(apps)/mobility/partials/index.tsx"; import * as $_apps_mobility_partials_overview from "./routes/(apps)/mobility/partials/overview.tsx"; +import * as $_apps_mobility_partials_students from "./routes/(apps)/mobility/partials/students.tsx"; import * as $_apps_notes_index from "./routes/(apps)/notes/index.tsx"; import * as $_apps_notes_partials_admin_courses from "./routes/(apps)/notes/partials/(admin)/courses.tsx"; import * as $_apps_notes_partials_admin_students from "./routes/(apps)/notes/partials/(admin)/students.tsx"; @@ -23,6 +23,8 @@ import * as $login from "./routes/login.tsx"; import * as $logout from "./routes/logout.tsx"; import * as $_islands_AppNavigator from "./routes/(_islands)/AppNavigator.tsx"; import * as $_islands_Navbar from "./routes/(_islands)/Navbar.tsx"; +import * as $_apps_mobility_islands_SaveStudents from "./routes/(apps)/mobility/(_islands)/SaveStudents.tsx"; +import * as $_apps_mobility_islands_UploadStudents from "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx"; import type { Manifest } from "$fresh/server.ts"; const manifest = { @@ -31,12 +33,12 @@ const manifest = { "./routes/(apps)/mobility/index.tsx": $_apps_mobility_index, "./routes/(apps)/mobility/partials/(admin)/mobility.tsx": $_apps_mobility_partials_admin_mobility, - "./routes/(apps)/mobility/partials/(admin)/students.tsx": - $_apps_mobility_partials_admin_students, "./routes/(apps)/mobility/partials/index.tsx": $_apps_mobility_partials_index, "./routes/(apps)/mobility/partials/overview.tsx": $_apps_mobility_partials_overview, + "./routes/(apps)/mobility/partials/students.tsx": + $_apps_mobility_partials_students, "./routes/(apps)/notes/index.tsx": $_apps_notes_index, "./routes/(apps)/notes/partials/(admin)/courses.tsx": $_apps_notes_partials_admin_courses, @@ -56,6 +58,10 @@ const manifest = { islands: { "./routes/(_islands)/AppNavigator.tsx": $_islands_AppNavigator, "./routes/(_islands)/Navbar.tsx": $_islands_Navbar, + "./routes/(apps)/mobility/(_islands)/SaveStudents.tsx": + $_apps_mobility_islands_SaveStudents, + "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx": + $_apps_mobility_islands_UploadStudents, }, baseUrl: import.meta.url, } satisfies Manifest; diff --git a/routes/(apps)/mobility/(_islands)/SaveStudents.tsx b/routes/(apps)/mobility/(_islands)/SaveStudents.tsx new file mode 100644 index 0000000..01d1cc3 --- /dev/null +++ b/routes/(apps)/mobility/(_islands)/SaveStudents.tsx @@ -0,0 +1,82 @@ +import { useSignal } from "@preact/signals"; +import Papa from "https://cdn.skypack.dev/papaparse"; + +type Student = { + firstName: string; + lastName: string; + email: string; +}; + +type Promotion = { + name: string; + students: Student[]; +}; + +export default function SaveStudents() { + const promotions = useSignal([]); + const statusMessage = useSignal(""); + + const loadCSV = async () => { + try { + const response = await fetch("/api/students"); // Assurez-vous que l'API est appelée correctement + if (!response.ok) { + throw new Error("Failed to load CSV file"); + } + const csvText = await response.text(); // Lire le contenu en texte + + const parsedData = Papa.parse(csvText, { + header: true, + skipEmptyLines: true, + }); + + const groupedPromotions: Record = {}; + parsedData.data.forEach((row: any) => { + const { promotion, firstName, lastName, email } = row; + if (!groupedPromotions[promotion]) { + groupedPromotions[promotion] = []; + } + groupedPromotions[promotion].push({ firstName, lastName, email }); + }); + + const loadedPromotions = Object.entries(groupedPromotions).map( + ([name, students]) => ({ + name, + students, + }) + ); + + promotions.value = loadedPromotions; + statusMessage.value = "Data loaded successfully!"; + } catch (error) { + console.error("Error loading CSV file:", error); + statusMessage.value = "Failed to load data. Please try again."; + } + }; + + + + // Charger les données CSV dès le chargement du composant + loadCSV(); + + return ( +
+

Loaded Promotions

+ +

{statusMessage.value}

+
    + {promotions.value.map((promotion) => ( +
  • + {promotion.name} +
      + {promotion.students.map((student, index) => ( +
    • + {student.firstName} {student.lastName} - {student.email} +
    • + ))} +
    +
  • + ))} +
+
+ ); +} diff --git a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx new file mode 100644 index 0000000..c1e717d --- /dev/null +++ b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx @@ -0,0 +1,123 @@ +// @deno-types="https://cdn.sheetjs.com/xlsx-0.20.3/package/types/index.d.ts" +import * as XLSX from "https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs"; +import { useSignal } from "@preact/signals"; +import Papa from "https://cdn.skypack.dev/papaparse"; // Bibliothèque pour manipuler CSVs + +type Student = { + firstName: string; + lastName: string; + email: string; +}; + +type Promotion = { + name: string; + students: Student[]; +}; + +export default function UploadStudents() { + const promotions = useSignal([]); + const tempPromotions = useSignal([]); + const statusMessage = useSignal(""); + + const handleFileUpload = async (event: Event) => { + const input = event.target as HTMLInputElement; + if (!input.files || input.files.length === 0) { + statusMessage.value = "No file selected"; + return; + } + + const file = input.files[0]; + const reader = new FileReader(); + + reader.onload = async (e) => { + try { + const arrayBuffer = e.target?.result as ArrayBuffer; + + // Lire le classeur Excel + const workbook = XLSX.read(arrayBuffer, { type: "array" }); + + const newPromotions: Promotion[] = []; + workbook.SheetNames.forEach((sheetName) => { + const worksheet = workbook.Sheets[sheetName]; + const students: Student[] = XLSX.utils.sheet_to_json(worksheet, { + header: ["firstName", "lastName", "email"], + range: 1, // Skip header row + }); + + newPromotions.push({ name: sheetName, students }); + }); + + tempPromotions.value = newPromotions; // Charger temporairement les promotions + statusMessage.value = "File loaded. Please confirm to save."; + } catch (error) { + statusMessage.value = "Error processing file"; + console.error(error); + } + }; + + reader.readAsArrayBuffer(file); + }; + + const confirmFileUpload = () => { + if (tempPromotions.value.length > 0) { + promotions.value = tempPromotions.value; // Mettre à jour les promotions + tempPromotions.value = []; // Réinitialiser le tampon temporaire + statusMessage.value = "Promotions updated successfully!"; + savePromotionsToCSV(promotions.value); // Enregistrer dans un fichier CSV + } else { + statusMessage.value = "No data to confirm."; + } + }; + + const savePromotionsToCSV = (data: Promotion[]) => { + const csvData = data.map((promotion) => { + return promotion.students.map((student) => ({ + promotion: promotion.name, + firstName: student.firstName, + lastName: student.lastName, + email: student.email, + })); + }); + + const flatData = csvData.flat(); + const csv = Papa.unparse(flatData); + const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); + const url = URL.createObjectURL(blob); + + // Télécharger le fichier + const link = document.createElement("a"); + link.setAttribute("href", url); + link.setAttribute("download", "students.csv"); + link.style.visibility = "hidden"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + return ( +
+

Upload Promotions

+ + +

{statusMessage.value}

+ +
+

Available Promotions

+
    + {promotions.value.map((promotion) => ( +
  • + {promotion.name} +
      + {promotion.students.map((student, index) => ( +
    • + {student.firstName} {student.lastName} - {student.email} +
    • + ))} +
    +
  • + ))} +
+
+
+ ); +} diff --git a/routes/(apps)/mobility/(_props)/props.ts b/routes/(apps)/mobility/(_props)/props.ts index a5fe572..b324ead 100644 --- a/routes/(apps)/mobility/(_props)/props.ts +++ b/routes/(apps)/mobility/(_props)/props.ts @@ -10,7 +10,7 @@ const properties: AppProperties = { mobility: "Mobility management", students: "Students management", }, - adminOnly: [], + adminOnly: ["students"], }; export default properties; diff --git a/routes/(apps)/mobility/partials/(admin)/students.tsx b/routes/(apps)/mobility/partials/(admin)/students.tsx deleted file mode 100644 index a73c859..0000000 --- a/routes/(apps)/mobility/partials/(admin)/students.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Partial } from "$fresh/runtime.ts"; -import { RouteConfig } from "$fresh/server.ts"; - -type ModulesProps = Record; - -export const config: RouteConfig = { - skipAppWrapper: true, - skipInheritedLayouts: true, -}; - -export default function Modules(_props: ModulesProps) { - return ( - - mobility - - ); -} diff --git a/routes/(apps)/mobility/partials/students.tsx b/routes/(apps)/mobility/partials/students.tsx new file mode 100644 index 0000000..9f40c20 --- /dev/null +++ b/routes/(apps)/mobility/partials/students.tsx @@ -0,0 +1,20 @@ +import { Partial } from "$fresh/runtime.ts"; +import { RouteConfig } from "$fresh/server.ts"; +import UploadStudents from "../(_islands)/UploadStudents.tsx"; +import SaveStudents from "../(_islands)/SaveStudents.tsx"; + +export const config: RouteConfig = { + skipAppWrapper: true, + skipInheritedLayouts: true, +}; + +export default function Students() { + return ( + +

Manage Promotions

+ +
+ +
+ ); +} From 3987d76d5944630fd6bfcad0ac08ba298fee8e80 Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Thu, 16 Jan 2025 14:51:53 +0100 Subject: [PATCH 03/14] Init module PolyMobility & creating pages --- fresh.gen.ts | 9 +++++++++ routes/(apps)/mobility/(_props)/props.ts | 9 +++++++-- .../mobility/partials/(admin)/mobility.tsx | 17 +++++++++++++++++ .../mobility/partials/(admin)/students.tsx | 17 +++++++++++++++++ routes/(apps)/mobility/partials/overview.tsx | 17 +++++++++++++++++ 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 routes/(apps)/mobility/partials/(admin)/mobility.tsx create mode 100644 routes/(apps)/mobility/partials/(admin)/students.tsx create mode 100644 routes/(apps)/mobility/partials/overview.tsx diff --git a/fresh.gen.ts b/fresh.gen.ts index 62756f9..73d132d 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -4,7 +4,10 @@ import * as $_apps_layout from "./routes/(apps)/_layout.tsx"; import * as $_apps_mobility_index from "./routes/(apps)/mobility/index.tsx"; +import * as $_apps_mobility_partials_admin_mobility from "./routes/(apps)/mobility/partials/(admin)/mobility.tsx"; +import * as $_apps_mobility_partials_admin_students from "./routes/(apps)/mobility/partials/(admin)/students.tsx"; import * as $_apps_mobility_partials_index from "./routes/(apps)/mobility/partials/index.tsx"; +import * as $_apps_mobility_partials_overview from "./routes/(apps)/mobility/partials/overview.tsx"; import * as $_apps_notes_index from "./routes/(apps)/notes/index.tsx"; import * as $_apps_notes_partials_admin_courses from "./routes/(apps)/notes/partials/(admin)/courses.tsx"; import * as $_apps_notes_partials_admin_students from "./routes/(apps)/notes/partials/(admin)/students.tsx"; @@ -26,8 +29,14 @@ const manifest = { routes: { "./routes/(apps)/_layout.tsx": $_apps_layout, "./routes/(apps)/mobility/index.tsx": $_apps_mobility_index, + "./routes/(apps)/mobility/partials/(admin)/mobility.tsx": + $_apps_mobility_partials_admin_mobility, + "./routes/(apps)/mobility/partials/(admin)/students.tsx": + $_apps_mobility_partials_admin_students, "./routes/(apps)/mobility/partials/index.tsx": $_apps_mobility_partials_index, + "./routes/(apps)/mobility/partials/overview.tsx": + $_apps_mobility_partials_overview, "./routes/(apps)/notes/index.tsx": $_apps_notes_index, "./routes/(apps)/notes/partials/(admin)/courses.tsx": $_apps_notes_partials_admin_courses, diff --git a/routes/(apps)/mobility/(_props)/props.ts b/routes/(apps)/mobility/(_props)/props.ts index f9c0b54..a5fe572 100644 --- a/routes/(apps)/mobility/(_props)/props.ts +++ b/routes/(apps)/mobility/(_props)/props.ts @@ -3,8 +3,13 @@ import { AppProperties } from "$root/defaults/interfaces.ts"; const properties: AppProperties = { name: "PolyMobility", icon: "flight_takeoff", - hint: "Gestionnaire de mobilité", - pages: {}, + hint: "Student mobility management", + pages: { + index: "Homepage", + overview: "Mobility overview", + mobility: "Mobility management", + students: "Students management", + }, adminOnly: [], }; diff --git a/routes/(apps)/mobility/partials/(admin)/mobility.tsx b/routes/(apps)/mobility/partials/(admin)/mobility.tsx new file mode 100644 index 0000000..a73c859 --- /dev/null +++ b/routes/(apps)/mobility/partials/(admin)/mobility.tsx @@ -0,0 +1,17 @@ +import { Partial } from "$fresh/runtime.ts"; +import { RouteConfig } from "$fresh/server.ts"; + +type ModulesProps = Record; + +export const config: RouteConfig = { + skipAppWrapper: true, + skipInheritedLayouts: true, +}; + +export default function Modules(_props: ModulesProps) { + return ( + + mobility + + ); +} diff --git a/routes/(apps)/mobility/partials/(admin)/students.tsx b/routes/(apps)/mobility/partials/(admin)/students.tsx new file mode 100644 index 0000000..a73c859 --- /dev/null +++ b/routes/(apps)/mobility/partials/(admin)/students.tsx @@ -0,0 +1,17 @@ +import { Partial } from "$fresh/runtime.ts"; +import { RouteConfig } from "$fresh/server.ts"; + +type ModulesProps = Record; + +export const config: RouteConfig = { + skipAppWrapper: true, + skipInheritedLayouts: true, +}; + +export default function Modules(_props: ModulesProps) { + return ( + + mobility + + ); +} diff --git a/routes/(apps)/mobility/partials/overview.tsx b/routes/(apps)/mobility/partials/overview.tsx new file mode 100644 index 0000000..a73c859 --- /dev/null +++ b/routes/(apps)/mobility/partials/overview.tsx @@ -0,0 +1,17 @@ +import { Partial } from "$fresh/runtime.ts"; +import { RouteConfig } from "$fresh/server.ts"; + +type ModulesProps = Record; + +export const config: RouteConfig = { + skipAppWrapper: true, + skipInheritedLayouts: true, +}; + +export default function Modules(_props: ModulesProps) { + return ( + + mobility + + ); +} From 416aad06ea44056d22d53f0522b2c5465dc82e78 Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Fri, 17 Jan 2025 10:40:49 +0100 Subject: [PATCH 04/14] Temporary student manager --- fresh.gen.ts | 12 +- .../mobility/(_islands)/SaveStudents.tsx | 82 ++++++++++++ .../mobility/(_islands)/UploadStudents.tsx | 123 ++++++++++++++++++ routes/(apps)/mobility/(_props)/props.ts | 2 +- .../mobility/partials/(admin)/students.tsx | 17 --- routes/(apps)/mobility/partials/students.tsx | 20 +++ 6 files changed, 235 insertions(+), 21 deletions(-) create mode 100644 routes/(apps)/mobility/(_islands)/SaveStudents.tsx create mode 100644 routes/(apps)/mobility/(_islands)/UploadStudents.tsx delete mode 100644 routes/(apps)/mobility/partials/(admin)/students.tsx create mode 100644 routes/(apps)/mobility/partials/students.tsx diff --git a/fresh.gen.ts b/fresh.gen.ts index 73d132d..1cfe548 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -5,9 +5,9 @@ import * as $_apps_layout from "./routes/(apps)/_layout.tsx"; import * as $_apps_mobility_index from "./routes/(apps)/mobility/index.tsx"; import * as $_apps_mobility_partials_admin_mobility from "./routes/(apps)/mobility/partials/(admin)/mobility.tsx"; -import * as $_apps_mobility_partials_admin_students from "./routes/(apps)/mobility/partials/(admin)/students.tsx"; import * as $_apps_mobility_partials_index from "./routes/(apps)/mobility/partials/index.tsx"; import * as $_apps_mobility_partials_overview from "./routes/(apps)/mobility/partials/overview.tsx"; +import * as $_apps_mobility_partials_students from "./routes/(apps)/mobility/partials/students.tsx"; import * as $_apps_notes_index from "./routes/(apps)/notes/index.tsx"; import * as $_apps_notes_partials_admin_courses from "./routes/(apps)/notes/partials/(admin)/courses.tsx"; import * as $_apps_notes_partials_admin_students from "./routes/(apps)/notes/partials/(admin)/students.tsx"; @@ -23,6 +23,8 @@ import * as $login from "./routes/login.tsx"; import * as $logout from "./routes/logout.tsx"; import * as $_islands_AppNavigator from "./routes/(_islands)/AppNavigator.tsx"; import * as $_islands_Navbar from "./routes/(_islands)/Navbar.tsx"; +import * as $_apps_mobility_islands_SaveStudents from "./routes/(apps)/mobility/(_islands)/SaveStudents.tsx"; +import * as $_apps_mobility_islands_UploadStudents from "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx"; import type { Manifest } from "$fresh/server.ts"; const manifest = { @@ -31,12 +33,12 @@ const manifest = { "./routes/(apps)/mobility/index.tsx": $_apps_mobility_index, "./routes/(apps)/mobility/partials/(admin)/mobility.tsx": $_apps_mobility_partials_admin_mobility, - "./routes/(apps)/mobility/partials/(admin)/students.tsx": - $_apps_mobility_partials_admin_students, "./routes/(apps)/mobility/partials/index.tsx": $_apps_mobility_partials_index, "./routes/(apps)/mobility/partials/overview.tsx": $_apps_mobility_partials_overview, + "./routes/(apps)/mobility/partials/students.tsx": + $_apps_mobility_partials_students, "./routes/(apps)/notes/index.tsx": $_apps_notes_index, "./routes/(apps)/notes/partials/(admin)/courses.tsx": $_apps_notes_partials_admin_courses, @@ -56,6 +58,10 @@ const manifest = { islands: { "./routes/(_islands)/AppNavigator.tsx": $_islands_AppNavigator, "./routes/(_islands)/Navbar.tsx": $_islands_Navbar, + "./routes/(apps)/mobility/(_islands)/SaveStudents.tsx": + $_apps_mobility_islands_SaveStudents, + "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx": + $_apps_mobility_islands_UploadStudents, }, baseUrl: import.meta.url, } satisfies Manifest; diff --git a/routes/(apps)/mobility/(_islands)/SaveStudents.tsx b/routes/(apps)/mobility/(_islands)/SaveStudents.tsx new file mode 100644 index 0000000..01d1cc3 --- /dev/null +++ b/routes/(apps)/mobility/(_islands)/SaveStudents.tsx @@ -0,0 +1,82 @@ +import { useSignal } from "@preact/signals"; +import Papa from "https://cdn.skypack.dev/papaparse"; + +type Student = { + firstName: string; + lastName: string; + email: string; +}; + +type Promotion = { + name: string; + students: Student[]; +}; + +export default function SaveStudents() { + const promotions = useSignal([]); + const statusMessage = useSignal(""); + + const loadCSV = async () => { + try { + const response = await fetch("/api/students"); // Assurez-vous que l'API est appelée correctement + if (!response.ok) { + throw new Error("Failed to load CSV file"); + } + const csvText = await response.text(); // Lire le contenu en texte + + const parsedData = Papa.parse(csvText, { + header: true, + skipEmptyLines: true, + }); + + const groupedPromotions: Record = {}; + parsedData.data.forEach((row: any) => { + const { promotion, firstName, lastName, email } = row; + if (!groupedPromotions[promotion]) { + groupedPromotions[promotion] = []; + } + groupedPromotions[promotion].push({ firstName, lastName, email }); + }); + + const loadedPromotions = Object.entries(groupedPromotions).map( + ([name, students]) => ({ + name, + students, + }) + ); + + promotions.value = loadedPromotions; + statusMessage.value = "Data loaded successfully!"; + } catch (error) { + console.error("Error loading CSV file:", error); + statusMessage.value = "Failed to load data. Please try again."; + } + }; + + + + // Charger les données CSV dès le chargement du composant + loadCSV(); + + return ( +
+

Loaded Promotions

+ +

{statusMessage.value}

+
    + {promotions.value.map((promotion) => ( +
  • + {promotion.name} +
      + {promotion.students.map((student, index) => ( +
    • + {student.firstName} {student.lastName} - {student.email} +
    • + ))} +
    +
  • + ))} +
+
+ ); +} diff --git a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx new file mode 100644 index 0000000..c1e717d --- /dev/null +++ b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx @@ -0,0 +1,123 @@ +// @deno-types="https://cdn.sheetjs.com/xlsx-0.20.3/package/types/index.d.ts" +import * as XLSX from "https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs"; +import { useSignal } from "@preact/signals"; +import Papa from "https://cdn.skypack.dev/papaparse"; // Bibliothèque pour manipuler CSVs + +type Student = { + firstName: string; + lastName: string; + email: string; +}; + +type Promotion = { + name: string; + students: Student[]; +}; + +export default function UploadStudents() { + const promotions = useSignal([]); + const tempPromotions = useSignal([]); + const statusMessage = useSignal(""); + + const handleFileUpload = async (event: Event) => { + const input = event.target as HTMLInputElement; + if (!input.files || input.files.length === 0) { + statusMessage.value = "No file selected"; + return; + } + + const file = input.files[0]; + const reader = new FileReader(); + + reader.onload = async (e) => { + try { + const arrayBuffer = e.target?.result as ArrayBuffer; + + // Lire le classeur Excel + const workbook = XLSX.read(arrayBuffer, { type: "array" }); + + const newPromotions: Promotion[] = []; + workbook.SheetNames.forEach((sheetName) => { + const worksheet = workbook.Sheets[sheetName]; + const students: Student[] = XLSX.utils.sheet_to_json(worksheet, { + header: ["firstName", "lastName", "email"], + range: 1, // Skip header row + }); + + newPromotions.push({ name: sheetName, students }); + }); + + tempPromotions.value = newPromotions; // Charger temporairement les promotions + statusMessage.value = "File loaded. Please confirm to save."; + } catch (error) { + statusMessage.value = "Error processing file"; + console.error(error); + } + }; + + reader.readAsArrayBuffer(file); + }; + + const confirmFileUpload = () => { + if (tempPromotions.value.length > 0) { + promotions.value = tempPromotions.value; // Mettre à jour les promotions + tempPromotions.value = []; // Réinitialiser le tampon temporaire + statusMessage.value = "Promotions updated successfully!"; + savePromotionsToCSV(promotions.value); // Enregistrer dans un fichier CSV + } else { + statusMessage.value = "No data to confirm."; + } + }; + + const savePromotionsToCSV = (data: Promotion[]) => { + const csvData = data.map((promotion) => { + return promotion.students.map((student) => ({ + promotion: promotion.name, + firstName: student.firstName, + lastName: student.lastName, + email: student.email, + })); + }); + + const flatData = csvData.flat(); + const csv = Papa.unparse(flatData); + const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); + const url = URL.createObjectURL(blob); + + // Télécharger le fichier + const link = document.createElement("a"); + link.setAttribute("href", url); + link.setAttribute("download", "students.csv"); + link.style.visibility = "hidden"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + return ( +
+

Upload Promotions

+ + +

{statusMessage.value}

+ +
+

Available Promotions

+
    + {promotions.value.map((promotion) => ( +
  • + {promotion.name} +
      + {promotion.students.map((student, index) => ( +
    • + {student.firstName} {student.lastName} - {student.email} +
    • + ))} +
    +
  • + ))} +
+
+
+ ); +} diff --git a/routes/(apps)/mobility/(_props)/props.ts b/routes/(apps)/mobility/(_props)/props.ts index a5fe572..b324ead 100644 --- a/routes/(apps)/mobility/(_props)/props.ts +++ b/routes/(apps)/mobility/(_props)/props.ts @@ -10,7 +10,7 @@ const properties: AppProperties = { mobility: "Mobility management", students: "Students management", }, - adminOnly: [], + adminOnly: ["students"], }; export default properties; diff --git a/routes/(apps)/mobility/partials/(admin)/students.tsx b/routes/(apps)/mobility/partials/(admin)/students.tsx deleted file mode 100644 index a73c859..0000000 --- a/routes/(apps)/mobility/partials/(admin)/students.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Partial } from "$fresh/runtime.ts"; -import { RouteConfig } from "$fresh/server.ts"; - -type ModulesProps = Record; - -export const config: RouteConfig = { - skipAppWrapper: true, - skipInheritedLayouts: true, -}; - -export default function Modules(_props: ModulesProps) { - return ( - - mobility - - ); -} diff --git a/routes/(apps)/mobility/partials/students.tsx b/routes/(apps)/mobility/partials/students.tsx new file mode 100644 index 0000000..9f40c20 --- /dev/null +++ b/routes/(apps)/mobility/partials/students.tsx @@ -0,0 +1,20 @@ +import { Partial } from "$fresh/runtime.ts"; +import { RouteConfig } from "$fresh/server.ts"; +import UploadStudents from "../(_islands)/UploadStudents.tsx"; +import SaveStudents from "../(_islands)/SaveStudents.tsx"; + +export const config: RouteConfig = { + skipAppWrapper: true, + skipInheritedLayouts: true, +}; + +export default function Students() { + return ( + +

Manage Promotions

+ +
+ +
+ ); +} From 6df75e0604cf83ad0b20a3a0d7248ea3be90cd17 Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Fri, 17 Jan 2025 15:37:27 +0100 Subject: [PATCH 05/14] Use the DB instead of a .csv (not working) --- databases/init/mobility.sql | 12 ++ databases/mobility.ts | 22 +++ fresh.gen.ts | 6 +- .../mobility/(_islands)/ConsultMobility.tsx | 0 .../mobility/(_islands)/ConsultStudents.tsx | 0 .../mobility/(_islands)/EditMobility.tsx | 0 .../mobility/(_islands)/EditStudents.tsx | 0 .../(apps)/mobility/(_islands)/ImportFile.tsx | 0 .../mobility/(_islands)/SaveStudents.tsx | 82 ---------- .../mobility/(_islands)/UploadStudents.tsx | 143 ++++++------------ routes/(apps)/mobility/partials/students.tsx | 14 +- 11 files changed, 91 insertions(+), 188 deletions(-) create mode 100644 databases/mobility.ts create mode 100644 routes/(apps)/mobility/(_islands)/ConsultMobility.tsx create mode 100644 routes/(apps)/mobility/(_islands)/ConsultStudents.tsx create mode 100644 routes/(apps)/mobility/(_islands)/EditMobility.tsx create mode 100644 routes/(apps)/mobility/(_islands)/EditStudents.tsx create mode 100644 routes/(apps)/mobility/(_islands)/ImportFile.tsx delete mode 100644 routes/(apps)/mobility/(_islands)/SaveStudents.tsx diff --git a/databases/init/mobility.sql b/databases/init/mobility.sql index e69de29..98ac5ae 100644 --- a/databases/init/mobility.sql +++ b/databases/init/mobility.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS students ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + firstName TEXT NOT NULL, + lastName TEXT NOT NULL, + email TEXT NOT NULL, + promotion TEXT NOT NULL +); +CREATE TABLE IF NOT EXISTS promotions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + year INTEGER NOT NULL +); diff --git a/databases/mobility.ts b/databases/mobility.ts new file mode 100644 index 0000000..13055f0 --- /dev/null +++ b/databases/mobility.ts @@ -0,0 +1,22 @@ +import { Database } from "@db/sqlite"; + +export default async function insertIntoMobility(data: Array<{ firstName: string; lastName: string; email: string }>, promoName: string) { + try { + const databasePath = "databases/data/mobility.db"; + const db = new Database(databasePath); + + db.transaction(() => { + for (const student of data) { + db.query( + "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)", + [student.firstName, student.lastName, student.email, promoName] + ); + } + })(); + + db.close(); + console.log(`Data for promotion ${promoName} inserted successfully.`); + } catch (error) { + console.error("Error inserting data into mobility database:", error); + } +} diff --git a/fresh.gen.ts b/fresh.gen.ts index 1cfe548..460b0a4 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -23,7 +23,7 @@ import * as $login from "./routes/login.tsx"; import * as $logout from "./routes/logout.tsx"; import * as $_islands_AppNavigator from "./routes/(_islands)/AppNavigator.tsx"; import * as $_islands_Navbar from "./routes/(_islands)/Navbar.tsx"; -import * as $_apps_mobility_islands_SaveStudents from "./routes/(apps)/mobility/(_islands)/SaveStudents.tsx"; +import * as $_apps_mobility_islands_ConsultStudents from "./routes/(apps)/mobility/(_islands)/ConsultStudents.tsx"; import * as $_apps_mobility_islands_UploadStudents from "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx"; import type { Manifest } from "$fresh/server.ts"; @@ -58,8 +58,8 @@ const manifest = { islands: { "./routes/(_islands)/AppNavigator.tsx": $_islands_AppNavigator, "./routes/(_islands)/Navbar.tsx": $_islands_Navbar, - "./routes/(apps)/mobility/(_islands)/SaveStudents.tsx": - $_apps_mobility_islands_SaveStudents, + "./routes/(apps)/mobility/(_islands)/ConsultStudents.tsx": + $_apps_mobility_islands_ConsultStudents, "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx": $_apps_mobility_islands_UploadStudents, }, diff --git a/routes/(apps)/mobility/(_islands)/ConsultMobility.tsx b/routes/(apps)/mobility/(_islands)/ConsultMobility.tsx new file mode 100644 index 0000000..e69de29 diff --git a/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx b/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx new file mode 100644 index 0000000..e69de29 diff --git a/routes/(apps)/mobility/(_islands)/EditMobility.tsx b/routes/(apps)/mobility/(_islands)/EditMobility.tsx new file mode 100644 index 0000000..e69de29 diff --git a/routes/(apps)/mobility/(_islands)/EditStudents.tsx b/routes/(apps)/mobility/(_islands)/EditStudents.tsx new file mode 100644 index 0000000..e69de29 diff --git a/routes/(apps)/mobility/(_islands)/ImportFile.tsx b/routes/(apps)/mobility/(_islands)/ImportFile.tsx new file mode 100644 index 0000000..e69de29 diff --git a/routes/(apps)/mobility/(_islands)/SaveStudents.tsx b/routes/(apps)/mobility/(_islands)/SaveStudents.tsx deleted file mode 100644 index 01d1cc3..0000000 --- a/routes/(apps)/mobility/(_islands)/SaveStudents.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useSignal } from "@preact/signals"; -import Papa from "https://cdn.skypack.dev/papaparse"; - -type Student = { - firstName: string; - lastName: string; - email: string; -}; - -type Promotion = { - name: string; - students: Student[]; -}; - -export default function SaveStudents() { - const promotions = useSignal([]); - const statusMessage = useSignal(""); - - const loadCSV = async () => { - try { - const response = await fetch("/api/students"); // Assurez-vous que l'API est appelée correctement - if (!response.ok) { - throw new Error("Failed to load CSV file"); - } - const csvText = await response.text(); // Lire le contenu en texte - - const parsedData = Papa.parse(csvText, { - header: true, - skipEmptyLines: true, - }); - - const groupedPromotions: Record = {}; - parsedData.data.forEach((row: any) => { - const { promotion, firstName, lastName, email } = row; - if (!groupedPromotions[promotion]) { - groupedPromotions[promotion] = []; - } - groupedPromotions[promotion].push({ firstName, lastName, email }); - }); - - const loadedPromotions = Object.entries(groupedPromotions).map( - ([name, students]) => ({ - name, - students, - }) - ); - - promotions.value = loadedPromotions; - statusMessage.value = "Data loaded successfully!"; - } catch (error) { - console.error("Error loading CSV file:", error); - statusMessage.value = "Failed to load data. Please try again."; - } - }; - - - - // Charger les données CSV dès le chargement du composant - loadCSV(); - - return ( -
-

Loaded Promotions

- -

{statusMessage.value}

-
    - {promotions.value.map((promotion) => ( -
  • - {promotion.name} -
      - {promotion.students.map((student, index) => ( -
    • - {student.firstName} {student.lastName} - {student.email} -
    • - ))} -
    -
  • - ))} -
-
- ); -} diff --git a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx index c1e717d..ea87ce1 100644 --- a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx +++ b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx @@ -1,123 +1,74 @@ // @deno-types="https://cdn.sheetjs.com/xlsx-0.20.3/package/types/index.d.ts" import * as XLSX from "https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs"; import { useSignal } from "@preact/signals"; -import Papa from "https://cdn.skypack.dev/papaparse"; // Bibliothèque pour manipuler CSVs - -type Student = { - firstName: string; - lastName: string; - email: string; -}; - -type Promotion = { - name: string; - students: Student[]; -}; +import insertIntoMobility from "../../../../databases/mobility.ts"; export default function UploadStudents() { - const promotions = useSignal([]); - const tempPromotions = useSignal([]); const statusMessage = useSignal(""); + const fileData = useSignal(null); - const handleFileUpload = async (event: Event) => { + const handleFileChange = (event: Event) => { const input = event.target as HTMLInputElement; - if (!input.files || input.files.length === 0) { + if (input.files && input.files.length > 0) { + fileData.value = input.files[0]; + statusMessage.value = "File selected: " + input.files[0].name; + } else { + fileData.value = null; statusMessage.value = "No file selected"; + } + }; + + const handleUpload = async () => { + if (!fileData.value) { + statusMessage.value = "Please select a file before confirming upload."; return; } - const file = input.files[0]; - const reader = new FileReader(); + try { + const reader = new FileReader(); + reader.onload = async (e) => { + try { + const arrayBuffer = e.target?.result as ArrayBuffer; + const workbook = XLSX.read(arrayBuffer, { type: "array" }); - reader.onload = async (e) => { - try { - const arrayBuffer = e.target?.result as ArrayBuffer; + for (const sheetName of workbook.SheetNames) { + const sheet = workbook.Sheets[sheetName]; + const data = XLSX.utils.sheet_to_json(sheet, { + header: ["firstName", "lastName", "email"], + range: 1, // Ignorer les en-têtes + }); - // Lire le classeur Excel - const workbook = XLSX.read(arrayBuffer, { type: "array" }); + console.log(`Data from sheet ${sheetName}:`, data); - const newPromotions: Promotion[] = []; - workbook.SheetNames.forEach((sheetName) => { - const worksheet = workbook.Sheets[sheetName]; - const students: Student[] = XLSX.utils.sheet_to_json(worksheet, { - header: ["firstName", "lastName", "email"], - range: 1, // Skip header row - }); + // Insérer les données dans la base de données + await insertIntoMobility(data as Array<{ firstName: string; lastName: string; email: string }>, sheetName); + } - newPromotions.push({ name: sheetName, students }); - }); + statusMessage.value = "File uploaded and data inserted successfully!"; + } catch (error) { + console.error("Error reading or inserting file:", error); + statusMessage.value = "Error processing the file. Please check its format."; + } + }; - tempPromotions.value = newPromotions; // Charger temporairement les promotions - statusMessage.value = "File loaded. Please confirm to save."; - } catch (error) { - statusMessage.value = "Error processing file"; - console.error(error); - } - }; + reader.onerror = (e) => { + console.error("FileReader error:", e); + statusMessage.value = "Error reading the file."; + }; - reader.readAsArrayBuffer(file); - }; - - const confirmFileUpload = () => { - if (tempPromotions.value.length > 0) { - promotions.value = tempPromotions.value; // Mettre à jour les promotions - tempPromotions.value = []; // Réinitialiser le tampon temporaire - statusMessage.value = "Promotions updated successfully!"; - savePromotionsToCSV(promotions.value); // Enregistrer dans un fichier CSV - } else { - statusMessage.value = "No data to confirm."; + reader.readAsArrayBuffer(fileData.value); + } catch (error) { + console.error("Error uploading file:", error); + statusMessage.value = "An unexpected error occurred during upload."; } }; - const savePromotionsToCSV = (data: Promotion[]) => { - const csvData = data.map((promotion) => { - return promotion.students.map((student) => ({ - promotion: promotion.name, - firstName: student.firstName, - lastName: student.lastName, - email: student.email, - })); - }); - - const flatData = csvData.flat(); - const csv = Papa.unparse(flatData); - const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); - const url = URL.createObjectURL(blob); - - // Télécharger le fichier - const link = document.createElement("a"); - link.setAttribute("href", url); - link.setAttribute("download", "students.csv"); - link.style.visibility = "hidden"; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - }; - return (
-

Upload Promotions

- - +

Upload Students

+ +

{statusMessage.value}

- -
-

Available Promotions

-
    - {promotions.value.map((promotion) => ( -
  • - {promotion.name} -
      - {promotion.students.map((student, index) => ( -
    • - {student.firstName} {student.lastName} - {student.email} -
    • - ))} -
    -
  • - ))} -
-
); } diff --git a/routes/(apps)/mobility/partials/students.tsx b/routes/(apps)/mobility/partials/students.tsx index 9f40c20..bef23ec 100644 --- a/routes/(apps)/mobility/partials/students.tsx +++ b/routes/(apps)/mobility/partials/students.tsx @@ -1,20 +1,20 @@ -import { Partial } from "$fresh/runtime.ts"; import { RouteConfig } from "$fresh/server.ts"; import UploadStudents from "../(_islands)/UploadStudents.tsx"; -import SaveStudents from "../(_islands)/SaveStudents.tsx"; +//import ConsultStudents from "../(_islands)/ConsultStudents.tsx"; +//import EditStudents from "../(_islands)/EditStudents.tsx"; export const config: RouteConfig = { - skipAppWrapper: true, - skipInheritedLayouts: true, + skipAppWrapper: false, + skipInheritedLayouts: false, }; export default function Students() { return ( - +

Manage Promotions


- - + +
); } From 3036f1a1bb7b1e955a6969bbbf90fd5908f7e3ce Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Sat, 18 Jan 2025 23:10:28 +0100 Subject: [PATCH 06/14] pushed changes --- fresh.gen.ts | 12 ++++++++++++ routes/(apps)/mobility/(_islands)/UploadStudents.tsx | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/fresh.gen.ts b/fresh.gen.ts index 460b0a4..f04adda 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -23,7 +23,11 @@ import * as $login from "./routes/login.tsx"; import * as $logout from "./routes/logout.tsx"; import * as $_islands_AppNavigator from "./routes/(_islands)/AppNavigator.tsx"; import * as $_islands_Navbar from "./routes/(_islands)/Navbar.tsx"; +import * as $_apps_mobility_islands_ConsultMobility from "./routes/(apps)/mobility/(_islands)/ConsultMobility.tsx"; import * as $_apps_mobility_islands_ConsultStudents from "./routes/(apps)/mobility/(_islands)/ConsultStudents.tsx"; +import * as $_apps_mobility_islands_EditMobility from "./routes/(apps)/mobility/(_islands)/EditMobility.tsx"; +import * as $_apps_mobility_islands_EditStudents from "./routes/(apps)/mobility/(_islands)/EditStudents.tsx"; +import * as $_apps_mobility_islands_ImportFile from "./routes/(apps)/mobility/(_islands)/ImportFile.tsx"; import * as $_apps_mobility_islands_UploadStudents from "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx"; import type { Manifest } from "$fresh/server.ts"; @@ -58,8 +62,16 @@ const manifest = { islands: { "./routes/(_islands)/AppNavigator.tsx": $_islands_AppNavigator, "./routes/(_islands)/Navbar.tsx": $_islands_Navbar, + "./routes/(apps)/mobility/(_islands)/ConsultMobility.tsx": + $_apps_mobility_islands_ConsultMobility, "./routes/(apps)/mobility/(_islands)/ConsultStudents.tsx": $_apps_mobility_islands_ConsultStudents, + "./routes/(apps)/mobility/(_islands)/EditMobility.tsx": + $_apps_mobility_islands_EditMobility, + "./routes/(apps)/mobility/(_islands)/EditStudents.tsx": + $_apps_mobility_islands_EditStudents, + "./routes/(apps)/mobility/(_islands)/ImportFile.tsx": + $_apps_mobility_islands_ImportFile, "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx": $_apps_mobility_islands_UploadStudents, }, diff --git a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx index ea87ce1..8d1edde 100644 --- a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx +++ b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx @@ -39,8 +39,6 @@ export default function UploadStudents() { }); console.log(`Data from sheet ${sheetName}:`, data); - - // Insérer les données dans la base de données await insertIntoMobility(data as Array<{ firstName: string; lastName: string; email: string }>, sheetName); } From 4057bb488c226512bd51b0f4513f786623a01228 Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Sat, 18 Jan 2025 23:38:29 +0100 Subject: [PATCH 07/14] Trying to use the DB (not working) --- databases/init/mobility.sql | 12 --- databases/mobility.ts | 35 +++++--- .../mobility/(_islands)/SaveStudents.tsx | 82 ------------------- .../mobility/(_islands)/UploadStudents.tsx | 12 ++- routes/(apps)/mobility/partials/students.tsx | 7 +- 5 files changed, 33 insertions(+), 115 deletions(-) delete mode 100644 routes/(apps)/mobility/(_islands)/SaveStudents.tsx diff --git a/databases/init/mobility.sql b/databases/init/mobility.sql index 98ac5ae..e69de29 100644 --- a/databases/init/mobility.sql +++ b/databases/init/mobility.sql @@ -1,12 +0,0 @@ -CREATE TABLE IF NOT EXISTS students ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - firstName TEXT NOT NULL, - lastName TEXT NOT NULL, - email TEXT NOT NULL, - promotion TEXT NOT NULL -); -CREATE TABLE IF NOT EXISTS promotions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - year INTEGER NOT NULL -); diff --git a/databases/mobility.ts b/databases/mobility.ts index 13055f0..9bb1400 100644 --- a/databases/mobility.ts +++ b/databases/mobility.ts @@ -1,21 +1,30 @@ -import { Database } from "@db/sqlite"; +import { DB } from "https://deno.land/x/sqlite/mod.ts"; -export default async function insertIntoMobility(data: Array<{ firstName: string; lastName: string; email: string }>, promoName: string) { +export default function insertIntoMobility( + data: Array<{ firstName: string; lastName: string; email: string }>, + promoName: string +) { try { - const databasePath = "databases/data/mobility.db"; - const db = new Database(databasePath); + const db = new DB("databases/data/mobility.db"); - db.transaction(() => { - for (const student of data) { - db.query( - "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)", - [student.firstName, student.lastName, student.email, promoName] - ); - } - })(); + db.execute(` + CREATE TABLE IF NOT EXISTS students ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + firstName TEXT NOT NULL, + lastName TEXT NOT NULL, + email TEXT NOT NULL, + promotion TEXT NOT NULL + ); + `); + + const insertQuery = "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)"; + for (const student of data) { + db.query(insertQuery, [student.firstName, student.lastName, student.email, promoName]); + } + + console.log(`Data for promotion ${promoName} inserted successfully.`); db.close(); - console.log(`Data for promotion ${promoName} inserted successfully.`); } catch (error) { console.error("Error inserting data into mobility database:", error); } diff --git a/routes/(apps)/mobility/(_islands)/SaveStudents.tsx b/routes/(apps)/mobility/(_islands)/SaveStudents.tsx deleted file mode 100644 index 01d1cc3..0000000 --- a/routes/(apps)/mobility/(_islands)/SaveStudents.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useSignal } from "@preact/signals"; -import Papa from "https://cdn.skypack.dev/papaparse"; - -type Student = { - firstName: string; - lastName: string; - email: string; -}; - -type Promotion = { - name: string; - students: Student[]; -}; - -export default function SaveStudents() { - const promotions = useSignal([]); - const statusMessage = useSignal(""); - - const loadCSV = async () => { - try { - const response = await fetch("/api/students"); // Assurez-vous que l'API est appelée correctement - if (!response.ok) { - throw new Error("Failed to load CSV file"); - } - const csvText = await response.text(); // Lire le contenu en texte - - const parsedData = Papa.parse(csvText, { - header: true, - skipEmptyLines: true, - }); - - const groupedPromotions: Record = {}; - parsedData.data.forEach((row: any) => { - const { promotion, firstName, lastName, email } = row; - if (!groupedPromotions[promotion]) { - groupedPromotions[promotion] = []; - } - groupedPromotions[promotion].push({ firstName, lastName, email }); - }); - - const loadedPromotions = Object.entries(groupedPromotions).map( - ([name, students]) => ({ - name, - students, - }) - ); - - promotions.value = loadedPromotions; - statusMessage.value = "Data loaded successfully!"; - } catch (error) { - console.error("Error loading CSV file:", error); - statusMessage.value = "Failed to load data. Please try again."; - } - }; - - - - // Charger les données CSV dès le chargement du composant - loadCSV(); - - return ( -
-

Loaded Promotions

- -

{statusMessage.value}

-
    - {promotions.value.map((promotion) => ( -
  • - {promotion.name} -
      - {promotion.students.map((student, index) => ( -
    • - {student.firstName} {student.lastName} - {student.email} -
    • - ))} -
    -
  • - ))} -
-
- ); -} diff --git a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx index 8d1edde..8f562f0 100644 --- a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx +++ b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx @@ -18,7 +18,7 @@ export default function UploadStudents() { } }; - const handleUpload = async () => { + const handleUpload = () => { if (!fileData.value) { statusMessage.value = "Please select a file before confirming upload."; return; @@ -39,13 +39,19 @@ export default function UploadStudents() { }); console.log(`Data from sheet ${sheetName}:`, data); - await insertIntoMobility(data as Array<{ firstName: string; lastName: string; email: string }>, sheetName); + await insertIntoMobility( + data as Array< + { firstName: string; lastName: string; email: string } + >, + sheetName, + ); } statusMessage.value = "File uploaded and data inserted successfully!"; } catch (error) { console.error("Error reading or inserting file:", error); - statusMessage.value = "Error processing the file. Please check its format."; + statusMessage.value = + "Error processing the file. Please check its format."; } }; diff --git a/routes/(apps)/mobility/partials/students.tsx b/routes/(apps)/mobility/partials/students.tsx index 964b91c..5878f81 100644 --- a/routes/(apps)/mobility/partials/students.tsx +++ b/routes/(apps)/mobility/partials/students.tsx @@ -1,13 +1,11 @@ - import { RouteConfig } from "$fresh/server.ts"; import UploadStudents from "../(_islands)/UploadStudents.tsx"; //import ConsultStudents from "../(_islands)/ConsultStudents.tsx"; //import EditStudents from "../(_islands)/EditStudents.tsx"; export const config: RouteConfig = { - skipAppWrapper: false, - skipInheritedLayouts: false, - + skipAppWrapper: false, + skipInheritedLayouts: false, }; export default function Students() { @@ -16,7 +14,6 @@ export default function Students() {

Manage Promotions


- ); } From a49683c10e3b1f756000ccef80d6685be8e6fa3e Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Mon, 20 Jan 2025 16:33:06 +0100 Subject: [PATCH 08/14] Use api for DB --- databases/mobility.ts | 31 -------- fresh.gen.ts | 21 +---- .../ConsultMobility.tsx | 0 .../ConsultStudents.tsx | 0 .../EditMobility.tsx | 0 .../EditStudents.tsx | 0 .../ImportFile.tsx | 0 .../mobility/(_components)/UploadStudents.tsx | 33 ++++++++ .../mobility/(_islands)/UploadStudents.tsx | 78 ------------------- routes/(apps)/mobility/api/insert_students.ts | 69 ++++++++++++++++ routes/(apps)/mobility/partials/students.tsx | 2 +- 11 files changed, 106 insertions(+), 128 deletions(-) delete mode 100644 databases/mobility.ts rename routes/(apps)/mobility/{(_islands) => (_components)}/ConsultMobility.tsx (100%) rename routes/(apps)/mobility/{(_islands) => (_components)}/ConsultStudents.tsx (100%) rename routes/(apps)/mobility/{(_islands) => (_components)}/EditMobility.tsx (100%) rename routes/(apps)/mobility/{(_islands) => (_components)}/EditStudents.tsx (100%) rename routes/(apps)/mobility/{(_islands) => (_components)}/ImportFile.tsx (100%) create mode 100644 routes/(apps)/mobility/(_components)/UploadStudents.tsx delete mode 100644 routes/(apps)/mobility/(_islands)/UploadStudents.tsx create mode 100644 routes/(apps)/mobility/api/insert_students.ts diff --git a/databases/mobility.ts b/databases/mobility.ts deleted file mode 100644 index 9bb1400..0000000 --- a/databases/mobility.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { DB } from "https://deno.land/x/sqlite/mod.ts"; - -export default function insertIntoMobility( - data: Array<{ firstName: string; lastName: string; email: string }>, - promoName: string -) { - try { - const db = new DB("databases/data/mobility.db"); - - db.execute(` - CREATE TABLE IF NOT EXISTS students ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - firstName TEXT NOT NULL, - lastName TEXT NOT NULL, - email TEXT NOT NULL, - promotion TEXT NOT NULL - ); - `); - - const insertQuery = "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)"; - for (const student of data) { - db.query(insertQuery, [student.firstName, student.lastName, student.email, promoName]); - } - - console.log(`Data for promotion ${promoName} inserted successfully.`); - - db.close(); - } catch (error) { - console.error("Error inserting data into mobility database:", error); - } -} diff --git a/fresh.gen.ts b/fresh.gen.ts index f04adda..0c4ae9b 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -3,6 +3,7 @@ // This file is automatically updated during development when running `dev.ts`. import * as $_apps_layout from "./routes/(apps)/_layout.tsx"; +import * as $_apps_mobility_api_insert_students from "./routes/(apps)/mobility/api/insert_students.ts"; import * as $_apps_mobility_index from "./routes/(apps)/mobility/index.tsx"; import * as $_apps_mobility_partials_admin_mobility from "./routes/(apps)/mobility/partials/(admin)/mobility.tsx"; import * as $_apps_mobility_partials_index from "./routes/(apps)/mobility/partials/index.tsx"; @@ -23,17 +24,13 @@ import * as $login from "./routes/login.tsx"; import * as $logout from "./routes/logout.tsx"; import * as $_islands_AppNavigator from "./routes/(_islands)/AppNavigator.tsx"; import * as $_islands_Navbar from "./routes/(_islands)/Navbar.tsx"; -import * as $_apps_mobility_islands_ConsultMobility from "./routes/(apps)/mobility/(_islands)/ConsultMobility.tsx"; -import * as $_apps_mobility_islands_ConsultStudents from "./routes/(apps)/mobility/(_islands)/ConsultStudents.tsx"; -import * as $_apps_mobility_islands_EditMobility from "./routes/(apps)/mobility/(_islands)/EditMobility.tsx"; -import * as $_apps_mobility_islands_EditStudents from "./routes/(apps)/mobility/(_islands)/EditStudents.tsx"; -import * as $_apps_mobility_islands_ImportFile from "./routes/(apps)/mobility/(_islands)/ImportFile.tsx"; -import * as $_apps_mobility_islands_UploadStudents from "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx"; import type { Manifest } from "$fresh/server.ts"; const manifest = { routes: { "./routes/(apps)/_layout.tsx": $_apps_layout, + "./routes/(apps)/mobility/api/insert_students.ts": + $_apps_mobility_api_insert_students, "./routes/(apps)/mobility/index.tsx": $_apps_mobility_index, "./routes/(apps)/mobility/partials/(admin)/mobility.tsx": $_apps_mobility_partials_admin_mobility, @@ -62,18 +59,6 @@ const manifest = { islands: { "./routes/(_islands)/AppNavigator.tsx": $_islands_AppNavigator, "./routes/(_islands)/Navbar.tsx": $_islands_Navbar, - "./routes/(apps)/mobility/(_islands)/ConsultMobility.tsx": - $_apps_mobility_islands_ConsultMobility, - "./routes/(apps)/mobility/(_islands)/ConsultStudents.tsx": - $_apps_mobility_islands_ConsultStudents, - "./routes/(apps)/mobility/(_islands)/EditMobility.tsx": - $_apps_mobility_islands_EditMobility, - "./routes/(apps)/mobility/(_islands)/EditStudents.tsx": - $_apps_mobility_islands_EditStudents, - "./routes/(apps)/mobility/(_islands)/ImportFile.tsx": - $_apps_mobility_islands_ImportFile, - "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx": - $_apps_mobility_islands_UploadStudents, }, baseUrl: import.meta.url, } satisfies Manifest; diff --git a/routes/(apps)/mobility/(_islands)/ConsultMobility.tsx b/routes/(apps)/mobility/(_components)/ConsultMobility.tsx similarity index 100% rename from routes/(apps)/mobility/(_islands)/ConsultMobility.tsx rename to routes/(apps)/mobility/(_components)/ConsultMobility.tsx diff --git a/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx b/routes/(apps)/mobility/(_components)/ConsultStudents.tsx similarity index 100% rename from routes/(apps)/mobility/(_islands)/ConsultStudents.tsx rename to routes/(apps)/mobility/(_components)/ConsultStudents.tsx diff --git a/routes/(apps)/mobility/(_islands)/EditMobility.tsx b/routes/(apps)/mobility/(_components)/EditMobility.tsx similarity index 100% rename from routes/(apps)/mobility/(_islands)/EditMobility.tsx rename to routes/(apps)/mobility/(_components)/EditMobility.tsx diff --git a/routes/(apps)/mobility/(_islands)/EditStudents.tsx b/routes/(apps)/mobility/(_components)/EditStudents.tsx similarity index 100% rename from routes/(apps)/mobility/(_islands)/EditStudents.tsx rename to routes/(apps)/mobility/(_components)/EditStudents.tsx diff --git a/routes/(apps)/mobility/(_islands)/ImportFile.tsx b/routes/(apps)/mobility/(_components)/ImportFile.tsx similarity index 100% rename from routes/(apps)/mobility/(_islands)/ImportFile.tsx rename to routes/(apps)/mobility/(_components)/ImportFile.tsx diff --git a/routes/(apps)/mobility/(_components)/UploadStudents.tsx b/routes/(apps)/mobility/(_components)/UploadStudents.tsx new file mode 100644 index 0000000..9044f57 --- /dev/null +++ b/routes/(apps)/mobility/(_components)/UploadStudents.tsx @@ -0,0 +1,33 @@ +// @deno-types="https://cdn.sheetjs.com/xlsx-0.20.3/package/types/index.d.ts" +import * as XLSX from "https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs"; +import { useSignal } from "@preact/signals"; +import handleUpload from "../api/insert_students.ts"; + +export default function UploadStudents() { + const statusMessage = useSignal(""); + const fileData = useSignal(null); + + const handleFileChange = (event: Event) => { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + fileData.value = input.files[0]; + statusMessage.value = "File selected: " + input.files[0].name; + } else { + fileData.value = null; + statusMessage.value = "No file selected"; + } + }; + + const confirmUpload = () => { + statusMessage.value = handleUpload(fileData.value); + }; + + return ( +
+

Upload Students

+ + +

{statusMessage.value}

+
+ ); +} diff --git a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx deleted file mode 100644 index 8f562f0..0000000 --- a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx +++ /dev/null @@ -1,78 +0,0 @@ -// @deno-types="https://cdn.sheetjs.com/xlsx-0.20.3/package/types/index.d.ts" -import * as XLSX from "https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs"; -import { useSignal } from "@preact/signals"; -import insertIntoMobility from "../../../../databases/mobility.ts"; - -export default function UploadStudents() { - const statusMessage = useSignal(""); - const fileData = useSignal(null); - - const handleFileChange = (event: Event) => { - const input = event.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - fileData.value = input.files[0]; - statusMessage.value = "File selected: " + input.files[0].name; - } else { - fileData.value = null; - statusMessage.value = "No file selected"; - } - }; - - const handleUpload = () => { - if (!fileData.value) { - statusMessage.value = "Please select a file before confirming upload."; - return; - } - - try { - const reader = new FileReader(); - reader.onload = async (e) => { - try { - const arrayBuffer = e.target?.result as ArrayBuffer; - const workbook = XLSX.read(arrayBuffer, { type: "array" }); - - for (const sheetName of workbook.SheetNames) { - const sheet = workbook.Sheets[sheetName]; - const data = XLSX.utils.sheet_to_json(sheet, { - header: ["firstName", "lastName", "email"], - range: 1, // Ignorer les en-têtes - }); - - console.log(`Data from sheet ${sheetName}:`, data); - await insertIntoMobility( - data as Array< - { firstName: string; lastName: string; email: string } - >, - sheetName, - ); - } - - statusMessage.value = "File uploaded and data inserted successfully!"; - } catch (error) { - console.error("Error reading or inserting file:", error); - statusMessage.value = - "Error processing the file. Please check its format."; - } - }; - - reader.onerror = (e) => { - console.error("FileReader error:", e); - statusMessage.value = "Error reading the file."; - }; - - reader.readAsArrayBuffer(fileData.value); - } catch (error) { - console.error("Error uploading file:", error); - statusMessage.value = "An unexpected error occurred during upload."; - } - }; - - return ( -
-

Upload Students

- - -

{statusMessage.value}

-
- ); -} diff --git a/routes/(apps)/mobility/api/insert_students.ts b/routes/(apps)/mobility/api/insert_students.ts new file mode 100644 index 0000000..b3ef625 --- /dev/null +++ b/routes/(apps)/mobility/api/insert_students.ts @@ -0,0 +1,69 @@ +// @deno-types="https://cdn.sheetjs.com/xlsx-0.20.3/package/types/index.d.ts" +import * as XLSX from "https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs"; +import { Database } from "@db/sqlite"; + +export default function handleUpload(file: File | null): string { + if (!file) { + return "Please select a file before confirming upload."; + } + + try { + const reader = new FileReader(); + let statusMessage = ""; + + reader.onload = async (e) => { + try { + const arrayBuffer = e.target?.result as ArrayBuffer; + const workbook = XLSX.read(arrayBuffer, { type: "array" }); + + const db = new Database("databases/data/mobility.db"); + + db.execute(` + CREATE TABLE IF NOT EXISTS students ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + firstName TEXT NOT NULL, + lastName TEXT NOT NULL, + email TEXT NOT NULL, + promotion TEXT NOT NULL + ); + `); + + for (const sheetName of workbook.SheetNames) { + const sheet = workbook.Sheets[sheetName]; + const data = XLSX.utils.sheet_to_json(sheet, { + header: ["firstName", "lastName", "email"], + range: 1, // Ignorer les en-têtes + }); + + const insertQuery = + "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)"; + for (const student of data) { + db.query(insertQuery, [ + student.firstName, + student.lastName, + student.email, + sheetName, + ]); + } + } + + db.close(); + statusMessage = "Data uploaded and inserted successfully!"; + } catch (error) { + console.error("Error reading or inserting file:", error); + statusMessage = "Error processing the file. Please check its format."; + } + }; + + reader.onerror = (e) => { + console.error("FileReader error:", e); + statusMessage = "Error reading the file."; + }; + + reader.readAsArrayBuffer(file); + return statusMessage; + } catch (error) { + console.error("Error uploading file:", error); + return "An unexpected error occurred during upload."; + } +} diff --git a/routes/(apps)/mobility/partials/students.tsx b/routes/(apps)/mobility/partials/students.tsx index 5878f81..89c55cf 100644 --- a/routes/(apps)/mobility/partials/students.tsx +++ b/routes/(apps)/mobility/partials/students.tsx @@ -1,5 +1,5 @@ import { RouteConfig } from "$fresh/server.ts"; -import UploadStudents from "../(_islands)/UploadStudents.tsx"; +import UploadStudents from "../(_components)/UploadStudents.tsx"; //import ConsultStudents from "../(_islands)/ConsultStudents.tsx"; //import EditStudents from "../(_islands)/EditStudents.tsx"; From 07148f16b2ca4958a31d26d4e99118e49a557498 Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Mon, 20 Jan 2025 17:33:07 +0100 Subject: [PATCH 09/14] pushed changes --- .../mobility/(_components)/UploadStudents.tsx | 52 ++++++- routes/(apps)/mobility/api/insert_students.ts | 135 ++++++++++-------- routes/(apps)/mobility/partials/students.tsx | 4 +- 3 files changed, 124 insertions(+), 67 deletions(-) diff --git a/routes/(apps)/mobility/(_components)/UploadStudents.tsx b/routes/(apps)/mobility/(_components)/UploadStudents.tsx index 9044f57..d8db47a 100644 --- a/routes/(apps)/mobility/(_components)/UploadStudents.tsx +++ b/routes/(apps)/mobility/(_components)/UploadStudents.tsx @@ -1,7 +1,6 @@ // @deno-types="https://cdn.sheetjs.com/xlsx-0.20.3/package/types/index.d.ts" import * as XLSX from "https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs"; import { useSignal } from "@preact/signals"; -import handleUpload from "../api/insert_students.ts"; export default function UploadStudents() { const statusMessage = useSignal(""); @@ -18,8 +17,55 @@ export default function UploadStudents() { } }; - const confirmUpload = () => { - statusMessage.value = handleUpload(fileData.value); + const confirmUpload = async () => { + if (!fileData.value) { + statusMessage.value = "Please select a file before confirming upload."; + return; + } + + try { + const reader = new FileReader(); + + reader.onload = async (e) => { + try { + const arrayBuffer = e.target?.result as ArrayBuffer; + const workbook = XLSX.read(arrayBuffer, { type: "array" }); + + for (const sheetName of workbook.SheetNames) { + const sheet = workbook.Sheets[sheetName]; + const data = XLSX.utils.sheet_to_json(sheet, { + header: ["Nom", "Prénom", "Mail"], + range: 1, // Ignorer les en-têtes + }); + + const response = await fetch("/api/insert_students", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ promoName: sheetName, data }), + }); + + if (!response.ok) { + throw new Error(`Failed to insert data for promotion ${sheetName}`); + } + } + + statusMessage.value = "Data uploaded and inserted successfully!"; + } catch (error) { + console.error("Error processing the file:", error); + statusMessage.value = "Error processing the file. Please check its format."; + } + }; + + reader.onerror = (e) => { + console.error("FileReader error:", e); + statusMessage.value = "Error reading the file."; + }; + + reader.readAsArrayBuffer(fileData.value); + } catch (error) { + console.error("Error uploading file:", error); + statusMessage.value = "An unexpected error occurred during upload."; + } }; return ( diff --git a/routes/(apps)/mobility/api/insert_students.ts b/routes/(apps)/mobility/api/insert_students.ts index b3ef625..790dce0 100644 --- a/routes/(apps)/mobility/api/insert_students.ts +++ b/routes/(apps)/mobility/api/insert_students.ts @@ -1,69 +1,80 @@ -// @deno-types="https://cdn.sheetjs.com/xlsx-0.20.3/package/types/index.d.ts" -import * as XLSX from "https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs"; +import { Handlers } from "$fresh/server.ts"; import { Database } from "@db/sqlite"; -export default function handleUpload(file: File | null): string { - if (!file) { - return "Please select a file before confirming upload."; - } +export const handler: Handlers = { + async GET(_request, context) { + try { + // Ouvre ou crée la base de données SQLite + const db = new Database("databases/data/mobility.db"); - try { - const reader = new FileReader(); - let statusMessage = ""; + // Crée la table si elle n'existe pas + db.execute(` + CREATE TABLE IF NOT EXISTS students ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + firstName TEXT NOT NULL, + lastName TEXT NOT NULL, + email TEXT NOT NULL, + promotion TEXT NOT NULL + ); + `); - reader.onload = async (e) => { - try { - const arrayBuffer = e.target?.result as ArrayBuffer; - const workbook = XLSX.read(arrayBuffer, { type: "array" }); - - const db = new Database("databases/data/mobility.db"); - - db.execute(` - CREATE TABLE IF NOT EXISTS students ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - firstName TEXT NOT NULL, - lastName TEXT NOT NULL, - email TEXT NOT NULL, - promotion TEXT NOT NULL - ); - `); - - for (const sheetName of workbook.SheetNames) { - const sheet = workbook.Sheets[sheetName]; - const data = XLSX.utils.sheet_to_json(sheet, { - header: ["firstName", "lastName", "email"], - range: 1, // Ignorer les en-têtes - }); - - const insertQuery = - "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)"; - for (const student of data) { - db.query(insertQuery, [ - student.firstName, - student.lastName, - student.email, - sheetName, - ]); - } - } - - db.close(); - statusMessage = "Data uploaded and inserted successfully!"; - } catch (error) { - console.error("Error reading or inserting file:", error); - statusMessage = "Error processing the file. Please check its format."; + // Récupère toutes les données + const students = []; + for (const [id, firstName, lastName, email, promotion] of db.query( + "SELECT id, firstName, lastName, email, promotion FROM students" + )) { + students.push({ id, firstName, lastName, email, promotion }); } - }; - reader.onerror = (e) => { - console.error("FileReader error:", e); - statusMessage = "Error reading the file."; - }; + db.close(); - reader.readAsArrayBuffer(file); - return statusMessage; - } catch (error) { - console.error("Error uploading file:", error); - return "An unexpected error occurred during upload."; - } -} + return new Response(JSON.stringify(students), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } catch (error) { + console.error("Error fetching students:", error); + return new Response("Failed to fetch students", { status: 500 }); + } + }, + + async POST(request) { + try { + const body = await request.json(); + const { data, promoName } = body; + + // Ouvre ou crée la base de données SQLite + const db = new Database("databases/data/mobility.db"); + + // Crée la table si elle n'existe pas + db.execute(` + CREATE TABLE IF NOT EXISTS students ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + firstName TEXT NOT NULL, + lastName TEXT NOT NULL, + email TEXT NOT NULL, + promotion TEXT NOT NULL + ); + `); + + // Prépare et insère les données + const insertQuery = + "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)"; + for (const student of data) { + db.query(insertQuery, [ + student.firstName, + student.lastName, + student.email, + promoName, + ]); + } + + db.close(); + + return new Response("Students inserted successfully", { status: 201 }); + } catch (error) { + console.error("Error inserting students:", error); + return new Response("Failed to insert students", { status: 500 }); + } + }, +}; diff --git a/routes/(apps)/mobility/partials/students.tsx b/routes/(apps)/mobility/partials/students.tsx index 89c55cf..c156c61 100644 --- a/routes/(apps)/mobility/partials/students.tsx +++ b/routes/(apps)/mobility/partials/students.tsx @@ -1,7 +1,7 @@ import { RouteConfig } from "$fresh/server.ts"; import UploadStudents from "../(_components)/UploadStudents.tsx"; -//import ConsultStudents from "../(_islands)/ConsultStudents.tsx"; -//import EditStudents from "../(_islands)/EditStudents.tsx"; +//import ConsultStudents from "../(_components)/ConsultStudents.tsx"; +//import EditStudents from "../(_components)/EditStudents.tsx"; export const config: RouteConfig = { skipAppWrapper: false, From 0672666bd3f128923bd0a7edaf94b0cdb7a36cbc Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Tue, 21 Jan 2025 15:17:47 +0100 Subject: [PATCH 10/14] CDN working with Docker --- Dockerfile | 2 +- deno.json | 1 + fresh.gen.ts | 18 ++++++++++++++++++ .../ConsultMobility.tsx | 0 .../ConsultStudents.tsx | 0 .../EditMobility.tsx | 0 .../EditStudents.tsx | 0 .../ImportFile.tsx | 0 .../UploadStudents.tsx | 10 +++++++++- routes/(apps)/mobility/api/insert_students.ts | 6 ------ routes/(apps)/mobility/partials/students.tsx | 2 +- 11 files changed, 30 insertions(+), 9 deletions(-) rename routes/(apps)/mobility/{(_components) => (_islands)}/ConsultMobility.tsx (100%) rename routes/(apps)/mobility/{(_components) => (_islands)}/ConsultStudents.tsx (100%) rename routes/(apps)/mobility/{(_components) => (_islands)}/EditMobility.tsx (100%) rename routes/(apps)/mobility/{(_components) => (_islands)}/EditStudents.tsx (100%) rename routes/(apps)/mobility/{(_components) => (_islands)}/ImportFile.tsx (100%) rename routes/(apps)/mobility/{(_components) => (_islands)}/UploadStudents.tsx (87%) diff --git a/Dockerfile b/Dockerfile index fd8f30c..6615438 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM denoland/deno:alpine WORKDIR /app COPY . . -RUN deno cache main.ts +RUN deno cache main.ts --allow-import flag RUN deno task build USER deno diff --git a/deno.json b/deno.json index e9dcaca..344187a 100644 --- a/deno.json +++ b/deno.json @@ -26,6 +26,7 @@ "@db/sqlite": "jsr:@db/sqlite@^0.12.0", "@melvdouc/xml-parser": "jsr:@melvdouc/xml-parser@^0.1.1", "@popov/jwt": "jsr:@popov/jwt@^1.0.1", + "@psych/sheet": "jsr:@psych/sheet@^1.0.6", "@std/cli": "jsr:@std/cli@^1.0.10", "preact": "https://esm.sh/preact@10.22.0", "preact/": "https://esm.sh/preact@10.22.0/", diff --git a/fresh.gen.ts b/fresh.gen.ts index 0c4ae9b..e385358 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -24,6 +24,12 @@ import * as $login from "./routes/login.tsx"; import * as $logout from "./routes/logout.tsx"; import * as $_islands_AppNavigator from "./routes/(_islands)/AppNavigator.tsx"; import * as $_islands_Navbar from "./routes/(_islands)/Navbar.tsx"; +import * as $_apps_mobility_islands_ConsultMobility from "./routes/(apps)/mobility/(_islands)/ConsultMobility.tsx"; +import * as $_apps_mobility_islands_ConsultStudents from "./routes/(apps)/mobility/(_islands)/ConsultStudents.tsx"; +import * as $_apps_mobility_islands_EditMobility from "./routes/(apps)/mobility/(_islands)/EditMobility.tsx"; +import * as $_apps_mobility_islands_EditStudents from "./routes/(apps)/mobility/(_islands)/EditStudents.tsx"; +import * as $_apps_mobility_islands_ImportFile from "./routes/(apps)/mobility/(_islands)/ImportFile.tsx"; +import * as $_apps_mobility_islands_UploadStudents from "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx"; import type { Manifest } from "$fresh/server.ts"; const manifest = { @@ -59,6 +65,18 @@ const manifest = { islands: { "./routes/(_islands)/AppNavigator.tsx": $_islands_AppNavigator, "./routes/(_islands)/Navbar.tsx": $_islands_Navbar, + "./routes/(apps)/mobility/(_islands)/ConsultMobility.tsx": + $_apps_mobility_islands_ConsultMobility, + "./routes/(apps)/mobility/(_islands)/ConsultStudents.tsx": + $_apps_mobility_islands_ConsultStudents, + "./routes/(apps)/mobility/(_islands)/EditMobility.tsx": + $_apps_mobility_islands_EditMobility, + "./routes/(apps)/mobility/(_islands)/EditStudents.tsx": + $_apps_mobility_islands_EditStudents, + "./routes/(apps)/mobility/(_islands)/ImportFile.tsx": + $_apps_mobility_islands_ImportFile, + "./routes/(apps)/mobility/(_islands)/UploadStudents.tsx": + $_apps_mobility_islands_UploadStudents, }, baseUrl: import.meta.url, } satisfies Manifest; diff --git a/routes/(apps)/mobility/(_components)/ConsultMobility.tsx b/routes/(apps)/mobility/(_islands)/ConsultMobility.tsx similarity index 100% rename from routes/(apps)/mobility/(_components)/ConsultMobility.tsx rename to routes/(apps)/mobility/(_islands)/ConsultMobility.tsx diff --git a/routes/(apps)/mobility/(_components)/ConsultStudents.tsx b/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx similarity index 100% rename from routes/(apps)/mobility/(_components)/ConsultStudents.tsx rename to routes/(apps)/mobility/(_islands)/ConsultStudents.tsx diff --git a/routes/(apps)/mobility/(_components)/EditMobility.tsx b/routes/(apps)/mobility/(_islands)/EditMobility.tsx similarity index 100% rename from routes/(apps)/mobility/(_components)/EditMobility.tsx rename to routes/(apps)/mobility/(_islands)/EditMobility.tsx diff --git a/routes/(apps)/mobility/(_components)/EditStudents.tsx b/routes/(apps)/mobility/(_islands)/EditStudents.tsx similarity index 100% rename from routes/(apps)/mobility/(_components)/EditStudents.tsx rename to routes/(apps)/mobility/(_islands)/EditStudents.tsx diff --git a/routes/(apps)/mobility/(_components)/ImportFile.tsx b/routes/(apps)/mobility/(_islands)/ImportFile.tsx similarity index 100% rename from routes/(apps)/mobility/(_components)/ImportFile.tsx rename to routes/(apps)/mobility/(_islands)/ImportFile.tsx diff --git a/routes/(apps)/mobility/(_components)/UploadStudents.tsx b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx similarity index 87% rename from routes/(apps)/mobility/(_components)/UploadStudents.tsx rename to routes/(apps)/mobility/(_islands)/UploadStudents.tsx index d8db47a..ab93e74 100644 --- a/routes/(apps)/mobility/(_components)/UploadStudents.tsx +++ b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx @@ -6,20 +6,26 @@ export default function UploadStudents() { const statusMessage = useSignal(""); const fileData = useSignal(null); + console.log("Component UploadStudents mounted"); + const handleFileChange = (event: Event) => { const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { fileData.value = input.files[0]; statusMessage.value = "File selected: " + input.files[0].name; + console.log("File selected:", input.files[0].name); } else { fileData.value = null; statusMessage.value = "No file selected"; + console.log("No file selected."); } }; const confirmUpload = async () => { + console.log("Confirm Upload clicked"); if (!fileData.value) { statusMessage.value = "Please select a file before confirming upload."; + console.error("Error: No file selected."); return; } @@ -34,10 +40,12 @@ export default function UploadStudents() { for (const sheetName of workbook.SheetNames) { const sheet = workbook.Sheets[sheetName]; const data = XLSX.utils.sheet_to_json(sheet, { - header: ["Nom", "Prénom", "Mail"], + header: ["Nom", "Prénom", "Mail"], range: 1, // Ignorer les en-têtes }); + console.log(`Data from sheet ${sheetName}:`, data); + const response = await fetch("/api/insert_students", { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/routes/(apps)/mobility/api/insert_students.ts b/routes/(apps)/mobility/api/insert_students.ts index 790dce0..52be9dd 100644 --- a/routes/(apps)/mobility/api/insert_students.ts +++ b/routes/(apps)/mobility/api/insert_students.ts @@ -4,10 +4,8 @@ import { Database } from "@db/sqlite"; export const handler: Handlers = { async GET(_request, context) { try { - // Ouvre ou crée la base de données SQLite const db = new Database("databases/data/mobility.db"); - // Crée la table si elle n'existe pas db.execute(` CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -18,7 +16,6 @@ export const handler: Handlers = { ); `); - // Récupère toutes les données const students = []; for (const [id, firstName, lastName, email, promotion] of db.query( "SELECT id, firstName, lastName, email, promotion FROM students" @@ -43,10 +40,8 @@ export const handler: Handlers = { const body = await request.json(); const { data, promoName } = body; - // Ouvre ou crée la base de données SQLite const db = new Database("databases/data/mobility.db"); - // Crée la table si elle n'existe pas db.execute(` CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -57,7 +52,6 @@ export const handler: Handlers = { ); `); - // Prépare et insère les données const insertQuery = "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)"; for (const student of data) { diff --git a/routes/(apps)/mobility/partials/students.tsx b/routes/(apps)/mobility/partials/students.tsx index c156c61..bbc0d34 100644 --- a/routes/(apps)/mobility/partials/students.tsx +++ b/routes/(apps)/mobility/partials/students.tsx @@ -1,5 +1,5 @@ import { RouteConfig } from "$fresh/server.ts"; -import UploadStudents from "../(_components)/UploadStudents.tsx"; +import UploadStudents from "../(_islands)/UploadStudents.tsx"; //import ConsultStudents from "../(_components)/ConsultStudents.tsx"; //import EditStudents from "../(_components)/EditStudents.tsx"; From e049056295c043908542f1538425ea10f2e97b85 Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Tue, 21 Jan 2025 15:45:38 +0100 Subject: [PATCH 11/14] Working DB for students insertion --- .../mobility/(_islands)/UploadStudents.tsx | 9 ++-- routes/(apps)/mobility/api/insert_students.ts | 50 ++++++++++++------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx index ab93e74..2400c35 100644 --- a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx +++ b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx @@ -6,8 +6,6 @@ export default function UploadStudents() { const statusMessage = useSignal(""); const fileData = useSignal(null); - console.log("Component UploadStudents mounted"); - const handleFileChange = (event: Event) => { const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { @@ -17,12 +15,11 @@ export default function UploadStudents() { } else { fileData.value = null; statusMessage.value = "No file selected"; - console.log("No file selected."); } }; const confirmUpload = async () => { - console.log("Confirm Upload clicked"); + console.log("Confirm Upload"); if (!fileData.value) { statusMessage.value = "Please select a file before confirming upload."; console.error("Error: No file selected."); @@ -45,8 +42,8 @@ export default function UploadStudents() { }); console.log(`Data from sheet ${sheetName}:`, data); - - const response = await fetch("/api/insert_students", { + + const response = await fetch("/mobility/api/insert_students", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ promoName: sheetName, data }), diff --git a/routes/(apps)/mobility/api/insert_students.ts b/routes/(apps)/mobility/api/insert_students.ts index 52be9dd..e75882d 100644 --- a/routes/(apps)/mobility/api/insert_students.ts +++ b/routes/(apps)/mobility/api/insert_students.ts @@ -1,12 +1,13 @@ import { Handlers } from "$fresh/server.ts"; -import { Database } from "@db/sqlite"; +import { Database } from "@db/sqlite"; export const handler: Handlers = { async GET(_request, context) { try { const db = new Database("databases/data/mobility.db"); - db.execute(` + db.prepare( + ` CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, firstName TEXT NOT NULL, @@ -14,18 +15,16 @@ export const handler: Handlers = { email TEXT NOT NULL, promotion TEXT NOT NULL ); - `); + ` + ).run(); - const students = []; - for (const [id, firstName, lastName, email, promotion] of db.query( + const rows = db.prepare( "SELECT id, firstName, lastName, email, promotion FROM students" - )) { - students.push({ id, firstName, lastName, email, promotion }); - } + ).all(); db.close(); - return new Response(JSON.stringify(students), { + return new Response(JSON.stringify(rows), { status: 200, headers: { "Content-Type": "application/json" }, }); @@ -36,13 +35,24 @@ export const handler: Handlers = { }, async POST(request) { + console.log("API /mobility/api/insert_students called"); + try { const body = await request.json(); const { data, promoName } = body; + console.log("Received data:", { promoName, data }); + + if (!promoName || !Array.isArray(data)) { + throw new Error("Invalid request body"); + } + const db = new Database("databases/data/mobility.db"); - db.execute(` + console.log("Database opened successfully"); + + db.prepare( + ` CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, firstName TEXT NOT NULL, @@ -50,19 +60,21 @@ export const handler: Handlers = { email TEXT NOT NULL, promotion TEXT NOT NULL ); - `); + ` + ).run(); + + console.log("Table ensured successfully"); + + const insertQuery = db.prepare( + "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)" + ); - const insertQuery = - "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)"; for (const student of data) { - db.query(insertQuery, [ - student.firstName, - student.lastName, - student.email, - promoName, - ]); + console.log("Inserting student:", student); + insertQuery.run(student.Nom, student["Prénom"], student.Mail, promoName); } + console.log("All students inserted successfully"); db.close(); return new Response("Students inserted successfully", { status: 201 }); From 9d4183f8b33927e271631b5d570b71aa0af3560f Mon Sep 17 00:00:00 2001 From: Clayzxr Date: Tue, 21 Jan 2025 15:54:38 +0100 Subject: [PATCH 12/14] Consult students from DB --- .../mobility/(_islands)/ConsultStudents.tsx | 67 +++++++++++++++++++ routes/(apps)/mobility/partials/students.tsx | 5 +- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx b/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx index e69de29..552b88b 100644 --- a/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx +++ b/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx @@ -0,0 +1,67 @@ +import { useEffect, useState } from "preact/hooks"; + +interface Student { + id: number; + firstName: string; + lastName: string; + email: string; + promotion: string; +} + +export default function ConsultStudents() { + const [students, setStudents] = useState([]); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchStudents = async () => { + try { + const response = await fetch("/mobility/api/insert_students"); + + if (!response.ok) { + throw new Error(`Error fetching students: ${response.statusText}`); + } + + const data: Student[] = await response.json(); + setStudents(data); + } catch (err) { + console.error("Error fetching students:", err); + setError("Failed to load students. Please try again later."); + } + }; + + fetchStudents(); + }, []); + + return ( +
+

Consult Students

+ {error &&

{error}

} + {students.length === 0 ? ( +

No students found.

+ ) : ( + + + + + + + + + + + + {students.map((student) => ( + + + + + + + + ))} + +
IDFirst NameLast NameEmailPromotion
{student.id}{student.firstName}{student.lastName}{student.email}{student.promotion}
+ )} +
+ ); +} diff --git a/routes/(apps)/mobility/partials/students.tsx b/routes/(apps)/mobility/partials/students.tsx index bbc0d34..f3c7cb9 100644 --- a/routes/(apps)/mobility/partials/students.tsx +++ b/routes/(apps)/mobility/partials/students.tsx @@ -1,7 +1,7 @@ import { RouteConfig } from "$fresh/server.ts"; import UploadStudents from "../(_islands)/UploadStudents.tsx"; -//import ConsultStudents from "../(_components)/ConsultStudents.tsx"; -//import EditStudents from "../(_components)/EditStudents.tsx"; +import ConsultStudents from "../(_islands)/ConsultStudents.tsx"; +//import EditStudents from "../(_islands)/EditStudents.tsx"; export const config: RouteConfig = { skipAppWrapper: false, @@ -14,6 +14,7 @@ export default function Students() {

Manage Promotions


+ ); } From fa66621abc3ba3e351e1bd8f4535a50601959fd7 Mon Sep 17 00:00:00 2001 From: Kevin FEDYNA Date: Tue, 21 Jan 2025 16:09:36 +0100 Subject: [PATCH 13/14] Fixed partial handling and added cookies --- databases/init/students.sql | 14 ++++++++ defaults/interfaces.ts | 41 +++++++++++++++++++++ defaults/makeIndex.ts | 14 ++++---- defaults/makePartials.tsx | 18 ++++++---- fresh.gen.ts | 7 ++++ routes/(apps)/students/(_props)/props.ts | 14 ++++++++ routes/(apps)/students/api/example.ts | 22 ++++++++++++ routes/(apps)/students/index.tsx | 2 ++ routes/(apps)/students/partials/index.tsx | 14 ++++++++ routes/_middleware.ts | 9 +++-- routes/login.tsx | 44 ++++++++--------------- toolbox/module/create.ts | 7 ++-- 12 files changed, 158 insertions(+), 48 deletions(-) create mode 100644 databases/init/students.sql create mode 100644 routes/(apps)/students/(_props)/props.ts create mode 100644 routes/(apps)/students/api/example.ts create mode 100644 routes/(apps)/students/index.tsx create mode 100644 routes/(apps)/students/partials/index.tsx diff --git a/databases/init/students.sql b/databases/init/students.sql new file mode 100644 index 0000000..b503221 --- /dev/null +++ b/databases/init/students.sql @@ -0,0 +1,14 @@ +create table students ( + userId text primary key, + firstName text, + lastName text, + mail text, + promo integer, + foreign key(promo) references promo(id) +); + +create table promo ( + id integer, + endyear integer, + current integer +); \ No newline at end of file diff --git a/defaults/interfaces.ts b/defaults/interfaces.ts index 9b417e2..3189dfe 100644 --- a/defaults/interfaces.ts +++ b/defaults/interfaces.ts @@ -1,3 +1,5 @@ +import { type RegularTagNode, type TextNode } from "@melvdouc/xml-parser"; + export interface AppProperties { name: string; icon: string; @@ -6,4 +8,43 @@ export interface AppProperties { hint: string; } +export interface CasTagNode extends RegularTagNode { + children: [TextNode]; +} + +export interface CasGroupNode extends RegularTagNode { + children: CasTagNode[]; +} + +export interface CasResponse extends RegularTagNode { + children: [TextNode, CasGroupNode]; +} + +export interface CasContent { + amuCampus: string; + amuComposante: string; + amuDateValidation: string; + coGroup: string; + eduPersonPrimaryAffiliation: string; + eduPersonPrincipalName: string; + mail: string; + displayName: string; + givenName: string; + memberOf: string[]; + sn: string; + supannCivilite: string; + supannEntiteAffectation: string; + supannEtuAnneeInscription: string; + supannEtuEtape: string; + uid: string; +} + +export interface LoginJWT { + iss: "PolyMPR"; + iat: number; + exp: number; + aud: "PolyMPR"; + user: CasContent; +} + export type EmptyObject = Record; diff --git a/defaults/makeIndex.ts b/defaults/makeIndex.ts index 4205b03..f4ebd11 100644 --- a/defaults/makeIndex.ts +++ b/defaults/makeIndex.ts @@ -1,10 +1,12 @@ -import { EmptyObject } from "$root/defaults/interfaces.ts"; +import { FreshContext } from "$fresh/server.ts"; +import { State } from "$root/routes/_middleware.ts"; -export default function makeIndex< - IndexProps = EmptyObject, ->(basePath: string) { - return async function Index(props: IndexProps) { +export default function makeIndex(basePath: string) { + return async function Index( + request: Request, + context: FreshContext, + ) { const index = (await import(`${basePath}/partials/index.tsx`)).Index; - return index(props); + return index(request, context); }; } diff --git a/defaults/makePartials.tsx b/defaults/makePartials.tsx index 783c20e..e6b81af 100644 --- a/defaults/makePartials.tsx +++ b/defaults/makePartials.tsx @@ -1,6 +1,7 @@ import { JSX } from "preact"; import { Partial } from "$fresh/runtime.ts"; -import { RouteConfig } from "$fresh/server.ts"; +import { FreshContext, RouteConfig } from "$fresh/server.ts"; +import { State } from "$root/routes/_middleware.ts"; export function getPartialsConfig(): RouteConfig { return { @@ -9,14 +10,19 @@ export function getPartialsConfig(): RouteConfig { }; } -// deno-lint-ignore no-explicit-any -export function makePartials( - page: (props: Props) => JSX.Element, +export function makePartials( + page: ( + request: Request, + context: FreshContext, + ) => Promise, ) { - return function WrappedElements(props: Props): JSX.Element { + return async function WrappedElements( + request: Request, + context: FreshContext, + ): Promise { return ( - {page(props)} + {await page(request, context)} ); }; diff --git a/fresh.gen.ts b/fresh.gen.ts index 0c4ae9b..a01fa4e 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -14,6 +14,9 @@ import * as $_apps_notes_partials_admin_courses from "./routes/(apps)/notes/part import * as $_apps_notes_partials_admin_students from "./routes/(apps)/notes/partials/(admin)/students.tsx"; import * as $_apps_notes_partials_index from "./routes/(apps)/notes/partials/index.tsx"; import * as $_apps_notes_partials_notes from "./routes/(apps)/notes/partials/notes.tsx"; +import * as $_apps_students_api_example from "./routes/(apps)/students/api/example.ts"; +import * as $_apps_students_index from "./routes/(apps)/students/index.tsx"; +import * as $_apps_students_partials_index from "./routes/(apps)/students/partials/index.tsx"; import * as $_404 from "./routes/_404.tsx"; import * as $_app from "./routes/_app.tsx"; import * as $_middleware from "./routes/_middleware.ts"; @@ -47,6 +50,10 @@ const manifest = { $_apps_notes_partials_admin_students, "./routes/(apps)/notes/partials/index.tsx": $_apps_notes_partials_index, "./routes/(apps)/notes/partials/notes.tsx": $_apps_notes_partials_notes, + "./routes/(apps)/students/api/example.ts": $_apps_students_api_example, + "./routes/(apps)/students/index.tsx": $_apps_students_index, + "./routes/(apps)/students/partials/index.tsx": + $_apps_students_partials_index, "./routes/_404.tsx": $_404, "./routes/_app.tsx": $_app, "./routes/_middleware.ts": $_middleware, diff --git a/routes/(apps)/students/(_props)/props.ts b/routes/(apps)/students/(_props)/props.ts new file mode 100644 index 0000000..0a7a522 --- /dev/null +++ b/routes/(apps)/students/(_props)/props.ts @@ -0,0 +1,14 @@ +import { AppProperties } from "$root/defaults/interfaces.ts"; + +const properties: AppProperties = { + name: "Students", + icon: "badge", + pages: { + index: "Homepage", + upload: "Upload students", + }, + adminOnly: ["upload"], + hint: "See student information", +}; + +export default properties; diff --git a/routes/(apps)/students/api/example.ts b/routes/(apps)/students/api/example.ts new file mode 100644 index 0000000..9f04cd1 --- /dev/null +++ b/routes/(apps)/students/api/example.ts @@ -0,0 +1,22 @@ +import { Handlers } from "$fresh/server.ts"; + +export const handler: Handlers = { + async POST(request, context) { + if (request.headers.get("content-type") != "application/json") { + return new Response(null, { + status: 400, + }); + } + + const responseBody = { + requestBody: await request.json(), + context, + }; + + return new Response(JSON.stringify(responseBody), { + headers: { + "content-type": "application/json", + }, + }); + }, +}; diff --git a/routes/(apps)/students/index.tsx b/routes/(apps)/students/index.tsx new file mode 100644 index 0000000..1d82f7f --- /dev/null +++ b/routes/(apps)/students/index.tsx @@ -0,0 +1,2 @@ +import makeIndex from "$root/defaults/makeIndex.ts"; +export default makeIndex(import.meta.dirname!); diff --git a/routes/(apps)/students/partials/index.tsx b/routes/(apps)/students/partials/index.tsx new file mode 100644 index 0000000..2971e0e --- /dev/null +++ b/routes/(apps)/students/partials/index.tsx @@ -0,0 +1,14 @@ +import { + getPartialsConfig, + makePartials, +} from "$root/defaults/makePartials.tsx"; +import { FreshContext } from "$fresh/server.ts"; +import { State } from "$root/routes/_middleware.ts"; + +// deno-lint-ignore require-await +export async function Index(_request: Request, context: FreshContext) { + return

Welcome to {context.state.session?.displayName}.

; +} + +export const config = getPartialsConfig(); +export default makePartials(Index); diff --git a/routes/_middleware.ts b/routes/_middleware.ts index 281a9c0..3601da1 100644 --- a/routes/_middleware.ts +++ b/routes/_middleware.ts @@ -1,7 +1,7 @@ import { FreshContext } from "$fresh/server.ts"; import { getCookies } from "$std/http/cookie.ts"; import { getJwtPayload, isJwtValid } from "@popov/jwt"; -import { LoginJWT } from "$root/routes/login.tsx"; +import { CasContent, LoginJWT } from "$root/defaults/interfaces.ts"; const PUBLIC_ROUTES = [ "/", @@ -16,6 +16,7 @@ const jwtKeyCache: Record = {}; export interface State { isAuthenticated: boolean; + session: CasContent; } function isRoutePublic(route: string) { @@ -44,12 +45,16 @@ export const handler = [ } const content = getJwtPayload(cookies["sessionToken"]) as LoginJWT; - const key = getKey(content.user.uid as string); + const key = getKey(content.user.uid); context.state.isAuthenticated = await isJwtValid( cookies["sessionToken"], key, ); + const session: CasContent = + (getJwtPayload(cookies["sessionToken"]) as LoginJWT).user; + + context.state.session = session; return await context.next(); }, diff --git a/routes/login.tsx b/routes/login.tsx index 284fc5e..66dfcb0 100644 --- a/routes/login.tsx +++ b/routes/login.tsx @@ -1,10 +1,12 @@ import { Handlers } from "$fresh/server.ts"; import { State } from "$root/routes/_middleware.ts"; +import { parse, type RegularTagNode } from "@melvdouc/xml-parser"; import { - parse, - type RegularTagNode, - type TextNode, -} from "@melvdouc/xml-parser"; + CasContent, + CasResponse, + CasTagNode, + LoginJWT, +} from "$root/defaults/interfaces.ts"; import { createJwt } from "@popov/jwt"; import { setCookie } from "$std/http/cookie.ts"; import { getKey } from "$root/routes/_middleware.ts"; @@ -12,26 +14,6 @@ import { getKey } from "$root/routes/_middleware.ts"; const SERVICE = "https://localhost/login"; const CAS = "https://ident.univ-amu.fr/cas"; -interface CasTagNode extends RegularTagNode { - children: [TextNode]; -} - -interface CasGroupNode extends RegularTagNode { - children: CasTagNode[]; -} - -interface CasResponse extends RegularTagNode { - children: [TextNode, CasGroupNode]; -} - -export interface LoginJWT { - iss: "PolyMPR"; - iat: number; - exp: number; - aud: "PolyMPR"; - user: Record; -} - function getTag(tag: CasTagNode): [string, string] { return [ tag.tagName.replace("cas:", ""), @@ -43,11 +25,13 @@ function createUserJWT(casResponse: CasResponse): Promise { const nodes = casResponse.children[1].children.map(getTag); const fullUserInfos: Record = {}; - nodes.forEach(([key, value]) => { - if (fullUserInfos[key] && Array.isArray(fullUserInfos[key])) { + nodes.forEach(([key, value]: [string, string]) => { + if (typeof fullUserInfos[key] == "string") { + fullUserInfos[key] = [fullUserInfos[key]]; + } + + if (Array.isArray(fullUserInfos[key])) { fullUserInfos[key].push(value); - } else if (fullUserInfos[key]) { - fullUserInfos[key] = [fullUserInfos[key], value]; } else { fullUserInfos[key] = value; } @@ -56,12 +40,12 @@ function createUserJWT(casResponse: CasResponse): Promise { const now = Math.floor(Date.now() / 1000); const oneHour = 60 * 60; - const payload = { + const payload: LoginJWT = { iss: "PolyMPR", iat: now, exp: now + oneHour, aud: "PolyMPR", - user: fullUserInfos, + user: fullUserInfos as unknown as CasContent, }; const key = getKey(fullUserInfos.uid as string); diff --git a/toolbox/module/create.ts b/toolbox/module/create.ts index f289d98..aa93463 100644 --- a/toolbox/module/create.ts +++ b/toolbox/module/create.ts @@ -77,15 +77,14 @@ function getIndexContent(_name: string) { function getPartialIndexContent(name: string) { return ` - import { EmptyObject } from "$root/defaults/interfaces.ts"; import { getPartialsConfig, makePartials, } from "$root/defaults/makePartials.tsx"; + import { FreshContext } from "$fresh/server.ts"; + import { State } from "$root/routes/_middleware.ts"; - type ${name}IndexProps = EmptyObject; - - export function Index(_props: ${name}IndexProps) { + export async function Index(request: Request, context: FreshContext) { return

Welcome to ${name}.

; } From 6b8966c5ca6c79fd1f3ffaf11ad61b5f9ca70f55 Mon Sep 17 00:00:00 2001 From: Kevin FEDYNA Date: Tue, 21 Jan 2025 16:13:50 +0100 Subject: [PATCH 14/14] Linted and formatted --- .../mobility/(_islands)/ConsultStudents.tsx | 4 +--- .../mobility/(_islands)/UploadStudents.tsx | 13 +++++++----- routes/(apps)/mobility/api/insert_students.ts | 20 ++++++++++++------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx b/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx index 552b88b..4ca3459 100644 --- a/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx +++ b/routes/(apps)/mobility/(_islands)/ConsultStudents.tsx @@ -36,9 +36,7 @@ export default function ConsultStudents() {

Consult Students

{error &&

{error}

} - {students.length === 0 ? ( -

No students found.

- ) : ( + {students.length === 0 ?

No students found.

: ( diff --git a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx index 2400c35..763cdc4 100644 --- a/routes/(apps)/mobility/(_islands)/UploadStudents.tsx +++ b/routes/(apps)/mobility/(_islands)/UploadStudents.tsx @@ -18,7 +18,7 @@ export default function UploadStudents() { } }; - const confirmUpload = async () => { + const confirmUpload = () => { console.log("Confirm Upload"); if (!fileData.value) { statusMessage.value = "Please select a file before confirming upload."; @@ -37,12 +37,12 @@ export default function UploadStudents() { for (const sheetName of workbook.SheetNames) { const sheet = workbook.Sheets[sheetName]; const data = XLSX.utils.sheet_to_json(sheet, { - header: ["Nom", "Prénom", "Mail"], + header: ["Nom", "Prénom", "Mail"], range: 1, // Ignorer les en-têtes }); console.log(`Data from sheet ${sheetName}:`, data); - + const response = await fetch("/mobility/api/insert_students", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -50,14 +50,17 @@ export default function UploadStudents() { }); if (!response.ok) { - throw new Error(`Failed to insert data for promotion ${sheetName}`); + throw new Error( + `Failed to insert data for promotion ${sheetName}`, + ); } } statusMessage.value = "Data uploaded and inserted successfully!"; } catch (error) { console.error("Error processing the file:", error); - statusMessage.value = "Error processing the file. Please check its format."; + statusMessage.value = + "Error processing the file. Please check its format."; } }; diff --git a/routes/(apps)/mobility/api/insert_students.ts b/routes/(apps)/mobility/api/insert_students.ts index e75882d..e8af2fe 100644 --- a/routes/(apps)/mobility/api/insert_students.ts +++ b/routes/(apps)/mobility/api/insert_students.ts @@ -1,8 +1,9 @@ import { Handlers } from "$fresh/server.ts"; -import { Database } from "@db/sqlite"; +import { Database } from "@db/sqlite"; export const handler: Handlers = { - async GET(_request, context) { + // deno-lint-ignore require-await + async GET(_request, _context) { try { const db = new Database("databases/data/mobility.db"); @@ -15,11 +16,11 @@ export const handler: Handlers = { email TEXT NOT NULL, promotion TEXT NOT NULL ); - ` + `, ).run(); const rows = db.prepare( - "SELECT id, firstName, lastName, email, promotion FROM students" + "SELECT id, firstName, lastName, email, promotion FROM students", ).all(); db.close(); @@ -60,18 +61,23 @@ export const handler: Handlers = { email TEXT NOT NULL, promotion TEXT NOT NULL ); - ` + `, ).run(); console.log("Table ensured successfully"); const insertQuery = db.prepare( - "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)" + "INSERT INTO students (firstName, lastName, email, promotion) VALUES (?, ?, ?, ?)", ); for (const student of data) { console.log("Inserting student:", student); - insertQuery.run(student.Nom, student["Prénom"], student.Mail, promoName); + insertQuery.run( + student.Nom, + student["Prénom"], + student.Mail, + promoName, + ); } console.log("All students inserted successfully");