diff --git a/databases/migrations/0003_add_session2_and_malus.sql b/databases/migrations/0003_add_session2_and_malus.sql
new file mode 100644
index 0000000..d3a950b
--- /dev/null
+++ b/databases/migrations/0003_add_session2_and_malus.sql
@@ -0,0 +1,3 @@
+ALTER TABLE "notes" ADD COLUMN "noteSession2" double precision;
+--> statement-breakpoint
+ALTER TABLE "ajustements" ADD COLUMN "malus" integer NOT NULL DEFAULT 0;
diff --git a/databases/migrations/meta/_journal.json b/databases/migrations/meta/_journal.json
index e4f070f..f81c27d 100644
--- a/databases/migrations/meta/_journal.json
+++ b/databases/migrations/meta/_journal.json
@@ -22,6 +22,13 @@
"when": 1777155028710,
"tag": "0002_update_permission_names",
"breakpoints": true
+ },
+ {
+ "idx": 3,
+ "version": "7",
+ "when": 1777155028711,
+ "tag": "0003_add_session2_and_malus",
+ "breakpoints": true
}
]
}
diff --git a/databases/schema.ts b/databases/schema.ts
index 823c7a2..9bf678d 100644
--- a/databases/schema.ts
+++ b/databases/schema.ts
@@ -75,6 +75,7 @@ export const notes = pgTable("notes", {
numEtud: integer("numEtud").notNull().references(() => students.numEtud),
idModule: text("idModule").notNull().references(() => modules.id),
note: doublePrecision("note").notNull(),
+ noteSession2: doublePrecision("noteSession2"),
}, (t) => ({
pk: primaryKey({ columns: [t.numEtud, t.idModule] }),
}));
@@ -83,6 +84,7 @@ export const ajustements = pgTable("ajustements", {
numEtud: integer("numEtud").notNull().references(() => students.numEtud),
idUE: integer("idUE").notNull().references(() => ues.id),
valeur: doublePrecision("valeur").notNull(),
+ malus: integer("malus").notNull().default(0),
}, (t) => ({
pk: primaryKey({ columns: [t.numEtud, t.idUE] }),
}));
diff --git a/defaults/ImportResultPopup.tsx b/defaults/ImportResultPopup.tsx
new file mode 100644
index 0000000..075db00
--- /dev/null
+++ b/defaults/ImportResultPopup.tsx
@@ -0,0 +1,102 @@
+import { useState } from "preact/hooks";
+
+export type ImportResult = {
+ added: number;
+ modified: number;
+ ignored: number;
+ errors: number;
+ details: ImportDetail[];
+};
+
+export type ImportDetail = {
+ type: "change" | "error";
+ message: string;
+};
+
+type Props = {
+ result: ImportResult;
+ onClose: () => void;
+};
+
+export default function ImportResultPopup({ result, onClose }: Props) {
+ const [showDetails, setShowDetails] = useState(false);
+ const hasErrors = result.errors > 0;
+ const changes = result.details.filter((d) => d.type === "change");
+ const errors = result.details.filter((d) => d.type === "error");
+
+ return (
+
+ );
+}
diff --git a/fresh.gen.ts b/fresh.gen.ts
index ffa7923..bd47e97 100644
--- a/fresh.gen.ts
+++ b/fresh.gen.ts
@@ -12,15 +12,22 @@ import * as $_apps_admin_api_modules_idModule_ from "./routes/(apps)/admin/api/m
import * as $_apps_admin_api_permissions from "./routes/(apps)/admin/api/permissions.ts";
import * as $_apps_admin_api_roles from "./routes/(apps)/admin/api/roles.ts";
import * as $_apps_admin_api_roles_idRole_ from "./routes/(apps)/admin/api/roles/[idRole].ts";
+import * as $_apps_admin_api_ue_modules from "./routes/(apps)/admin/api/ue-modules.ts";
+import * as $_apps_admin_api_ue_modules_idModule_idUE_idPromo_ from "./routes/(apps)/admin/api/ue-modules/[idModule]/[idUE]/[idPromo].ts";
+import * as $_apps_admin_api_ues from "./routes/(apps)/admin/api/ues.ts";
+import * as $_apps_admin_api_ues_idUE_ from "./routes/(apps)/admin/api/ues/[idUE].ts";
import * as $_apps_admin_api_users from "./routes/(apps)/admin/api/users.ts";
import * as $_apps_admin_api_users_id_ from "./routes/(apps)/admin/api/users/[id].ts";
import * as $_apps_admin_index from "./routes/(apps)/admin/index.tsx";
import * as $_apps_admin_modules_idModule_ from "./routes/(apps)/admin/modules/[idModule].tsx";
import * as $_apps_admin_partials_enseignements from "./routes/(apps)/admin/partials/enseignements.tsx";
+import * as $_apps_admin_partials_import_maquette from "./routes/(apps)/admin/partials/import-maquette.tsx";
import * as $_apps_admin_partials_index from "./routes/(apps)/admin/partials/index.tsx";
import * as $_apps_admin_partials_modules from "./routes/(apps)/admin/partials/modules.tsx";
import * as $_apps_admin_partials_permissions from "./routes/(apps)/admin/partials/permissions.tsx";
+import * as $_apps_admin_partials_promotions from "./routes/(apps)/admin/partials/promotions.tsx";
import * as $_apps_admin_partials_roles from "./routes/(apps)/admin/partials/roles.tsx";
+import * as $_apps_admin_partials_ues from "./routes/(apps)/admin/partials/ues.tsx";
import * as $_apps_admin_partials_users from "./routes/(apps)/admin/partials/users.tsx";
import * as $_apps_admin_users_id_ from "./routes/(apps)/admin/users/[id].tsx";
import * as $_apps_mobility_api_insert_mobility from "./routes/(apps)/mobility/api/insert_mobility.ts";
@@ -33,15 +40,10 @@ import * as $_apps_notes_api_ajustements_numEtud_idUE_ from "./routes/(apps)/not
import * as $_apps_notes_api_notes from "./routes/(apps)/notes/api/notes.ts";
import * as $_apps_notes_api_notes_numEtud_idModule_ from "./routes/(apps)/notes/api/notes/[numEtud]/[idModule].ts";
import * as $_apps_notes_api_notes_import_xlsx from "./routes/(apps)/notes/api/notes/import-xlsx.ts";
-import * as $_apps_notes_api_ue_modules from "./routes/(apps)/notes/api/ue-modules.ts";
-import * as $_apps_notes_api_ue_modules_idModule_idUE_idPromo_ from "./routes/(apps)/notes/api/ue-modules/[idModule]/[idUE]/[idPromo].ts";
-import * as $_apps_notes_api_ues from "./routes/(apps)/notes/api/ues.ts";
-import * as $_apps_notes_api_ues_idUE_ from "./routes/(apps)/notes/api/ues/[idUE].ts";
import * as $_apps_notes_edition_numEtud_ from "./routes/(apps)/notes/edition/[numEtud].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_import from "./routes/(apps)/notes/partials/(admin)/import.tsx";
-import * as $_apps_notes_partials_admin_ues from "./routes/(apps)/notes/partials/(admin)/ues.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_notes_recap_numEtud_ from "./routes/(apps)/notes/recap/[numEtud].tsx";
@@ -53,7 +55,6 @@ import * as $_apps_students_api_students_import_csv from "./routes/(apps)/studen
import * as $_apps_students_edit_numEtud_ from "./routes/(apps)/students/edit/[numEtud].tsx";
import * as $_apps_students_index from "./routes/(apps)/students/index.tsx";
import * as $_apps_students_partials_admin_consult from "./routes/(apps)/students/partials/(admin)/consult.tsx";
-import * as $_apps_students_partials_admin_promotions from "./routes/(apps)/students/partials/(admin)/promotions.tsx";
import * as $_apps_students_partials_admin_upload from "./routes/(apps)/students/partials/(admin)/upload.tsx";
import * as $_apps_students_partials_index from "./routes/(apps)/students/partials/index.tsx";
import * as $_apps_students_types_d from "./routes/(apps)/students/types.d.ts";
@@ -71,19 +72,20 @@ import * as $_islands_Navbar from "./routes/(_islands)/Navbar.tsx";
import * as $_apps_admin_islands_AdminEnseignements from "./routes/(apps)/admin/(_islands)/AdminEnseignements.tsx";
import * as $_apps_admin_islands_AdminModules from "./routes/(apps)/admin/(_islands)/AdminModules.tsx";
import * as $_apps_admin_islands_AdminPermissions from "./routes/(apps)/admin/(_islands)/AdminPermissions.tsx";
+import * as $_apps_admin_islands_AdminPromotions from "./routes/(apps)/admin/(_islands)/AdminPromotions.tsx";
import * as $_apps_admin_islands_AdminRoles from "./routes/(apps)/admin/(_islands)/AdminRoles.tsx";
+import * as $_apps_admin_islands_AdminUEs from "./routes/(apps)/admin/(_islands)/AdminUEs.tsx";
import * as $_apps_admin_islands_AdminUsers from "./routes/(apps)/admin/(_islands)/AdminUsers.tsx";
import * as $_apps_admin_islands_EditModule from "./routes/(apps)/admin/(_islands)/EditModule.tsx";
import * as $_apps_admin_islands_EditUser from "./routes/(apps)/admin/(_islands)/EditUser.tsx";
+import * as $_apps_admin_islands_ImportMaquette from "./routes/(apps)/admin/(_islands)/ImportMaquette.tsx";
import * as $_apps_mobility_islands_ConsultMobility from "./routes/(apps)/mobility/(_islands)/ConsultMobility.tsx";
import * as $_apps_mobility_islands_EditMobility from "./routes/(apps)/mobility/(_islands)/EditMobility.tsx";
import * as $_apps_mobility_islands_ImportFile from "./routes/(apps)/mobility/(_islands)/ImportFile.tsx";
import * as $_apps_notes_islands_AdminConsultNotes from "./routes/(apps)/notes/(_islands)/AdminConsultNotes.tsx";
-import * as $_apps_notes_islands_AdminUEs from "./routes/(apps)/notes/(_islands)/AdminUEs.tsx";
import * as $_apps_notes_islands_ImportNotes from "./routes/(apps)/notes/(_islands)/ImportNotes.tsx";
import * as $_apps_notes_islands_NoteRecap from "./routes/(apps)/notes/(_islands)/NoteRecap.tsx";
import * as $_apps_notes_islands_NotesView from "./routes/(apps)/notes/(_islands)/NotesView.tsx";
-import * as $_apps_students_islands_AdminPromotions from "./routes/(apps)/students/(_islands)/AdminPromotions.tsx";
import * as $_apps_students_islands_ConsultStudents from "./routes/(apps)/students/(_islands)/ConsultStudents.tsx";
import * as $_apps_students_islands_EditStudents from "./routes/(apps)/students/(_islands)/EditStudents.tsx";
import * as $_apps_students_islands_UploadStudents from "./routes/(apps)/students/(_islands)/UploadStudents.tsx";
@@ -105,6 +107,11 @@ const manifest = {
"./routes/(apps)/admin/api/roles.ts": $_apps_admin_api_roles,
"./routes/(apps)/admin/api/roles/[idRole].ts":
$_apps_admin_api_roles_idRole_,
+ "./routes/(apps)/admin/api/ue-modules.ts": $_apps_admin_api_ue_modules,
+ "./routes/(apps)/admin/api/ue-modules/[idModule]/[idUE]/[idPromo].ts":
+ $_apps_admin_api_ue_modules_idModule_idUE_idPromo_,
+ "./routes/(apps)/admin/api/ues.ts": $_apps_admin_api_ues,
+ "./routes/(apps)/admin/api/ues/[idUE].ts": $_apps_admin_api_ues_idUE_,
"./routes/(apps)/admin/api/users.ts": $_apps_admin_api_users,
"./routes/(apps)/admin/api/users/[id].ts": $_apps_admin_api_users_id_,
"./routes/(apps)/admin/index.tsx": $_apps_admin_index,
@@ -112,11 +119,16 @@ const manifest = {
$_apps_admin_modules_idModule_,
"./routes/(apps)/admin/partials/enseignements.tsx":
$_apps_admin_partials_enseignements,
+ "./routes/(apps)/admin/partials/import-maquette.tsx":
+ $_apps_admin_partials_import_maquette,
"./routes/(apps)/admin/partials/index.tsx": $_apps_admin_partials_index,
"./routes/(apps)/admin/partials/modules.tsx": $_apps_admin_partials_modules,
"./routes/(apps)/admin/partials/permissions.tsx":
$_apps_admin_partials_permissions,
+ "./routes/(apps)/admin/partials/promotions.tsx":
+ $_apps_admin_partials_promotions,
"./routes/(apps)/admin/partials/roles.tsx": $_apps_admin_partials_roles,
+ "./routes/(apps)/admin/partials/ues.tsx": $_apps_admin_partials_ues,
"./routes/(apps)/admin/partials/users.tsx": $_apps_admin_partials_users,
"./routes/(apps)/admin/users/[id].tsx": $_apps_admin_users_id_,
"./routes/(apps)/mobility/api/insert_mobility.ts":
@@ -136,11 +148,6 @@ const manifest = {
$_apps_notes_api_notes_numEtud_idModule_,
"./routes/(apps)/notes/api/notes/import-xlsx.ts":
$_apps_notes_api_notes_import_xlsx,
- "./routes/(apps)/notes/api/ue-modules.ts": $_apps_notes_api_ue_modules,
- "./routes/(apps)/notes/api/ue-modules/[idModule]/[idUE]/[idPromo].ts":
- $_apps_notes_api_ue_modules_idModule_idUE_idPromo_,
- "./routes/(apps)/notes/api/ues.ts": $_apps_notes_api_ues,
- "./routes/(apps)/notes/api/ues/[idUE].ts": $_apps_notes_api_ues_idUE_,
"./routes/(apps)/notes/edition/[numEtud].tsx":
$_apps_notes_edition_numEtud_,
"./routes/(apps)/notes/index.tsx": $_apps_notes_index,
@@ -148,8 +155,6 @@ const manifest = {
$_apps_notes_partials_admin_courses,
"./routes/(apps)/notes/partials/(admin)/import.tsx":
$_apps_notes_partials_admin_import,
- "./routes/(apps)/notes/partials/(admin)/ues.tsx":
- $_apps_notes_partials_admin_ues,
"./routes/(apps)/notes/partials/index.tsx": $_apps_notes_partials_index,
"./routes/(apps)/notes/partials/notes.tsx": $_apps_notes_partials_notes,
"./routes/(apps)/notes/recap/[numEtud].tsx": $_apps_notes_recap_numEtud_,
@@ -167,8 +172,6 @@ const manifest = {
"./routes/(apps)/students/index.tsx": $_apps_students_index,
"./routes/(apps)/students/partials/(admin)/consult.tsx":
$_apps_students_partials_admin_consult,
- "./routes/(apps)/students/partials/(admin)/promotions.tsx":
- $_apps_students_partials_admin_promotions,
"./routes/(apps)/students/partials/(admin)/upload.tsx":
$_apps_students_partials_admin_upload,
"./routes/(apps)/students/partials/index.tsx":
@@ -193,14 +196,20 @@ const manifest = {
$_apps_admin_islands_AdminModules,
"./routes/(apps)/admin/(_islands)/AdminPermissions.tsx":
$_apps_admin_islands_AdminPermissions,
+ "./routes/(apps)/admin/(_islands)/AdminPromotions.tsx":
+ $_apps_admin_islands_AdminPromotions,
"./routes/(apps)/admin/(_islands)/AdminRoles.tsx":
$_apps_admin_islands_AdminRoles,
+ "./routes/(apps)/admin/(_islands)/AdminUEs.tsx":
+ $_apps_admin_islands_AdminUEs,
"./routes/(apps)/admin/(_islands)/AdminUsers.tsx":
$_apps_admin_islands_AdminUsers,
"./routes/(apps)/admin/(_islands)/EditModule.tsx":
$_apps_admin_islands_EditModule,
"./routes/(apps)/admin/(_islands)/EditUser.tsx":
$_apps_admin_islands_EditUser,
+ "./routes/(apps)/admin/(_islands)/ImportMaquette.tsx":
+ $_apps_admin_islands_ImportMaquette,
"./routes/(apps)/mobility/(_islands)/ConsultMobility.tsx":
$_apps_mobility_islands_ConsultMobility,
"./routes/(apps)/mobility/(_islands)/EditMobility.tsx":
@@ -209,16 +218,12 @@ const manifest = {
$_apps_mobility_islands_ImportFile,
"./routes/(apps)/notes/(_islands)/AdminConsultNotes.tsx":
$_apps_notes_islands_AdminConsultNotes,
- "./routes/(apps)/notes/(_islands)/AdminUEs.tsx":
- $_apps_notes_islands_AdminUEs,
"./routes/(apps)/notes/(_islands)/ImportNotes.tsx":
$_apps_notes_islands_ImportNotes,
"./routes/(apps)/notes/(_islands)/NoteRecap.tsx":
$_apps_notes_islands_NoteRecap,
"./routes/(apps)/notes/(_islands)/NotesView.tsx":
$_apps_notes_islands_NotesView,
- "./routes/(apps)/students/(_islands)/AdminPromotions.tsx":
- $_apps_students_islands_AdminPromotions,
"./routes/(apps)/students/(_islands)/ConsultStudents.tsx":
$_apps_students_islands_ConsultStudents,
"./routes/(apps)/students/(_islands)/EditStudents.tsx":
diff --git a/routes/(apps)/_middleware.ts b/routes/(apps)/_middleware.ts
index e60886b..ece0de4 100644
--- a/routes/(apps)/_middleware.ts
+++ b/routes/(apps)/_middleware.ts
@@ -21,16 +21,20 @@ export const handler: MiddlewareHandler[] = [
`./${currentApp}/(_props)/props.ts`
)).default;
- context.state.availablePages = properties.pages;
+ context.state.availablePages = { ...properties.pages };
const isStudent =
- context.state.session.eduPersonPrimaryAffiliation == "student" &&
- Deno.env.get("LOCAL") != "true";
+ context.state.session.eduPersonPrimaryAffiliation === "student";
+ const isLocal = Deno.env.get("LOCAL") === "true";
if (isStudent) {
+ // Students only see studentOnly pages (+ non-restricted pages)
properties.adminOnly.forEach((page) =>
delete context.state.availablePages[page]
);
+ } else if (isLocal) {
+ // In local mode, employees see all pages (admin + student)
} else {
+ // In prod, employees don't see studentOnly pages
properties.studentOnly?.forEach((page) =>
delete context.state.availablePages[page]
);
diff --git a/routes/(apps)/students/(_islands)/AdminPromotions.tsx b/routes/(apps)/admin/(_islands)/AdminPromotions.tsx
similarity index 92%
rename from routes/(apps)/students/(_islands)/AdminPromotions.tsx
rename to routes/(apps)/admin/(_islands)/AdminPromotions.tsx
index 7f32f91..68c71c6 100644
--- a/routes/(apps)/students/(_islands)/AdminPromotions.tsx
+++ b/routes/(apps)/admin/(_islands)/AdminPromotions.tsx
@@ -74,13 +74,26 @@ export default function AdminPromotions() {
}
async function deletePromo(id: string) {
- if (!confirm(`Supprimer la promotion ${id} ?`)) return;
+ if (studentCount(id) > 0) {
+ setError(
+ `Impossible de supprimer ${id} : des étudiants y sont encore assignés. Réassignez-les d'abord.`,
+ );
+ return;
+ }
+ if (
+ !confirm(`Supprimer la promotion ${id} et toutes ses données liées ?`)
+ ) {
+ return;
+ }
try {
const res = await fetch(
`/students/api/promotions/${encodeURIComponent(id)}`,
{ method: "DELETE" },
);
- if (!res.ok) throw new Error("Suppression échouée");
+ if (!res.ok) {
+ const body = await res.json().catch(() => ({}));
+ throw new Error(body.error ?? "Suppression échouée");
+ }
await load();
} catch (e) {
setError(e instanceof Error ? e.message : "Erreur");
@@ -218,6 +231,10 @@ export default function AdminPromotions() {