Passa al contenuto principale

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àFileNote
CategoryCategory.razorAnagrafica semplice
CourseCourse.razorForm ricco con cascade variante normativa
TrainingSessionTrainingSession.razorSessione formativa (ciclo vita stati)
TrainingSessionsCourseTrainingSessionsCourse.razorM:N session ↔ corso (co-erogazione)
AppointmentAppointment.razor (grid CRUD) + Pages/AppointmentsCalendar/ (vista calendario page-level)Doppia vista griglia + calendario
AppointmentsCourseAppointmentsCourse.razorCella della matrice "programma" (appointment × corso)
AppointmentsCoursesArgumentAppointmentsCoursesArgument.razorArgomenti trattati per slot × corso
LocationLocation.razorAule con indirizzo inline + FK opzionale a reg.companies (gestore aula)
TeacherTeacher.razorAnagrafica docente
AppointmentsTeacherAppointmentsTeacher.razorM:N con unitAmount + costMode
TeacherCostTeacherCost.razorCosti per organizer
TeacherSkillTeacherSkill.razorCompetenze 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" apre SessionPlannerPopup.
  • Conflict detection via ISessionPlannerService.DetectConflictsAsync con 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.csSessionPlannerPopup.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:

  1. Sessione — data inizio + argomento opzionale (filtra corsi).
  2. Corsi — aggiunge uno o più corsi in co-erogazione. ⚠️ Le finestre orarie non stanno qui: trainingSessionsCourses non ha colonne orarie. Il per-slot si imposta nello step Programma, su appointmentsCourses.startDateTime/endDateTime.
  3. Date e sedi — appuntamenti fisici e sede.
  4. Programma — matrice appuntamento × corso (TrainingSessionProgramMatrix): dichiara quali corsi sono erogati in ogni slot e popola gli argomenti per cella (slot × corso). Logica in SessionPlannerPopup.razor.Step4Program.cs.
  5. Docenti — assegnazione con "Suggerisci docenti per skill" (filtro su teacherSkills del corso). Logica in SessionPlannerPopup.razor.Step4Teachers.cs.
  6. Iscritti — aggiunta workers con scelta del corso di destinazione; integrazione con coda richieste e scadenziario.
  7. Riepilogo — stima Iscrizioni + Docenze + Aule. OnStepChange punta a ConfirmOnAdvance: la conferma della sessione (planned → open, richieste soddisfatte) avviene uscendo in avanti da questo step, non premendo un bottone finale.
  8. DocumentiSessionDocumentsMatrix sul gate page-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 = false a 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 principale
  • Components/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.razor per lo step Programma)
  • Components/CRUD/edu/AppointmentsData.razor — vista dati aggregati
  • Components/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 serve conf.json. Routing dichiarato con @page direttiva.
  • 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 in AppointmentsCalendar.razor, il backing field nel code-behind, e passa il valore a IAppointmentsCalendarService.GetAppointmentsAsync.

⚠️ Debito tecnico

  • AppointmentsCalendar senza test UI. Componente complesso custom, non coperto da test automatici.
  • Filtro docenti per competenza. Coperto dallo step 4 del SessionPlannerPopup ("Suggerisci docenti per skill") che usa la skill matrix edu.teacherSkills.

🔗 Vedi anche