feat(app): add studentOnly pages and new routes
Add routes for modules, users, notes import, recap, and islands edit. Update middleware to filter pages based on user role. feat(admin): add modal for assigning teaching, replace delete icon with SVG refactor(server): rename port variable to uppercase and add env support feat(admin): add enseignants, users, filtering and role colors refactor(AdminRoles): improve role UI and add permission mapping feat(admin-users): add role colors, role filter, and modal for creating users feat(admin): add EditModule component for module editing feat(admin): add EditUser page for editing users and managing enseignements feat(promo-select): display id and name in options for promo dropdown feat: add edit module/user routes, inline coeff editing, UI tweaks refactor: UI – icons, modal overlay, grid, subtitles, import margin
This commit is contained in:
@@ -130,7 +130,17 @@ export default function AdminConsultNotes() {
|
||||
href={`/notes/edition/${s.numEtud}`}
|
||||
f-client-nav={false}
|
||||
>
|
||||
✏ édit
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
|
||||
</svg>{" "}
|
||||
édit
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -31,6 +31,10 @@ export default function AdminUEs() {
|
||||
const [adding, setAdding] = useState(false);
|
||||
const [addError, setAddError] = useState<string | null>(null);
|
||||
|
||||
// Inline coeff editing
|
||||
const [editingCoeff, setEditingCoeff] = useState<string | null>(null);
|
||||
const [editCoeffValue, setEditCoeffValue] = useState("");
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const [uRes, umRes, mRes, pRes] = await Promise.all([
|
||||
@@ -137,6 +141,32 @@ export default function AdminUEs() {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCoeff(
|
||||
idModule: string,
|
||||
idUE: number,
|
||||
idPromo: string,
|
||||
coeff: number,
|
||||
) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/notes/api/ue-modules/${encodeURIComponent(idModule)}/${idUE}/${
|
||||
encodeURIComponent(idPromo)
|
||||
}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ coeff }),
|
||||
},
|
||||
);
|
||||
if (!res.ok) throw new Error("Modification échouée");
|
||||
await load();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : "Erreur");
|
||||
} finally {
|
||||
setEditingCoeff(null);
|
||||
}
|
||||
}
|
||||
|
||||
const moduleMap = Object.fromEntries(modules.map((m) => [m.id, m]));
|
||||
|
||||
const selectedUeModules = selectedUe
|
||||
@@ -249,7 +279,59 @@ export default function AdminUEs() {
|
||||
<td>
|
||||
<span class="promo-chip">{um.idPromo}</span>
|
||||
</td>
|
||||
<td>{um.coeff}</td>
|
||||
<td
|
||||
onClick={() => {
|
||||
const key =
|
||||
`${um.idModule}-${um.idUE}-${um.idPromo}`;
|
||||
setEditingCoeff(key);
|
||||
setEditCoeffValue(String(um.coeff));
|
||||
}}
|
||||
style="cursor: pointer"
|
||||
>
|
||||
{editingCoeff ===
|
||||
`${um.idModule}-${um.idUE}-${um.idPromo}`
|
||||
? (
|
||||
<input
|
||||
type="number"
|
||||
class="form-input"
|
||||
value={editCoeffValue}
|
||||
min="0.1"
|
||||
step="0.5"
|
||||
style="width: 5rem; padding: 0.2rem 0.4rem; font-size: 0.82rem"
|
||||
autoFocus
|
||||
onInput={(e) =>
|
||||
setEditCoeffValue(
|
||||
(e.target as HTMLInputElement)
|
||||
.value,
|
||||
)}
|
||||
onBlur={() => {
|
||||
const v = parseFloat(
|
||||
editCoeffValue,
|
||||
);
|
||||
if (!isNaN(v) && v > 0) {
|
||||
updateCoeff(
|
||||
um.idModule,
|
||||
um.idUE,
|
||||
um.idPromo,
|
||||
v,
|
||||
);
|
||||
} else {
|
||||
setEditingCoeff(null);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
(e.target as HTMLInputElement)
|
||||
.blur();
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
setEditingCoeff(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
: um.coeff}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
@@ -261,7 +343,24 @@ export default function AdminUEs() {
|
||||
um.idPromo,
|
||||
)}
|
||||
>
|
||||
🗑
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M3 6h18" />
|
||||
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
||||
<rect
|
||||
x="5"
|
||||
y="6"
|
||||
width="14"
|
||||
height="16"
|
||||
rx="1"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -326,7 +326,17 @@ export default function NoteRecap({ numEtud }: Props) {
|
||||
: "",
|
||||
})}
|
||||
>
|
||||
✏ note
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
|
||||
</svg>{" "}
|
||||
note
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ const properties: AppProperties = {
|
||||
import: "Import xlsx",
|
||||
},
|
||||
adminOnly: ["courses", "ues", "import"],
|
||||
studentOnly: ["notes"],
|
||||
hint: "Student grading management",
|
||||
};
|
||||
|
||||
|
||||
@@ -14,12 +14,6 @@ async function ImportNotesPage(
|
||||
return (
|
||||
<div class="page-content">
|
||||
<h2 class="page-title">Importer des Notes</h2>
|
||||
<p
|
||||
class="upload-format"
|
||||
style="margin-bottom: 1.25rem"
|
||||
>
|
||||
POST /notes/api/notes
|
||||
</p>
|
||||
<ImportNotes />
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user