From 93aa2769dd9594dc99671a5be6fc68f9099818c8 Mon Sep 17 00:00:00 2001 From: Djalim Simaila Date: Tue, 21 Apr 2026 16:03:40 +0200 Subject: [PATCH] added admin module et get enseignement --- .../admin/(_islands)/ConsultMobility.tsx | 115 ++++++++ .../admin/(_islands)/ConsultStudents_test.tsx | 75 ++++++ .../(apps)/admin/(_islands)/EditMobility.tsx | 248 ++++++++++++++++++ routes/(apps)/admin/(_islands)/ImportFile.tsx | 0 routes/(apps)/admin/(_props)/props.ts | 16 ++ routes/(apps)/admin/api/enseignement.ts | 18 ++ routes/(apps)/admin/api/insert_mobility.ts | 116 ++++++++ routes/(apps)/admin/index.tsx | 2 + .../(admin)/consult_students_test.tsx | 21 ++ .../admin/partials/(admin)/edit_mobility.tsx | 20 ++ routes/(apps)/admin/partials/index.tsx | 14 + routes/(apps)/admin/partials/overview.tsx | 20 ++ 12 files changed, 665 insertions(+) create mode 100644 routes/(apps)/admin/(_islands)/ConsultMobility.tsx create mode 100644 routes/(apps)/admin/(_islands)/ConsultStudents_test.tsx create mode 100644 routes/(apps)/admin/(_islands)/EditMobility.tsx create mode 100644 routes/(apps)/admin/(_islands)/ImportFile.tsx create mode 100644 routes/(apps)/admin/(_props)/props.ts create mode 100644 routes/(apps)/admin/api/enseignement.ts create mode 100644 routes/(apps)/admin/api/insert_mobility.ts create mode 100644 routes/(apps)/admin/index.tsx create mode 100644 routes/(apps)/admin/partials/(admin)/consult_students_test.tsx create mode 100644 routes/(apps)/admin/partials/(admin)/edit_mobility.tsx create mode 100644 routes/(apps)/admin/partials/index.tsx create mode 100644 routes/(apps)/admin/partials/overview.tsx diff --git a/routes/(apps)/admin/(_islands)/ConsultMobility.tsx b/routes/(apps)/admin/(_islands)/ConsultMobility.tsx new file mode 100644 index 0000000..7befa63 --- /dev/null +++ b/routes/(apps)/admin/(_islands)/ConsultMobility.tsx @@ -0,0 +1,115 @@ +import { useEffect, useState } from "preact/hooks"; + +interface Promotion { + id: number; + name: string; +} + +interface Student { + id: string; + firstName: string; + lastName: string; + promotionId: number; +} + +interface Mobility { + id: number; + studentId: string; + startDate: string | null; + endDate: string | null; + weeksCount: number | null; + destinationCountry: string | null; + destinationName: string | null; + mobilityStatus: string; +} + +export default function ConsultMobility() { + const [data, setData] = useState< + | { + promotions?: Promotion[]; + students?: Student[]; + mobilities?: Mobility[]; + } + | null + >(null); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + console.log("ConsultMobility: Fetching data from API..."); + try { + const response = await fetch("/mobility/api/insert_mobility"); + console.log("ConsultMobility: API response status:", response.status); + + if (!response.ok) { + throw new Error(`Error fetching data: ${response.statusText}`); + } + + const result = await response.json(); + console.log("ConsultMobility: Data fetched successfully:", result); + setData(result); + } catch (err) { + console.error("ConsultMobility: Error fetching data:", err); + setError("Failed to load mobility data. Please try again later."); + } + }; + + fetchData(); + }, []); + + if (error) { + return

{error}

; + } + + if (!data?.promotions) { + return

No promotions found.

; + } + + return ( +
+

Consult Mobility

+ {data.promotions.map((promo) => ( +
+

Promotion: {promo.name}

+ + + + + + + + + + + + + + + + {data.students + ?.filter((student) => student.promotionId === promo.id) + .map((student) => { + const mobility = data.mobilities?.find((mob) => + mob.studentId === student.id + ); + return ( + + + + + + + + + + + + ); + })} + +
IDFirst NameLast NameStart DateEnd DateWeeks CountDestination CountryDestination NameStatus
{student.id}{student.firstName}{student.lastName}{mobility?.startDate || "N/A"}{mobility?.endDate || "N/A"}{mobility?.weeksCount ?? "N/A"}{mobility?.destinationCountry || "N/A"}{mobility?.destinationName || "N/A"}{mobility?.mobilityStatus || "N/A"}
+
+ ))} +
+ ); +} diff --git a/routes/(apps)/admin/(_islands)/ConsultStudents_test.tsx b/routes/(apps)/admin/(_islands)/ConsultStudents_test.tsx new file mode 100644 index 0000000..3e008bc --- /dev/null +++ b/routes/(apps)/admin/(_islands)/ConsultStudents_test.tsx @@ -0,0 +1,75 @@ +import { useEffect, useState } from "preact/hooks"; + +interface Promotion { + id: number; + name: string; +} + +interface Student { + id: number; + firstName: string; + lastName: string; + mail: string; + promotionId: number; + promotionName: string; +} + +export default function ConsultStudents_test() { + const [data, setData] = useState< + { promotions: Promotion[]; students: Student[] } | null + >(null); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await fetch("/students/api/insert_students"); + if (!response.ok) { + throw new Error(`Error fetching data: ${response.statusText}`); + } + + const result = await response.json(); + setData(result); + } catch (err) { + console.error("Error fetching data:", err); + setError("Failed to load data. Please try again later."); + } + }; + + fetchData(); + }, []); + + return ( +
+

Consult Students

+ {error &&

{error}

} + {data?.promotions.map((promo) => ( +
+

Promotion: {promo.id}

+ + + + + + + + + + + {data.students + .filter((student) => student.promotionId === promo.id) + .map((student) => ( + + + + + + + ))} + +
IDFirst NameLast NameEmail
{student.id}{student.firstName}{student.lastName}{student.mail}
+
+ ))} +
+ ); +} diff --git a/routes/(apps)/admin/(_islands)/EditMobility.tsx b/routes/(apps)/admin/(_islands)/EditMobility.tsx new file mode 100644 index 0000000..8991926 --- /dev/null +++ b/routes/(apps)/admin/(_islands)/EditMobility.tsx @@ -0,0 +1,248 @@ +import { useEffect, useState } from "preact/hooks"; + +interface Student { + id: string; + firstName: string; + lastName: string; + promotionId: number; +} + +interface Promotion { + id: number; + name: string; +} + +interface Mobility { + id: number | null; + studentId: string; + startDate: string | null; + endDate: string | null; + weeksCount: number | null; + destinationCountry: string | null; + destinationName: string | null; + mobilityStatus: string; +} + +export default function EditMobility() { + const [data, setData] = useState< + | { + promotions?: Promotion[]; + students?: Student[]; + mobilities?: Mobility[]; + } + | null + >(null); + const [error, setError] = useState(null); + const [isSaving, setIsSaving] = useState(false); + + useEffect(() => { + const fetchData = async () => { + console.log("EditMobility: Fetching data from API..."); + try { + const response = await fetch("/mobility/api/insert_mobility"); + console.log("EditMobility: API response status:", response.status); + + if (!response.ok) { + throw new Error(`Error fetching data: ${response.statusText}`); + } + + const result = await response.json(); + console.log("EditMobility: Data fetched successfully:", result); + setData(result); + } catch (err) { + console.error("EditMobility: Error fetching data:", err); + setError("Failed to load mobility data. Please try again later."); + } + }; + + fetchData(); + }, []); + + const handleChange = ( + studentId: string, + field: keyof Mobility, + value: string | number | null, + ) => { + if (!data) return; + + setData((prevData) => { + if (!prevData) return null; + + const updatedMobilities = prevData.mobilities?.map((mobility) => { + if (mobility.studentId === studentId) { + const updatedMobility = { ...mobility, [field]: value }; + + if (field === "startDate" || field === "endDate") { + const startDate = new Date(updatedMobility.startDate || ""); + const endDate = new Date(updatedMobility.endDate || ""); + if (startDate && endDate && startDate <= endDate) { + const weeks = Math.ceil( + (endDate.getTime() - startDate.getTime()) / + (7 * 24 * 60 * 60 * 1000), + ); + updatedMobility.weeksCount = weeks; + } else { + updatedMobility.weeksCount = null; + } + } + + return updatedMobility; + } + return mobility; + }) || []; + + return { ...prevData, mobilities: updatedMobilities }; + }); + }; + + const handleSave = async () => { + setIsSaving(true); + + try { + const response = await fetch("/mobility/api/insert_mobility", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ data: data?.mobilities }), + }); + + console.log("EditMobility: Save response status:", response.status); + + if (response.ok) { + alert("Data saved successfully!"); + globalThis.location.reload(); + } else { + throw new Error(`Failed to save data: ${response.statusText}`); + } + } catch (error) { + console.error("EditMobility: Error saving data:", error); + alert("An error occurred while saving data."); + } finally { + setIsSaving(false); + } + }; + + if (error) { + return

{error}

; + } + + if (!data?.promotions) { + return

Loading data...

; + } + + return ( +
+

Edit Mobility

+ {data.promotions.map((promo) => ( +
+

Promotion: {promo.name}

+ + + + + + + + + + + + + + + + {data.students + ?.filter((student) => student.promotionId === promo.id) + .map((student) => { + const mobility = data.mobilities?.find((mob) => + mob.studentId === student.id + ) || { + id: null, + studentId: student.id, + startDate: null, + endDate: null, + weeksCount: null, + destinationCountry: null, + destinationName: null, + mobilityStatus: "N/A", + }; + + return ( + + + + + + + + + + + + ); + })} + +
IDFirst NameLast NameStart DateEnd DateWeeks CountDestination CountryDestination NameStatus
{student.id}{student.firstName}{student.lastName} + + handleChange( + student.id, + "startDate", + e.target.value, + )} + /> + + + handleChange(student.id, "endDate", e.target.value)} + /> + {mobility.weeksCount ?? "N/A"} + + handleChange( + student.id, + "destinationCountry", + e.target.value, + )} + /> + + + handleChange( + student.id, + "destinationName", + e.target.value, + )} + /> + + +
+
+ ))} + +
+ ); +} diff --git a/routes/(apps)/admin/(_islands)/ImportFile.tsx b/routes/(apps)/admin/(_islands)/ImportFile.tsx new file mode 100644 index 0000000..e69de29 diff --git a/routes/(apps)/admin/(_props)/props.ts b/routes/(apps)/admin/(_props)/props.ts new file mode 100644 index 0000000..75a91b4 --- /dev/null +++ b/routes/(apps)/admin/(_props)/props.ts @@ -0,0 +1,16 @@ +import { AppProperties } from "$root/defaults/interfaces.ts"; + +const properties: AppProperties = { + name: "PolyMobility", + icon: "flight_takeoff", + hint: "Student mobility management", + pages: { + index: "Homepage", + overview: "Mobility overview", + edit_mobility: "Mobility management", + consult_students_test: "Test consult students", + }, + adminOnly: ["edit_mobility", "consult_students_test"], +}; + +export default properties; diff --git a/routes/(apps)/admin/api/enseignement.ts b/routes/(apps)/admin/api/enseignement.ts new file mode 100644 index 0000000..1e0771f --- /dev/null +++ b/routes/(apps)/admin/api/enseignement.ts @@ -0,0 +1,18 @@ +import { Handlers } from "$fresh/server.ts"; +import { db } from "$root/databases/db.ts"; +import { mobility, promotions, students,ens } from "$root/databases/schema.ts"; +import { eq } from "npm:drizzle-orm"; + +export const handler: Handlers = { + async GET() { + try { + // recup les enseigne + + const rows = await + db.select().from(students) + + // en faire des json + // les retouner + return { students: rows as Student[], promos }; + } + } diff --git a/routes/(apps)/admin/api/insert_mobility.ts b/routes/(apps)/admin/api/insert_mobility.ts new file mode 100644 index 0000000..0452d26 --- /dev/null +++ b/routes/(apps)/admin/api/insert_mobility.ts @@ -0,0 +1,116 @@ +import { Handlers } from "$fresh/server.ts"; +import { db } from "$root/databases/db.ts"; +import { mobility, promotions, students } from "$root/databases/schema.ts"; +import { eq } from "npm:drizzle-orm"; + +export const handler: Handlers = { + async GET() { + try { + const studentRows = await db + .select({ + id: students.userId, + firstName: students.firstName, + lastName: students.lastName, + promotionId: students.promotionId, + endyear: promotions.endyear, + current: promotions.current, + }) + .from(students) + .leftJoin(promotions, eq(students.promotionId, promotions.id)); + + const mobilityRows = await db.select().from(mobility); + + const promotionRows = await db + .select({ id: promotions.id, endyear: promotions.endyear, current: promotions.current }) + .from(promotions); + + return new Response( + JSON.stringify({ + mobilities: mobilityRows, + students: studentRows, + promotions: promotionRows, + }), + { status: 200, headers: { "Content-Type": "application/json" } }, + ); + } catch (error) { + console.error("Error fetching mobility data:", error); + return new Response("Failed to fetch data", { status: 500 }); + } + }, + + async POST(request) { + try { + const body = await request.json(); + const { data } = body; + + if (!Array.isArray(data)) { + throw new Error("Invalid request body"); + } + + for (const entry of data) { + const { + id, + studentId, + startDate, + endDate, + weeksCount, + destinationCountry, + destinationName, + mobilityStatus = "N/A", + } = entry; + + const studentExists = await db + .select({ userId: students.userId }) + .from(students) + .where(eq(students.userId, studentId)) + .limit(1) + .then((rows) => rows.length > 0); + + if (!studentExists) { + console.warn(`Skipping mobility for unknown studentId: ${studentId}`); + continue; + } + + let calculatedWeeksCount = weeksCount; + if (startDate && endDate) { + const start = new Date(startDate); + const end = new Date(endDate); + calculatedWeeksCount = start <= end + ? Math.ceil( + (end.getTime() - start.getTime()) / (7 * 24 * 60 * 60 * 1000), + ) + : null; + } + + await db + .insert(mobility) + .values({ + id, + studentId, + startDate, + endDate, + weeksCount: calculatedWeeksCount, + destinationCountry, + destinationName, + mobilityStatus, + }) + .onConflictDoUpdate({ + target: mobility.id, + set: { + startDate, + endDate, + weeksCount: calculatedWeeksCount, + destinationCountry, + destinationName, + mobilityStatus, + }, + }); + } + + return new Response("Data inserted/updated successfully", { status: 200 }); + } catch (error) { + console.error("Error inserting mobility data:", error); + return new Response("Failed to insert/update data", { status: 500 }); + } + }, +}; diff --git a/routes/(apps)/admin/index.tsx b/routes/(apps)/admin/index.tsx new file mode 100644 index 0000000..1d82f7f --- /dev/null +++ b/routes/(apps)/admin/index.tsx @@ -0,0 +1,2 @@ +import makeIndex from "$root/defaults/makeIndex.ts"; +export default makeIndex(import.meta.dirname!); diff --git a/routes/(apps)/admin/partials/(admin)/consult_students_test.tsx b/routes/(apps)/admin/partials/(admin)/consult_students_test.tsx new file mode 100644 index 0000000..8727e5d --- /dev/null +++ b/routes/(apps)/admin/partials/(admin)/consult_students_test.tsx @@ -0,0 +1,21 @@ +import ConsultStudents_test from "$root/routes/(apps)/mobility/(_islands)/ConsultStudents_test.tsx"; +import { + getPartialsConfig, + makePartials, +} from "$root/defaults/makePartials.tsx"; +import { FreshContext } from "$fresh/server.ts"; +import { State } from "$root/routes/_middleware.ts"; +//import EditStudents from "../(_islands)/EditStudents.tsx"; + +// deno-lint-ignore require-await +async function Mobility(_request: Request, _context: FreshContext) { + return ( + <> +

Test consult students

+ + + ); +} + +export const config = getPartialsConfig(); +export default makePartials(Mobility); diff --git a/routes/(apps)/admin/partials/(admin)/edit_mobility.tsx b/routes/(apps)/admin/partials/(admin)/edit_mobility.tsx new file mode 100644 index 0000000..88e75bf --- /dev/null +++ b/routes/(apps)/admin/partials/(admin)/edit_mobility.tsx @@ -0,0 +1,20 @@ +import EditMobility from "$root/routes/(apps)/mobility/(_islands)/EditMobility.tsx"; +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 +async function Mobility(_request: Request, _context: FreshContext) { + return ( + <> +

Edit mobility

+ + + ); +} + +export const config = getPartialsConfig(); +export default makePartials(Mobility); diff --git a/routes/(apps)/admin/partials/index.tsx b/routes/(apps)/admin/partials/index.tsx new file mode 100644 index 0000000..2971e0e --- /dev/null +++ b/routes/(apps)/admin/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/(apps)/admin/partials/overview.tsx b/routes/(apps)/admin/partials/overview.tsx new file mode 100644 index 0000000..c8775a6 --- /dev/null +++ b/routes/(apps)/admin/partials/overview.tsx @@ -0,0 +1,20 @@ +import ConsultMobility from "$root/routes/(apps)/mobility/(_islands)/ConsultMobility.tsx"; +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 +async function Mobility(_request: Request, _context: FreshContext) { + return ( + <> +

Edit mobility

+ + + ); +} + +export const config = getPartialsConfig(); +export default makePartials(Mobility);