Componenti UI — dominio reg
🎯 Cosa fa
Sotto TrainingHub.BackOffice/Components/CRUD/reg/ vivono i componenti
Blazor CRUD auto-generati per le 15 entità del dominio registry (4 in
scope per questa spec). Il pattern di generazione è lo stesso del
dominio inv: vedi
componenti UI inv per la
triade di file (grid + form + popup), la regola "custom solo nel
.razor.cs" e la configurazione tramite conf.json.
Questa pagina documenta le particolarità reg-specifiche.
🗺️ Entità in scope
| Entità | File CRUD | Note |
|---|---|---|
Company | Company.razor (+ .razor.cs, .razor.tt.cs) | Pagina principale azienda con code-behind ricco (compliance, dipartimenti, rischi) |
CompanyContact | CompanyContact.razor | Contatti embedded nella scheda azienda o standalone |
CompanyLocation | CompanyLocation.razor | Sedi del cliente, classificate via locationTag |
Headquarter | Headquarter.razor | Sedi TrainingHub |
Forms associati in Forms/<Entity>Form.razor, FormPopups in
FormPopups/<Entity>FormPopup.razor.
🧩 Pattern chiave reg-specifici
Form azienda con AddressFormatter
Il form CompanyForm.razor usa il componente
Tabiot.Blazor.DxAddressFormatter.AddressFormatter per gestire
l'indirizzo strutturato:
<Tabiot.Blazor.DxAddressFormatter.AddressFormatter
@bind-FormattedAddress="@dataItem.formattedAddress"
DataItem="@dataItem"
TModel="TrainingHub.DataLayer.reg.company"
ReadOnly="@readOnly"
StartExpanded="false"
@ref="addressFormatter" />
Il componente:
- Binda i campi strutturati (
country,province,city,zipCode,address,streetNumber) direttamente suldataItem - Genera
formattedAddress(testuale) elatitude/longitude(geocoding) - Lo stesso componente viene usato in
CompanyLocationForm,HeadquarterForme anche nel dominioinvper indirizzi fatture
Cascade ATECO → riskLevelId
Nel form azienda, il combo ATECO ha:
@bind-Value:after="SetRiskLevelId"
Quando l'utente cambia ATECO, SetRiskLevelId() (nel .razor.cs)
carica il riskLevelId associato dal dominio job e lo imposta sul
dataItem. Il riskLevel combobox è readonly a meno che il flag
riskLevelOverride non sia attivo:
ReadOnly="@(readOnly || !dataItem.riskLevelOverride)"
Il checkbox riskLevelOverride ha @bind-Checked:after="SetRiskLevelId":
toggle dell'override ricalcola il valore da ATECO se disattivato, o
sblocca l'editazione manuale se attivato.
Validazione VIES inline
Il combobox VIES non è un combobox: è un DxTextBox con bottone
accessorio:
<DxTextBox @bind-Text="@dataItem.vatCode">
<Buttons>
<DxEditorButton IconCssClass="fa-duotone fa-solid fa-cloud-check"
Tooltip="@localizer["validate_vatCode_vies"]"
Click="@ValidateVatCodeAsync" />
</Buttons>
</DxTextBox>
ValidateVatCodeAsync nel .razor.cs chiama l'integrazione VIES; in
caso di esito positivo setta viesValidated = true.
Company.razor.cs — dettaglio compliance aggregato
Quando l'utente seleziona un'azienda nella grid, il code-behind
OnSelectedCompanyChanged carica in parallelo tre dataset per il
pannello dettaglio:
[Inject] private IRiskInheritanceService RiskService;
[Inject] private ITrainingExpirationService ExpirationService;
[Inject] private ISimpleCRUDService SimpleCRUD;
protected async Task OnSelectedCompanyChanged(Guid companyId)
{
var risksTask = SimpleCRUD.GetListAsync<companiesRisk>(
"WHERE companyId = @companyId", new { companyId });
var departmentsTask = SimpleCRUD.GetListAsync<department>(
"WHERE companyId = @companyId AND active = 1 ORDER BY label",
new { companyId });
var statsTask = ExpirationService.GetComplianceStatsAsync(companyId);
await Task.WhenAll(risksTask, departmentsTask, statsTask);
selectedCompanyRisks = risksTask.Result;
selectedCompanyDepartments = departmentsTask.Result;
selectedCompanyStats = statsTask.Result;
}
Caching: la chiamata è idempotente per companyId (tracking su
lastLoadedCompanyId) → evita ricarichi inutili cliccando la stessa
riga.
Headquarter senza FK a company
Headquarter.razor è un CRUD standalone sul modello generico: non ha
parametri companyId né embedding in altre schede. Si accede dal menu
principale come qualsiasi altra anagrafica indipendente.
📁 File chiave
Components/CRUD/reg/Company.razor.cs— code-behind principale (compliance + dipartimenti + rischi inject)Components/CRUD/reg/Forms/CompanyForm.razor— form con AddressFormatter, cascade ATECO, VIES buttonComponents/CRUD/reg/Forms/CompanyForm.razor.cs—SetRiskLevelId,ValidateVatCodeAsyncComponents/CRUD/reg/_conf/companies.dxgrid.conf.json— configurazione generazione (rotte, permessi, colonne)- Altre entità seguono il pattern standard triade + conf.json
🔌 Estensione tipica
Le estensioni seguono lo stesso schema del dominio inv (vedi
componenti UI inv). Specificità reg:
- Aggiungere un campo indirizzo. Se il campo fa parte dello schema
AddressFormatter(via, civico, città, ecc.), il componente lo gestisce automaticamente daldataItem. Per campi indirizzo non standard, aggiungere markup esplicito. - Aggiungere una validazione ATECO-dipendente. Estendere
SetRiskLevelIdnel.razor.csper logica custom al cambio ATECO. - Aggiungere pannello dettaglio a
Company.razor. Seguire lo schemaOnSelectedCompanyChanged+ parallel load con Task.WhenAll, pattern già in uso per rischi/dipartimenti/stats.
⚠️ Debito tecnico
-
Company.razor.csiniezione cross-dominio. Code-behind injectIRiskInheritanceService(job) eITrainingExpirationService(edu): accoppia la UI reg ai domini correlati. Accettabile perché la vista è di compliance aggregata, ma tenere sotto controllo la crescita. - Logica dei ReadOnly riskLevel duplicata. L'espressione
ReadOnly="@(readOnly || !dataItem.riskLevelOverride)"è ripetuta sia nel form normale che nel flusso insert: estrarre in un helper o computed property. -
AddressFormatterlock-in. Il componente binda l'oggettodataItemintero viaDataItem="@dataItem": se cambiano i nomi dei campi indirizzo la generazione CRUD rompe l'integrazione. Valutare contratto più esplicito.
🔗 Vedi anche
- Panoramica dominio
- Schema DB
- Dominio
inv: componenti UI — pattern CRUD base