Componenti UI — dominio edu
🎯 Cosa fa
Sotto TrainingHub.BackOffice/Components/CRUD/edu/ vivono i
componenti Blazor CRUD auto-generati per le 64 entità del dominio
formazione. Il pattern di generazione è lo stesso di inv e reg
(triade razor + razor.tt.cs + razor.cs + Forms/ + FormPopups/). Vedi
componenti UI inv per i dettagli.
Questa pagina documenta le particolarità edu-specifiche.
🗺️ Entità core in scope
| Entità | File | Note |
|---|---|---|
Category | Category.razor | Anagrafica semplice |
Course | Course.razor | Form ricco con cascade variante normativa |
TrainingSession | TrainingSession.razor | Sessione formativa (ciclo vita stati) |
TrainingSessionsCourse | TrainingSessionsCourse.razor | M:N session ↔ corso (co-erogazione) |
Appointment | Appointment.razor (grid CRUD) + Pages/AppointmentsCalendar/ (vista calendario page-level) | Doppia vista griglia + calendario |
AppointmentsCourse | AppointmentsCourse.razor | Cella della matrice "programma" (appointment × corso) |
AppointmentsCoursesArgument | AppointmentsCoursesArgument.razor | Argomenti trattati per slot × corso |
Location | Location.razor | Aule con indirizzo inline + FK opzionale a reg.companies (gestore aula) |
Teacher | Teacher.razor | Anagrafica docente |
AppointmentsTeacher | AppointmentsTeacher.razor | M:N con unitAmount + costMode |
TeacherCost | TeacherCost.razor | Costi per organizer |
TeacherSkill | TeacherSkill.razor | Competenze docente (skill matrix ↔ variant) |
🧩 Pattern chiave edu-specifici
Vista calendario appuntamenti
Pages/AppointmentsCalendar/AppointmentsCalendar.razor (route
/appointments-calendar) è la vista calendario principale degli
appuntamenti, complementare alla griglia standard Appointment.razor.
Scritta a mano (non CRUD-generata). Sub-componenti:
CalendarGrid.razor— rendering mese/settimana/giorno.AppointmentDetailPanel.razor— pannello laterale con dettagli appuntamento + contesto sessione + azioni Edit/Duplica/Modifica sessione.
Caratteristiche principali (per dettaglio vedi panoramica edu — Vista calendario custom):
- Filtri Corso/Sede/Docente/Sessione con cascade Corso → Sessione.
- Colore appuntamento derivato da
trainingTopics.color(primo topic alfabetico della session). - Click slot apre
AppointmentFormPopup; pulsante "Nuova Sessione" apreSessionPlannerPopup. - Conflict detection via
ISessionPlannerService.DetectConflictsAsynccon toast warning post-save. - Query param
?teacherId=<guid>per landing dal teacher-calendar.
Wizard pianificazione sessione
Components/edu/SessionPlanner/SessionPlannerPopup.razor è un
wizard a 8 step per pianificare una sessione formativa
end-to-end. Le partial class
SessionPlannerPopup.razor.Step1Session.cs …
SessionPlannerPopup.razor.Step6Review.cs (più
SessionPlannerPopup.razor.Step4Program.cs e
SessionPlannerPopup.razor.Delete.cs) contengono la logica per-step.
Nella stessa cartella vivono anche i componenti di contorno, non
CRUD-generati: ConflictBanner.razor, DuplicateSessionPopup.razor,
SessionDocumentsMatrix.razor, ArgumentsCatalogMultiSelectPopup.razor,
ArgumentsCopyFromSlotPopup.razor, StaticTemplatePickerPopup.razor,
SessionSummaryView.razor.
Il backend è ISessionPlannerService (in Shared/Services/SessionPlanner/)
con stato persistito su SessionPlannerState + SessionCoursePlan[].
Step:
- Sessione — data inizio + argomento opzionale (filtra corsi).
- Corsi — aggiunge uno o più corsi in co-erogazione.
⚠️ Le finestre orarie non stanno qui:
trainingSessionsCoursesnon ha colonne orarie. Il per-slot si imposta nello step Programma, suappointmentsCourses.startDateTime/endDateTime. - Date e sedi — appuntamenti fisici e sede.
- Programma — matrice appuntamento × corso
(
TrainingSessionProgramMatrix): dichiara quali corsi sono erogati in ogni slot e popola gli argomenti per cella (slot × corso). Logica inSessionPlannerPopup.razor.Step4Program.cs. - Docenti — assegnazione con "Suggerisci docenti per skill"
(filtro su
teacherSkillsdel corso). Logica inSessionPlannerPopup.razor.Step4Teachers.cs. - Iscritti — aggiunta workers con scelta del corso di destinazione; integrazione con coda richieste e scadenziario.
- Riepilogo — stima Iscrizioni + Docenze + Aule.
OnStepChangepunta aConfirmOnAdvance: la conferma della sessione (planned → open, richieste soddisfatte) avviene uscendo in avanti da questo step, non premendo un bottone finale. - Documenti —
SessionDocumentsMatrixsul gatepage-edu-sessionDocuments_R, visibile solo a sessione già salvata. Il bottone «Concludi» qui è un backstop idempotente che chiude il wizard.
Lo step Documenti e il gate «sessione gestita»
La matrice incrocia i tipi previsti (edu.sessionDocumentTypes, lista fissa seedata) con quanto è già
archiviato per la sessione (edu.sessionDocuments, bridge M2M Pattern A su oss.documents — il tipo del
file si legge dal categorySlug del documento, non dal bridge). Per ogni tipo si genera da modello o si
carica il file.
Il gate sta sulla colonna trainingSessions.documentsManagedAt (trainingSessions.sql:14):
SetManagedAsync(true)rifiuta se manca un documento obbligatorio — non è un flag libero, è un'asserzione di completezza;- valorizzata, il popup passa
isEditable = falsea tutti gli step: in cima compare l'avviso «Sessione gestita (sola lettura). Usa "Annulla gestione" nello step Documenti per modificare», e i comandi di modifica spariscono ovunque; SetManagedAsync(false)la azzera e riapre il wizard.
È l'unico meccanismo che rende una sessione non modificabile dal wizard: non c'è uno stato di sessione che faccia lo stesso.
Punti d'ingresso: coda richieste, dashboard compliance, scheda
lavoratore, calendario (pulsante "Nuova Sessione", che passa il filtro corso attivo come hint), scheda
progetto FondItalia. ⚠️ Non dalla lista corsi, e non dalla
lista sessioni: lì «Aggiungi» apre il form CRUD e «Duplica» apre
DuplicateSessionPopup, che copia subito.
Vista dati aggregati
AppointmentsData.razor fornisce una vista di dati aggregati per
analisi (totale ore per docente, saturazione aule, ecc.), distinta
dal CRUD. Non è un CRUD: è una pagina di reportistica.
CourseForm non ha cascade
Il form del corso espone il combobox Variante normativa, ma non
pre-popola nulla al cambio: Components/CRUD/edu/Forms/CourseForm.razor.cs
è una partial class vuota. Durata minima e obblighi restano dati della
variante, letti a valle — non copiati sul corso.
Il cascade ATECO → riskLevel del form aziende
(CRUD/reg/Forms/CompanyForm.razor.cs:64-71) non ha un gemello qui.
Location con indirizzo inline e FK a reg.companies
Il form LocationForm.razor espone i campi indirizzo direttamente
sull'aula (formattedAddress + geocoded country/province/city/zipCode/address/streetNumber/latitude/longitude)
e un combobox opzionale companyId per il gestore aula (azienda
proprietaria, NULL = aula 3SD). Cross-dominio: edu consuma reg per le
aziende gestrici.
Suggerimento docenti per skill
Il filtro per competenza non vive più sul form
AppointmentForm.razor (l'appuntamento non ha più una lezione
associata): lo step "Docenti" del wizard
SessionPlannerPopup.Step4Teachers usa il pulsante
"Suggerisci docenti per skill" che incrocia
edu.teacherSkills.trainingVariantId con le varianti dei corsi
erogati nella sessione (trainingSessionsCourses → courses → trainingVariantId).
📁 File chiave
Components/CRUD/edu/Course.razor.cs— code-behind principaleComponents/CRUD/edu/Forms/CourseForm.razor— form del corso (il.razor.csè vuoto: nessuna logica custom)Components/Pages/AppointmentsCalendar/— vista calendario page-level (AppointmentsCalendar.razor,CalendarGrid.razor,AppointmentDetailPanel.razor)Components/edu/SessionPlanner/— wizard a 8 step pianificazione sessione (SessionPlannerPopup.razor+ Step1..Step6 + Step4Program partial;TrainingSessionProgramMatrix.razorper lo step Programma)Components/CRUD/edu/AppointmentsData.razor— vista dati aggregatiComponents/CRUD/edu/_conf/*.dxgrid.conf.json— configurazione
🔌 Estensione tipica
Segue il pattern generale (vedi
componenti UI inv). Specificità edu:
- Aggiungere un campo a
Course. Rigenera + valuta se il cascade variante va aggiornato per precompilare anche il nuovo campo. - Nuova vista analitica cross-dominio. Aggiungi una nuova pagina
Razor non-CRUD in
CRUD/edu/(es.MyAnalytics.razor), inject i service necessari. Non serveconf.json. Routing dichiarato con@pagedirettiva. - Estendere vista calendario. I file in
Pages/AppointmentsCalendar/sono scritti a mano — non CRUD generato. Modifiche dirette senza rigenerazione. Per aggiungere un filtro nuovo: aggiungi il combobox inAppointmentsCalendar.razor, il backing field nel code-behind, e passa il valore aIAppointmentsCalendarService.GetAppointmentsAsync.
⚠️ Debito tecnico
-
AppointmentsCalendarsenza test UI. Componente complesso custom, non coperto da test automatici. -
Filtro docenti per competenza. Coperto dallo step 4 delSessionPlannerPopup("Suggerisci docenti per skill") che usa la skill matrixedu.teacherSkills.
🔗 Vedi anche
- Panoramica dominio
- Schema DB
- Logica applicativa
- Dominio
inv: componenti UI — pattern CRUD base