Passa al contenuto principale

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 CRUDNote
CompanyCompany.razor (+ .razor.cs, .razor.tt.cs)Pagina principale azienda con code-behind ricco (compliance, dipartimenti, rischi)
CompanyContactCompanyContact.razorContatti embedded nella scheda azienda o standalone
CompanyLocationCompanyLocation.razorSedi del cliente, classificate via locationTag
HeadquarterHeadquarter.razorSedi 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 sul dataItem
  • Genera formattedAddress (testuale) e latitude/longitude (geocoding)
  • Lo stesso componente viene usato in CompanyLocationForm, HeadquarterForm e anche nel dominio inv per 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 button
  • Components/CRUD/reg/Forms/CompanyForm.razor.csSetRiskLevelId, ValidateVatCodeAsync
  • Components/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 dal dataItem. Per campi indirizzo non standard, aggiungere markup esplicito.
  • Aggiungere una validazione ATECO-dipendente. Estendere SetRiskLevelId nel .razor.cs per logica custom al cambio ATECO.
  • Aggiungere pannello dettaglio a Company.razor. Seguire lo schema OnSelectedCompanyChanged + parallel load con Task.WhenAll, pattern già in uso per rischi/dipartimenti/stats.

⚠️ Debito tecnico

  • Company.razor.cs iniezione cross-dominio. Code-behind inject IRiskInheritanceService (job) e ITrainingExpirationService (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.
  • AddressFormatter lock-in. Il componente binda l'oggetto dataItem intero via DataItem="@dataItem": se cambiano i nomi dei campi indirizzo la generazione CRUD rompe l'integrazione. Valutare contratto più esplicito.

🔗 Vedi anche