laif-ds 1.0.2-app-data-grid.79 → 1.0.2-app-data-grid.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CHANGELOG.md +4 -1
- package/dist/agent-docs/adoption-report.json +3 -3
- package/dist/agent-docs/manifest.json +3 -3
- package/dist/components/ui/app-data-grid/engine/use-grid-engine.js +272 -252
- package/dist/components/ui/app-data-grid/grid-filter-client.js +73 -0
- package/package.json +1 -1
package/dist/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### 🚀 Added
|
|
11
11
|
|
|
12
|
+
- **AppDataGrid**: **i filtri di colonna ora filtrano anche in CLIENT-MODE.** Fino a oggi i badge della Riga Zero raggiungevano solo il ramo server: `state.query.filters` diventava `computedFilter` nel payload emesso, ma **`columnFilters` non veniva mai passato a `useReactTable`** (`engine/use-grid-engine.ts`). In client-mode l'effetto era un comando **BUGIARDO**, che è peggio di un comando morto: scrivendo in una cella la riga restava a schermo, e intanto la cella dichiarava `data-grid-filter-cell-applied="true"` e l'intestazione portava `data-grid-header-filtered` — un test E2E che aspettasse quegli attributi **sarebbe passato su un filtro che non filtra**. Misurato sull'app viva il 2026-09-22 (scheda Ruoli di un utente: valore inesistente, riga che resta; controesempio: la searchbar, che passa dalla stessa porta come `globalFilter`, filtrava 1 riga → 0). ⚠️ **NON È UNA REGRESSIONE**: `git log -S "columnFilters"` restituisce un solo commit, quello di nascita della corsia — il cablaggio non è mai esistito. ⚠️ **E NON CREDERE A `app-data-grid-emitter.stories.tsx`**, che afferma di aver misurato il filtro client battendo in una cella: è falso per costruzione, e la sua stessa play digita nella searchbar. ⚠️ **QUANDO I FILTRI SONO SOLLEVATI (prop `filters`) LA GRIGLIA NON FILTRA IN LOCALE**, ed è una condizione, non una svista: chi solleva lo stato possiede anche il filtraggio, e senza quel gate un call-site esistente avrebbe subito un **doppio filtraggio** — `app-data-grid-filters-lifted.stories.tsx` monta esattamente quella configurazione ed è rimasta verde **senza essere ri-fixturata**, che è la prova che la condizione è quella giusta. ⚠️ **SI FILTRA SULLA STESSA POPOLAZIONE CHE IL SERVER RICEVE** (`usableBadges`): filtrare su una popolazione diversa è il modo in cui la stessa griglia filtra in due modi secondo la modalità. ⚠️ **LA PARITÀ CON I DICIOTTO BACKEND NON È SORVEGLIATA DA NESSUN GATE** — `check-datatable-grid-parity` confronta il vocabolario degli operatori, non la loro semantica — quindi le sette divergenze note (logica a tre valori sui `NULL`, metacaratteri di `LIKE` che il backend non escapa, case folding della collation, `eq` su testo, float64 contro `NUMERIC`, fuso e strettezza delle date, `array_overlap`) **sono scritte nel TSDoc di `grid-filter-client.ts`**, non in un documento di analisi: una funzione che promette una parità che non ha, senza dirlo nel file, è lo stesso difetto che questa corsia tratta come tale altrove. Due di quelle classi, peraltro, **sono già spedite oggi** dalla searchbar client-vs-server: questa funzione allarga un rischio accettato, non ne apre uno nuovo. Dieci scene nuove in `app-data-grid-filtro-client.stories.tsx`.
|
|
12
13
|
- **AppDataGrid**: Nuovo slot **`toolbar.resetFilters`** (`GridToolbarResetSlot` = `GridToolbarSlot` + `onReset?: () => void`) e nuova chiave i18n **`filters.restoreDefaults`** (inglese `"Restore defaults"`, italiano `"Ripristina i predefiniti"` in `stories/app-data-grid-i18n-it.ts`). La griglia rende un `<button data-grid-toolbar-reset-filters>` con l'icona `RotateCcw` e la veste condivisa `gridToolbarTriggerClass`, **subito dopo la ricerca e prima del grilletto «Filtri»** quando quest'ultimo c'è. ⚠️ **LA VISIBILITÀ È DEL CONSUMER, E NON È UNA COMODITÀ: È L'UNICA FORMA POSSIBILE.** La griglia non può calcolare «i filtri correnti differiscono dai default della pagina», perché i default non le arrivano mai — `initialState` è un SEME letto una volta (il `useMemo` con dipendenze `[]` in `engine/use-grid-engine.ts`, non tenuto in nessun ref e mai confrontato con niente) e `preset` porta la vista **ripristinata dell'utente**, non i default. Quindi la risposta è la **presenza di `onReset`**: senza `onReset` e senza `render` il nodo non si rende, esattamente come per `filters` e `actions`. ⚠️ **E LA GRIGLIA NON AGGIUNGE NESSUN GATE SUL CONTEGGIO DEI FILTRI**, che sarebbe la scorciatoia ovvia e sarebbe **sbagliata in un caso reale**: `gridActiveFilterCount(…) > 0` nasconderebbe il bottone quando l'utente ha **tolto** un filtro dichiarato dalla pagina — cioè proprio quando il ripristino ha lavoro da fare. ⚠️ **AMBITO DICHIARATO: FILTRI E RICERCA.** Ordinamento, taglia di pagina, larghezze, ordine e visibilità delle colonne devono **sopravvivere**, e siccome `query/preset` e `view/preset` **sostituiscono in blocco**, chi implementa `onReset` deve costruire un preset che porta i **default** per `filters`/`search` e i valori **correnti** per tutto il resto — un preset parziale non esiste. Il ritorno a `pagination.pageIndex = 0` è inevitabile e corretto: è ciò che fa qualunque cambio di filtro. ⚠️ **NON SOSTITUISCE «Ripristina vista predefinita»** nel menu azioni, che è la via d'uscita TOTALE da una vista salvata illeggibile: i due comandi coesistono e hanno ambiti diversi. ⚠️ **`render` SI RENDE COMUNQUE**: su quel ramo la condizione «differisce dai default» è per intero del consumer — la presenza di `onReset` governa il SOLO nodo che la griglia disegna. ⚠️ **IL FUOCO È GESTITO, E MISURATO.** Il bottone rimuove la condizione della propria esistenza: premuto, il consumer smette di passare `onReset` e il nodo si smonta. Tenerlo montato e `disabled` sarebbe il comando morto che questa corsia vieta, quindi al clic il fuoco si sposta sul **campo di ricerca** — l'altro comando che restringe i dati, il vicino sinistro, e l'unico bersaglio reso SEMPRE. **Non** è il grilletto «Filtri»: quello esiste solo se lo slot `filters` è dichiarato, e nella configurazione più diffusa (`filterMode: "row"`) non c'è affatto — era la ragione per cui la v1 aveva dichiarato il limite come inevitabile, e la ragione era sbagliata perché il bersaglio c'era. La play `IlRipristinoChiedeENonAgisce` **legge `document.activeElement` dopo il click** e pretende il campo di ricerca. Chi passa `render` gestisce il fuoco del proprio nodo da sé. ⚠️ **NON SI RIUSA `i18n.reset`**: quella stringa nomina il bottone di `GridFilterChips` che **svuota** i filtri applicati, questa nomina il gesto che li riporta a ciò che la pagina ha **dichiarato** — con default non vuoti i due esiti divergono, e in italiano «Azzera» e «Ripristina i predefiniti» non sono la stessa frase. ⚠️ **Il cambiamento è additivo e retrocompatibile**: due chiavi opzionali nuove, nessun consumer esistente compila diversamente e nessun nodo nuovo appare senza che qualcuno lo dichiari. L'unico `tsc` che diventa rosso è interno al repo (`gridI18nIt` è tipato `typeof defaultGridI18n` e il suo `filters` è un literal fresco), ed è il meccanismo voluto: la chiave non si spedisce non tradotta. ⚠️ **`GridToolbarResetSlot` NON è esportato dal barrel**, come i fratelli `GridToolbarSlot` e `GridToolbarDisclosureSlot`: `GridToolbarSlots` — che è esportato — basta per annotare, e nessun nome nuovo entra in `scripts/codemod/rename-map.json`. **Cinque story nuove** in `AppDataGrid/Guscio (C3)`, di cui due sulla POSIZIONE e **una sulla configurazione del consumer reale** (`filterMode="row"` **senza** slot `filters`, dove il grilletto «Filtri» non esiste affatto e l'ordine misurato è ricerca → ripristino → «Mostra la riga filtri»): senza quella, l'ordine sarebbe stato misurato solo rispetto a un nodo che il consumer non rende mai
|
|
13
14
|
|
|
14
15
|
- **AppDataGrid**: Nuova story `AppDataGrid/Completa → ScenaDipinta` — le **cinque personalizzazioni del chiamante accese nello stesso posto**, che il repo esponeva già una per una e non mostrava mai insieme: **header in HTML custom** (le due intestazioni di gruppo e quella foglia di «Stato», via `header: ReactNode` + `headerLabel`), **celle in HTML custom** (`cell` — avatar con le iniziali derivate dalla ragione sociale, e la barra di avanzamento), **celle colorate** (`cellClassName` a funzione, una tinta per ciascuno dei quattro stati), **righe colorate** (`rowClassName` a funzione, sul settore) e **colonne colorate** (`headerClassName` + `cellClassName` statica sulle due superfici di «Totale»). **Nessuna prop nuova**: sono cinque canali che il contratto ha già, e la story è una dimostrazione, non un allargamento di superficie. ⚠️ **UNA COLONNA PER DIMOSTRAZIONE, e non è pignoleria**: tinta di colonna e tinta per valore sulla stessa colonna sarebbero due `bg-*` nello stesso `cn()` e `tailwind-merge` ne terrebbe **una**, quindi la scena mostrerebbe quattro cose dichiarandone cinque e nessuna play potrebbe dire quale ha vinto — da qui la mappa, che è anche l'ordine delle **sei** colonne: `Commessa` (110) in chiaro con `rowHeader`, `Cliente` (170) cella custom, `Referente` (130) in chiaro, `Stato` (150) header custom **e** celle colorate, `Avanzamento` (150) cella custom, `Totale` (120) colonna colorata. ⚠️ **Header custom e tinta per valore CONVIVONO sulla stessa colonna** e la regola non lo vieta: quella riguarda due `bg-*` nello stesso `cn()`, non un `<th>` e dei `<td>` — sono due elementi, due prop, e due asserzioni separate. ⚠️ **`Referente` non è riempitivo**: è l'unica colonna senza tinta propria, cioè l'**unico** posto da cui si vede il fondo della **riga** — ed è per questo che la play misura lì il colore risolto. ⚠️ **SEI COLONNE PER 830px, E IL NUMERO È MISURATO**: con barra laterale e pannello addon aperti — come la story si apre — il viewport della griglia misura **850px** di `clientWidth`. La prima stesura aveva sette colonne per **1120px** dichiarati, onorati esatti dal `<colgroup>`, e proprio per questo `Totale` e `Consegna` cadevano **fuori**: la dimostrazione della colonna colorata si vedeva solo scorrendo, e una vetrina che chiede di scorrere per vedere una delle cinque cose che dichiara non le sta mostrando. Da qui la somma sotto la misura e `Consegna` — la seconda linea di base, la meno portante — via, verificato in Chrome: nessun contenitore scrollabile in orizzontale e il bordo destro di `Totale` dentro quello della griglia. ⚠️ **«In pausa» è `slate-200` e non `zinc-100`**, per una ragione che sta a schermo e non nei numeri: `zinc-100` è misurabilmente diverso da `amber-50` — l'asserzione sulle quattro stringhe distinte passava — ma accanto alla tinta di riga si legge come la stessa carta crema, cioè due tinte distinte che sembravano una. ⚠️ **NIENTE COLONNE PINNATE E NIENTE `checkable` in questa scena**, per due limiti già misurati e già scritti nel repo: lo sfondo opaco di una cella pinnata copre un `bg-*` che arrivi da `rowClassName` (lacuna dichiarata in `AppDataGrid.md` e nel commento di `parts/grid-row.tsx`), e la tinta della selezione è uno stile **inline** su ogni cella, che vince su qualunque utilità (`adg-r19`) — accendere l'una o l'altra renderebbe ambigua la dimostrazione proprio nel punto che deve dimostrare. ⚠️ **LA PRECEDENZA FRA LE TRE TINTE È MISURATA SUL COLORE RISOLTO**, non dedotta dalla cascata: su una riga tinta la play legge `getComputedStyle().backgroundColor` della cella di `Totale` e di quella di `Referente` e asserisce che sono **diversi**, e che la seconda è `rgba(0, 0, 0, 0)` — cioè che la cella in chiaro lascia passare il fondo della `<tr>` invece di ridipingerlo. La sonda è scritta **nel verso giusto**: se Tailwind non emettesse le classi della story i due colori sarebbero uguali e l'asserzione andrebbe **rossa**, non verde. ⚠️ **SEI FALSIFICAZIONI ESEGUITE**, ognuna riportata al verde subito dopo e ognuna rossa sul criterio suo: togliere `cellClassName` dalla colonna colorata (`expected +0 to be 12`); sostituire la tinta di colonna con una classe che Tailwind **non può emettere** — che lascia **verde** il criterio sulle classi e rossa la sonda del colore (`expected 'rgba(0, 0, 0, 0)' not to be 'rgba(0, 0, 0, 0)'`), cioè prova che quella sonda misura il CSS e non il markup; azzerare `rowClassName`; togliere il marcatore dell'header foglia; togliere quello dell'header di gruppo; e azzerare la tinta per valore (`expected 'truncate px-3 text-left' to contain 'bg-emerald-100 text-emerald-900'`). ⚠️ **La play non scrive numeri a mano**: il conteggio atteso delle celle custom è `dataRows(grid).length` letto dal DOM, e stato e settore di ogni riga si riagganciano ai dati per `data-grid-row` (da qui `getRowId={(riga) => riga.id}`, il codice e non l'indice), con `rigaDipintaDi` che **solleva** se una riga del DOM non si ritrova nei dati invece di misurare la riga sbagliata in silenzio
|
|
@@ -29,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
29
30
|
|
|
30
31
|
### 🐛 Fixed
|
|
31
32
|
|
|
33
|
+
- **AppDataGrid — ⚠️ IN `serverMode` LA PAGINA 2 NON È PIÙ VUOTA: LA GRIGLIA NON RI-TAGLIA LA FETTA CHE IL SERVER HA GIÀ TAGLIATO.** Il motore montava `getPaginationRowModel` e decideva `manualPagination` guardando il solo `paginated`, senza `serverMode`: con `paginated` acceso la griglia ritagliava con il **proprio** `pageIndex` righe che il server aveva già tagliato con lo **stesso** indice — `slice(10, 20)` su dieci righe ricevute, cioè `[]`. ⚠️ **ED ERA UN GUASTO SOLO, NON DUE**: da quel `[]` esce `dataRowCount = 0`, che è lo stesso numero che accende lo stato «Nessun risultato» **e** smonta il paginatore, selettore della taglia di pagina compreso — una pagina vuota senza nessun comando per uscirne, se non un'azione che passa da `restart` o un ricarico. Ogni griglia `serverMode` + `paginated` che rispondesse con la sola pagina richiesta — cioè ogni consumer che manda `limit`/`skip`, il comportamento normale e quello che il contratto documenta — aveva **la pagina 2 e tutte le successive sempre vuote**. Ora vale `tagliaInLocale = paginated && !serverMode` (`engine/use-grid-engine.ts`): la griglia taglia da sé **solo** quando la pagina è sua, ed è la riga che il gemello congelato aveva già giusta (`tables/data-table/data-table.tsx`, `manualPagination: isServerSide`). ⚠️ **NIENTE SI MUOVE PER CHI NON È IN `serverMode`**, e nemmeno il conteggio delle pagine: `getPageCount` usa `options.rowCount`, che l'engine popola da `serverConfig.totalItems`; un consumer server che ometta `totalItems` ricade su `data.length`, identico a prima, perché il taglio avveniva **dopo** quel modello. ⚠️ **QUATTRO BANCHI DI STORY CODIFICAVANO IL COMPORTAMENTO SBAGLIATO** — il loro finto server ritornava l'insieme intero — e sono stati **ri-fixturati, non pinnati**: la scena sbagliata era la fixture, perché un server vero taglia. ⚠️ **E UNA FRASE DI DOCUMENTAZIONE È STATA REVOCATA**: `app-data-grid-completa.stories.tsx` dichiarava la pagina 2 vuota come limite inevitabile («un consumer che rispondesse con la SOLA pagina richiesta vedrebbe la pagina 2 vuota»). Era la descrizione esatta del difetto, scambiata per un contratto. Story di regressione: `LaPaginaDueDelServerNonSiSvuota`, con una sonda sulla pagina **servita** dal finto server, perché senza quella la play era verde per un istante su codice rotto.
|
|
32
34
|
- **AppDataGrid — ⚠️ IL BADGE FANTASMA: SVUOTARE UNA CELLA DELLA RIGA ZERO ORA TOGLIE IL FILTRO, NON SOLO IL
|
|
33
35
|
VALORE.** Con `filterMode="row"`, scrivere in una cella-filtro e poi **cancellare** lasciava il badge vivo in
|
|
34
36
|
`state.query.filters` con `value: ""`. La query ripartiva correttamente senza quel vincolo — `usableBadges`
|
|
@@ -296,7 +298,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
296
298
|
|
|
297
299
|
### 🔧 Changed
|
|
298
300
|
|
|
299
|
-
- **AppDataGrid**: ⚠️ **BREAKING — il default di `filterMode` passa da `"panel"` a `"row"`** (il destructuring di `AppDataGrid` in `src/components/ui/app-data-grid/app-data-grid.tsx`). Chi non passa `filterMode` ora ottiene la **Riga Zero**: una QUARTA `<tr>` di `<thead>` (`tr[data-grid-filter-row]`), montata **al primo paint** perché il toggle nasce aperto (`useState(true)`), più il bottone **«Mostra/Nascondi la riga filtri»** (`[data-grid-toolbar-filter-row-toggle]`) nel gruppo sinistro della toolbar. **Il `<thead>` cresce di 36px** e chi budgeta l'altezza deve passare `gridViewportHeight({ …, hasFilterRow: filterMode === "row" && open })` leggendo `open` da `onFilterRowOpenChange` — un budget calcolato con `hasFilterRow: false` ora promette 36px che la griglia occupa. ⚠️ **NON È UN CAMBIO DI TIPI: `tsc` resta verde e nessun call-site smette di compilare** — il cambiamento è di COMPORTAMENTO A SCHERMO, quindi si scopre nel DOM e non nel compilatore. ⚠️ **E IL CONTO VERO, MISURATO ESEGUENDO E NON DEDOTTO, È PIÙ ALTO DI QUELLO CHE IL REPO SI ASPETTAVA: 11 scene rosse, non 14 asserzioni in due file.** `yarn run spec` passa da `1 failed | 800 passed (801)` a `12 failed | 789 passed` e torna a `1 failed | 800 passed` — l'unico rosso, prima e dopo, è il preesistente `app-sidebar.stories.tsx > Expand On Hover`. ⚠️ **E QUELLE 11 SONO LE ROSSE
|
|
301
|
+
- **AppDataGrid**: ⚠️ **BREAKING — il default di `filterMode` passa da `"panel"` a `"row"`** (il destructuring di `AppDataGrid` in `src/components/ui/app-data-grid/app-data-grid.tsx`). Chi non passa `filterMode` ora ottiene la **Riga Zero**: una QUARTA `<tr>` di `<thead>` (`tr[data-grid-filter-row]`), montata **al primo paint** perché il toggle nasce aperto (`useState(true)`), più il bottone **«Mostra/Nascondi la riga filtri»** (`[data-grid-toolbar-filter-row-toggle]`) nel gruppo sinistro della toolbar. **Il `<thead>` cresce di 36px** e chi budgeta l'altezza deve passare `gridViewportHeight({ …, hasFilterRow: filterMode === "row" && open })` leggendo `open` da `onFilterRowOpenChange` — un budget calcolato con `hasFilterRow: false` ora promette 36px che la griglia occupa. ⚠️ **NON È UN CAMBIO DI TIPI: `tsc` resta verde e nessun call-site smette di compilare** — il cambiamento è di COMPORTAMENTO A SCHERMO, quindi si scopre nel DOM e non nel compilatore. ⚠️ **E IL CONTO VERO, MISURATO ESEGUENDO E NON DEDOTTO, È PIÙ ALTO DI QUELLO CHE IL REPO SI ASPETTAVA: 11 scene rosse, non 14 asserzioni in due file.** `yarn run spec` passa da `1 failed | 800 passed (801)` a `12 failed | 789 passed` e torna a `1 failed | 800 passed` — l'unico rosso, prima e dopo, è il preesistente `app-sidebar.stories.tsx > Expand On Hover`. ⚠️ **E QUELLE 11 SONO LE ROSSE _NON_ PREVISTE**, perché i tre siti che l'indagine aveva enumerato — gli helper `thead tr` di `app-data-grid-header-levels.stories.tsx` (13 asserzioni) e `app-data-grid-integrazione.stories.tsx` (1), più `ultimaRigaDiThead` in `app-data-grid-grouping.stories.tsx`, il caso che non sarebbe diventato rosso — **sono stati curati nello stesso passo del flip e quindi non hanno mai suonato**: il conto qui sotto è ciò che è emerso ESEGUENDO, oltre a quello. Le rosse si dividono in tre famiglie e **nessuna delle tre era stata prevista**: **(a) un QUARTO helper della stessa classe, che l'inventario statico non aveva trovato** — `intestazioniFoglia` in `app-data-grid-guscio-registro.stories.tsx` (4 scene R16), che prendeva l'ULTIMA `thead tr` per averne le FOGLIE e, trovando la riga zero, **SOLLEVAVA** invece di confrontare due liste vuote: la sua guardia di non-vacuità ha fatto esattamente il mestiere per cui era stata scritta; **(b) chi budgeta la cornice con `hasFilterRow: false`** — `app-data-grid-cornice.stories.tsx` (dieci righe intere diventano nove), `app-data-grid-esiti.stories.tsx` (otto righe sopra il footer diventano sette), `app-data-grid-virtualization.stories.tsx` (i 36px entrano nello `scrollHeight` del viewport e sforano la tolleranza di due righe); **(c) — la famiglia che nessuna previsione conteneva — chi misura il FILO fra le righe di intestazione**, `app-data-grid-header-levels.stories.tsx` e `app-data-grid-density-fullscreen.stories.tsx`, perché `haUnaRigaSotto` (`parts/grid-header.tsx`) conta la Riga Zero come una riga sotto, per una decisione firmata il 2026-09-10, e quindi la riga delle foglie ACQUISTA il filo che quelle scene asserivano assente. Le asserzioni su `[data-grid-header-row]` e su `thead th` restano **immuni per costruzione**: la riga zero porta un marcatore proprio (`parts/grid-filter-row.tsx`) e le sue celle sono `<td>`. **Nessuna scena è stata pinnata in blocco**: 5 siti aggiornati (4 helper portati da `thead tr` a `[data-grid-header-row]` — la cura è una riga per file, perché quei file misurano i LIVELLI di intestazione e non la riga zero — più la scena del filo di `header-levels`, riscritta sulla verità della modalità nuova) e **6 scene pinnate a `filterMode="panel"` esplicito**, una per una e con la ragione scritta accanto al pin: `BordoHeaderConDueLivelli` (density-fullscreen), `AltezzaCorniceMonotona` (cornice), `FooterDeiTotali` (esiti), `CambioDensitaMantieneLeMisure` (virtualization), `ConfineDelBloccoPinnato` (banda-tabella), `FiltriSoloFuori` (completa). ⚠️ **DUE DI QUEI PIN NON SONO RUMORE TOLTO DI MEZZO, SONO COPERTURA CHE SI SAREBBE PERSA**: `BordoHeaderConDueLivelli` è ora l'UNICA scena che pinna il ramo FALSO di `haUnaRigaSotto` («l'ultima riga di intestazione non porta il filo, perché sotto c'è il corpo»), e il ramo VERO — che fino a oggi non aveva **nessuna** copertura eseguibile, perché entrambe le scene del filo montavano in `"panel"` — è pinnato dalla scena aggiornata di `header-levels`. ⚠️ **E DUE ERANO FILI TESI CHE HANNO SUONATO COME SCRITTO**: `ConfineDelBloccoPinnato` dichiarava già nel proprio commento che il conto «regge per quel motivo, non per fortuna», e `FiltriSoloFuori` enumera sette selettori di superfici di filtro «se un giorno una di esse diventasse resa per difetto, la riga che la nomina diventa rossa e dice QUALE» — è successo, e ha detto quale. ⚠️ **REVOCA UNA DECISIONE FIRMATA**, e la revoca sta qui perché chi aveva letto quella va avvisato: il TSDoc di `filterMode` dichiarava «IL DEFAULT E' `"panel"` ED E' UNA DECISIONE, non un ripiego di comodo», con la ragione — le scene esistenti che contano le righe di `<thead>` diventerebbero rosse senza che nessuno le abbia toccate. **La ragione era ed è vera, e la previsione era giusta nel meccanismo e sbagliata nei numeri**: diceva «TRE in nove punti» in header-levels e «altrettanto» in integrazione (sono 13 e 1), ometteva il caso che non diventa rosso (`grouping`) e ometteva del tutto le famiglie (b) e (c). È il **peso** della ragione a essere cambiato: il pannello era la modalità già consegnata quando la Riga Zero era nuova, e oggi la Riga Zero è la modalità che le applicazioni montano. Il prezzo si paga una volta, nelle scene, e non ogni volta in ogni call-site.
|
|
300
302
|
- **AppDataGrid**: `filterMode="panel"` è **work in progress**. Il pannello inventario **funziona, resta nell'unione `GridFilterMode` e resta passabile**, e `GridFilterPanel` resta esportato da entrambi i barili: **niente è stato rimosso e nessun call-site smette di compilare**. Quello che cambia è che non ci si arriva più per difetto e che la modalità non riceve lavoro finché la Riga Zero non è chiusa — la stessa distinzione, e la stessa forma, della voce `GridFilterPanel` in `src/components/documents/components-list.md`, dove la nuova voce sta **accanto** alla sua. ⚠️ **LA MARCATURA È DICHIARATIVA E NON ATTIVA, ed è scritta nei tre posti che un consumer legge davvero**: il TSDoc di `filterMode` (`src/components/ui/app-data-grid/app-data-grid.types.ts`), la riga di tabella di `filterMode` in `src/components/documents/components/AppDataGrid.md`, e la voce nuova in `components-list.md`. **Nessun `console.warn` su `filterMode="panel"`, e l'omissione è una scelta misurata**: le scene che montano il pannello per MISURARE il contratto (`app-data-grid-riga-zero.stories.tsx`) sono copertura corretta, e un warn le trasformerebbe in rumore a ogni run — a maggior ragione ora che sei scene lo passano esplicitamente per pinnare la geometria senza riga zero. La porta resta aperta: un warn è additivo. ⚠️ **È REVERSIBILE CON UNA RIGA**: rimettere `filterMode = "panel"` nel destructuring di `AppDataGrid` riporta tutto com'era, perché nessun tipo è cambiato e nessun export si è mosso, e le sei scene pinnate restano corrette in entrambi i versi. ⚠️ **IL TIPO NON È STATO TOCCATO**: `GridFilterMode = "row" | "panel"` è invariato, e `filterMode` resta appesa in coda a `GridShellFields` per non spostare le righe di `onQueryChange?: never` e `paginated?: never`, citate per numero da `scripts/check-grid-coexistence-matrix.mjs`.
|
|
301
303
|
|
|
302
304
|
- **AppDataGrid**: The **25 «intermediate» story files** — every one except the three showcase files (`AppDataGrid/Mockup`, `AppDataGrid/Completa`, `AppDataGrid/Confronto`) — moved from `title: "AppDataGrid/<name>"` to `title: "CI-CD/AppDataGrid/<name>"`, so the regression specs collect under one `CI-CD` sidebar node and the showcase keeps `AppDataGrid/` to itself. ⚠️ **Each one also pins `id:` in its own meta, and that is the whole point of the change being safe.** A CSF component id is DERIVED from the title (`toId`), so the retitle alone would have moved the URL of every story in those files — and those URLs are cited OUTSIDE the source: 12 files under `.laif/tasks/to-validate` (tasks awaiting human signature, naming the page to open for a criterion not yet signed) and 11 append-only run logs, which are not rewritable, so any id change would leave permanently dead links in the audit trail. `id?: ComponentId` is the documented clause for exactly this (`node_modules/storybook/dist/csf/index.d.ts:876`, «used for URLs. By default is inferred from sanitizing the title»); Storybook in use is 10.0.8. ⚠️ **MEASURED on Storybook's own `/index.json`, before and after — not deduced from the source**: `appdatagrid-*` stories 319 → **319**; ids that disappeared (broken URLs) **0**; unexpected new ids **0**; stories whose title changed at an **unchanged id** **305** (the remaining 14 are the three showcase files); total index 725 → **725**. Citations to intermediate specs that no longer resolve: **2 before, the same 2 after** — `atomo-di-filtro-c7--inventario` and `selezione-c6--barra-di-selezione` were ALREADY dangling before the rename, so the retitle broke nothing. `npm run spec` stays **102 files / 725 tests, 0 failed** (it filters by file path, not by title) and all **14** `scripts/check-*.mjs` gates exit 0 (they read paths and sources; none reads a title). ⚠️ **The id values were COPIED from the index, never retyped**: one of the twenty-five is `appdatagrid-densità-e-schermo-intero-p6`, accent included — retyping it would have normalised the accent and silently broken a URL that works today. ⚠️ **Accepted consequence: title and URL now diverge for good** — a future title rename will silently keep the pinned id, which is why every one of the 25 metas carries the reason inline and points at the «Il nodo CI-CD» section of `docs/analysis/datatable-v2/PIANO-COMPLETAMENTO.md`. ⚠️ **The seven `Manuale` stories move too, and it is stated rather than hidden**: they are the deliberately play-less bench for human task validation, and in CSF a story cannot override its meta's title, so keeping them under `AppDataGrid/` would have meant splitting files — not done. Their URLs are unchanged, so every citation by id still resolves; only their sidebar location moved
|
|
@@ -305,6 +307,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
305
307
|
- **AppDataGrid**: `AppDataGrid.mockup.stories.tsx` stays published in Storybook as `AppDataGrid/Mockup`, frozen: it is the reference the delivery is signed against, and it is not public API — what leaves it is the stale «this mockup is not a component» banner (verifiable by difference: `grep -c 'non è un componente' src/components/stories/AppDataGrid.mockup.stories.tsx` went 1 → 0), not the story, because the delivery is signed by opening the two target scenes — `?path=/story/appdatagrid-completa--modalita-filtri` and `?path=/story/appdatagrid-completa--raggruppata` — next to `?path=/story/appdatagrid-mockup--modalita-filtri` from the Storybook manager, both of them, since groups and pager cannot be represented together and the mockup's single scene therefore has two counterparts
|
|
306
308
|
- **AppDataGrid**: ⚠️ **Known limitation, open** — the two-value operator composer opens from the «Modifica valore…» menu item but **not** from the operator radio path of the filter inventory: the closing `DropdownMenu`'s `DismissableLayer` swallows the `Popover` that mounts in the same frame, and on the radio path the dismissal window outlives a single `requestAnimationFrame` because changing the group's value adds a render before the unmount (tried in four shapes, `modal={false}` included). The only known remedy is a 200ms `setTimeout`, which this lane's «zero setTimeout» convention rejects. Measured and written up in `src/components/documents/components/AppDataGrid.md` and at the `modal={false}` comment in `src/components/ui/app-data-grid/parts/grid-filter-editor.tsx`
|
|
307
309
|
- **Gantt**: Confirmed `react-window` and `@types/react-window` stay as direct dependencies — `VariableSizeList`/`ListOnItemsRenderedProps` are imported directly by `src/components/ui/gantt/types/GanttContext.tsx`, `components/Chart/Chart.tsx`, `components/Chart/Scale/Scale.tsx`, `components/Chart/Tree/Tree.tsx`, `components/Chart/Bars/Bars.tsx` and `components/Gantt/Gantt.tsx`; the only other match in `src/` is a textual mention inside a mock message in `message-list.stories.tsx`, not an import
|
|
310
|
+
|
|
308
311
|
## [laif-ds@1.0.2]
|
|
309
312
|
|
|
310
313
|
### 🐛 Fixed
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-22T17:28:05.419Z",
|
|
4
4
|
"package": {
|
|
5
5
|
"name": "laif-ds",
|
|
6
|
-
"version": "1.0.2-app-data-grid.
|
|
6
|
+
"version": "1.0.2-app-data-grid.80"
|
|
7
7
|
},
|
|
8
8
|
"summary": {
|
|
9
|
-
"scannedFiles":
|
|
9
|
+
"scannedFiles": 489,
|
|
10
10
|
"deprecatedComponentsCount": 4,
|
|
11
11
|
"deprecatedComponentsUsedCount": 2,
|
|
12
12
|
"componentsWithoutExamplesCount": 5,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.1.0",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-22T17:28:04.919Z",
|
|
4
4
|
"package": {
|
|
5
5
|
"name": "laif-ds",
|
|
6
|
-
"version": "1.0.2-app-data-grid.
|
|
6
|
+
"version": "1.0.2-app-data-grid.80"
|
|
7
7
|
},
|
|
8
8
|
"stats": {
|
|
9
9
|
"documentedComponentCount": 97,
|
|
@@ -6549,5 +6549,5 @@
|
|
|
6549
6549
|
]
|
|
6550
6550
|
}
|
|
6551
6551
|
],
|
|
6552
|
-
"checksum": "
|
|
6552
|
+
"checksum": "92e578a83a1d88b7f6993c0cbf205f625b03f71874b1a3254a5459a68f2d94b0"
|
|
6553
6553
|
}
|
|
@@ -1,45 +1,46 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useReactTable as
|
|
3
|
-
import { useMemo as n, useReducer as qe, useRef as
|
|
4
|
-
import { applyGridAggregate as
|
|
5
|
-
import { createGridColumnRegistry as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
2
|
+
import { useReactTable as ut } from "../../../../node_modules/@tanstack/react-table/build/lib/index.js";
|
|
3
|
+
import { useMemo as n, useReducer as qe, useRef as h, useCallback as s, useState as H, useEffect as dt } from "react";
|
|
4
|
+
import { applyGridAggregate as gt } from "../grid-aggregate.js";
|
|
5
|
+
import { createGridColumnRegistry as pt } from "../grid-column-registry.js";
|
|
6
|
+
import { buildGridClientColumnFilters as mt, gridBadgeMatches as ft } from "../grid-filter-client.js";
|
|
7
|
+
import { buildGridGroupTree as yt, gridGroupPaths as ht, gridGroupPathsSignature as wt, seedGridGroupExpansion as bt, flattenGridGroupTree as Ve } from "../grid-grouping.js";
|
|
8
|
+
import { pruneHiddenGridColumns as vt, flattenGridColumns as oe } from "../grid-header-matrix.js";
|
|
9
|
+
import { createInitialGridState as Rt, gridQueryReducer as Ee, gridViewReducer as Ct, toGridServerState as Me, isAdditiveSort as Ft } from "../grid-reducer.js";
|
|
10
|
+
import { gridDefaultDebounceTime as St } from "../grid-tokens.js";
|
|
11
|
+
import { GRID_VIEW_PRESET_VERSION as Gt } from "../grid-view-preset.js";
|
|
12
|
+
import { getPaginationRowModel as It, getSortedRowModel as qt, getFilteredRowModel as Vt, getCoreRowModel as Et } from "../../../../node_modules/@tanstack/table-core/build/lib/index.js";
|
|
13
|
+
const Mt = (i) => i.type === "query/search" || i.type === "query/filters", kt = ({
|
|
13
14
|
columns: i,
|
|
14
|
-
data:
|
|
15
|
-
sortMode:
|
|
16
|
-
maxSortedColumns:
|
|
17
|
-
density:
|
|
18
|
-
paginated:
|
|
19
|
-
pageSize:
|
|
20
|
-
onQueryChange:
|
|
21
|
-
debounceTime:
|
|
22
|
-
serverMode:
|
|
23
|
-
serverConfig:
|
|
15
|
+
data: v,
|
|
16
|
+
sortMode: p,
|
|
17
|
+
maxSortedColumns: d,
|
|
18
|
+
density: M,
|
|
19
|
+
paginated: se,
|
|
20
|
+
pageSize: le,
|
|
21
|
+
onQueryChange: ae,
|
|
22
|
+
debounceTime: Te,
|
|
23
|
+
serverMode: A = !1,
|
|
24
|
+
serverConfig: X,
|
|
24
25
|
initialState: w,
|
|
25
|
-
initialFullscreen:
|
|
26
|
+
initialFullscreen: Pe,
|
|
26
27
|
groupBy: ce,
|
|
27
|
-
initialOpenGroups:
|
|
28
|
-
rowSelection:
|
|
28
|
+
initialOpenGroups: Ne = "all",
|
|
29
|
+
rowSelection: k,
|
|
29
30
|
getRowId: ue,
|
|
30
31
|
onRowSelectionChange: de,
|
|
31
32
|
onCheckedRowsChange: ge,
|
|
32
|
-
rowClassName:
|
|
33
|
-
filters:
|
|
34
|
-
columnVisibility:
|
|
33
|
+
rowClassName: O,
|
|
34
|
+
filters: S,
|
|
35
|
+
columnVisibility: Oe
|
|
35
36
|
}) => {
|
|
36
37
|
const pe = n(
|
|
37
|
-
() =>
|
|
38
|
-
density:
|
|
38
|
+
() => Rt({
|
|
39
|
+
density: M,
|
|
39
40
|
// P10: `initialState.pagination.pageSize` vince su `pageSize`, perché un
|
|
40
41
|
// call-site che passa entrambi ha scritto il primo per dire proprio
|
|
41
42
|
// questo. Il gemello congelato si comporta così.
|
|
42
|
-
pageSize: w?.pagination?.pageSize ??
|
|
43
|
+
pageSize: w?.pagination?.pageSize ?? le,
|
|
43
44
|
// E la PAGINA dichiarata, per la stessa ragione: `GridPagination` la
|
|
44
45
|
// pretende, quindi scartarla significa farla scrivere per niente.
|
|
45
46
|
pageIndex: w?.pagination?.pageIndex,
|
|
@@ -53,7 +54,7 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
53
54
|
// seme valeva 5 e la prima richiesta partiva con `pageIndex: 5` su
|
|
54
55
|
// pagine da 50 — le righe 50-249 non le chiedeva nessuno.
|
|
55
56
|
requestCount: Math.ceil(
|
|
56
|
-
|
|
57
|
+
v.length / (w?.pagination?.pageSize ?? le)
|
|
57
58
|
),
|
|
58
59
|
// Le tre fette dello stato iniziale che il corpus scrive. Lette UNA
|
|
59
60
|
// VOLTA, al montaggio: cambiarle dopo non fa niente, come nel gemello.
|
|
@@ -77,63 +78,63 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
77
78
|
})),
|
|
78
79
|
filters: w?.filters?.filterBadges,
|
|
79
80
|
columnVisibility: w?.columnVisibility,
|
|
80
|
-
fullscreen:
|
|
81
|
+
fullscreen: Pe
|
|
81
82
|
}),
|
|
82
83
|
// Solo al primo render: `density` qui è il valore INIZIALE, e cambiarla poi
|
|
83
84
|
// è un'azione di vista, non un rimontaggio.
|
|
84
85
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
85
86
|
[]
|
|
86
|
-
), [
|
|
87
|
-
|
|
87
|
+
), [J, _e] = qe(
|
|
88
|
+
Ee,
|
|
88
89
|
pe.query
|
|
89
|
-
), [
|
|
90
|
+
), [R, j] = qe(Ct, pe.view), G = Oe ?? R.columnVisibility, _ = n(() => {
|
|
90
91
|
if (!Object.values(G).some(
|
|
91
92
|
(r) => r === !1
|
|
92
93
|
)) return i;
|
|
93
|
-
const t =
|
|
94
|
+
const t = vt(i, G);
|
|
94
95
|
return t.length === 0 ? i : t;
|
|
95
|
-
}, [i, G]),
|
|
96
|
-
const e =
|
|
97
|
-
if (e.length === 0) return
|
|
98
|
-
const t = new Map(e.map((
|
|
99
|
-
for (const
|
|
100
|
-
if (!("columns" in
|
|
101
|
-
const c =
|
|
96
|
+
}, [i, G]), U = n(() => {
|
|
97
|
+
const e = R.columnOrder;
|
|
98
|
+
if (e.length === 0) return _;
|
|
99
|
+
const t = new Map(e.map((y, g) => [y, g])), r = /* @__PURE__ */ new Map(), a = (y) => {
|
|
100
|
+
for (const g of y) {
|
|
101
|
+
if (!("columns" in g) || !Array.isArray(g.columns)) continue;
|
|
102
|
+
const c = g.columns;
|
|
102
103
|
r.set(
|
|
103
|
-
|
|
104
|
-
oe([
|
|
105
|
-
(
|
|
106
|
-
|
|
107
|
-
t.get(
|
|
104
|
+
g.id,
|
|
105
|
+
oe([g]).reduce(
|
|
106
|
+
(E, Q) => Math.min(
|
|
107
|
+
E,
|
|
108
|
+
t.get(Q.id) ?? Number.MAX_SAFE_INTEGER
|
|
108
109
|
),
|
|
109
110
|
Number.MAX_SAFE_INTEGER
|
|
110
111
|
)
|
|
111
112
|
), a(c);
|
|
112
113
|
}
|
|
113
114
|
};
|
|
114
|
-
a(
|
|
115
|
-
const Ge = (
|
|
116
|
-
const
|
|
117
|
-
return c === void 0 ?
|
|
118
|
-
}, Ie = (
|
|
115
|
+
a(_);
|
|
116
|
+
const Ge = (y) => {
|
|
117
|
+
const g = t.get(y.id) ?? Number.MAX_SAFE_INTEGER, c = "columns" in y && Array.isArray(y.columns) ? r.get(y.id) : void 0;
|
|
118
|
+
return c === void 0 ? g : Math.min(g, c);
|
|
119
|
+
}, Ie = (y) => [...y].sort((c, E) => Ge(c) - Ge(E)).map((c) => {
|
|
119
120
|
if (!("columns" in c) || !Array.isArray(c.columns)) return c;
|
|
120
|
-
const
|
|
121
|
-
return
|
|
121
|
+
const E = c.columns, Q = Ie(E);
|
|
122
|
+
return Q.every((at, ct) => at === E[ct]) ? c : { ...c, columns: Q };
|
|
122
123
|
});
|
|
123
|
-
return Ie(
|
|
124
|
-
}, [
|
|
125
|
-
() => oe(
|
|
126
|
-
[
|
|
127
|
-
),
|
|
128
|
-
() =>
|
|
129
|
-
[
|
|
130
|
-
),
|
|
131
|
-
|
|
132
|
-
const
|
|
124
|
+
return Ie(_);
|
|
125
|
+
}, [_, R.columnOrder]), me = n(
|
|
126
|
+
() => oe(U),
|
|
127
|
+
[U]
|
|
128
|
+
), o = n(
|
|
129
|
+
() => pt(me),
|
|
130
|
+
[me]
|
|
131
|
+
), Y = JSON.stringify(S ?? null), fe = h(Y), Z = h(!1);
|
|
132
|
+
fe.current !== Y && (fe.current = Y, Z.current = !0);
|
|
133
|
+
const x = s(
|
|
133
134
|
(e) => {
|
|
134
|
-
if (
|
|
135
|
-
const t = { ...e, filters: [...
|
|
136
|
-
return
|
|
135
|
+
if (S === void 0) return e;
|
|
136
|
+
const t = { ...e, filters: [...S] };
|
|
137
|
+
return Z.current ? {
|
|
137
138
|
...t,
|
|
138
139
|
pagination: { ...t.pagination, pageIndex: 0 }
|
|
139
140
|
// ⚠️ RILIEVO motore-1 DELL'AUDIT 2026-09-09: APERTO, E LA SUA CORREZIONE È
|
|
@@ -166,13 +167,13 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
166
167
|
// filtri sollevati. Appartiene a chi possiede il contratto dell'emettitore.
|
|
167
168
|
} : t;
|
|
168
169
|
},
|
|
169
|
-
[
|
|
170
|
+
[S]
|
|
170
171
|
), I = n(
|
|
171
|
-
() =>
|
|
172
|
-
[
|
|
173
|
-
), [ye,
|
|
174
|
-
() =>
|
|
175
|
-
[
|
|
172
|
+
() => x(J),
|
|
173
|
+
[x, J]
|
|
174
|
+
), [ye, xe] = H(J), T = n(
|
|
175
|
+
() => x(ye),
|
|
176
|
+
[x, ye]
|
|
176
177
|
), q = n(
|
|
177
178
|
() => ({
|
|
178
179
|
// LA STESSA slice che il ref porta: una sorgente sola. Innestare i filtri
|
|
@@ -188,50 +189,50 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
188
189
|
// potatura leggeva il riduttore (`?? `) mentre QUESTA riga metteva `null`
|
|
189
190
|
// dentro `state.view.columnVisibility`, che il pannello del registro
|
|
190
191
|
// dereferenzia. Due espressioni per la stessa cosa sono due cose.
|
|
191
|
-
view: { ...
|
|
192
|
+
view: { ...R, columnVisibility: G }
|
|
192
193
|
}),
|
|
193
|
-
[I,
|
|
194
|
-
),
|
|
195
|
-
() =>
|
|
196
|
-
[
|
|
197
|
-
),
|
|
198
|
-
const e =
|
|
194
|
+
[I, R, G]
|
|
195
|
+
), z = n(
|
|
196
|
+
() => o.all().filter((e) => e.searchable).map((e) => ({ id: e.id, columnType: e.columnType })),
|
|
197
|
+
[o]
|
|
198
|
+
), ze = n(() => {
|
|
199
|
+
const e = Me(I, z);
|
|
199
200
|
return JSON.stringify({
|
|
200
201
|
filters: e.filters,
|
|
201
202
|
computedFilter: e.computedFilter,
|
|
202
203
|
computedSorting: e.computedSorting,
|
|
203
204
|
pageSize: e.pagination.pageSize
|
|
204
205
|
});
|
|
205
|
-
}, [I,
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
const $ =
|
|
214
|
-
$.current =
|
|
215
|
-
const
|
|
216
|
-
|
|
206
|
+
}, [I, z]), m = h(I);
|
|
207
|
+
m.current = I;
|
|
208
|
+
const P = h(R);
|
|
209
|
+
P.current = { ...R, columnVisibility: G };
|
|
210
|
+
const he = h(ae);
|
|
211
|
+
he.current = ae;
|
|
212
|
+
const we = h(z);
|
|
213
|
+
we.current = z;
|
|
214
|
+
const $ = h(0);
|
|
215
|
+
$.current = Te ?? X?.serverDebounceTime ?? St;
|
|
216
|
+
const C = h(null);
|
|
217
|
+
dt(
|
|
217
218
|
() => () => {
|
|
218
|
-
|
|
219
|
+
C.current !== null && window.clearTimeout(C.current);
|
|
219
220
|
},
|
|
220
221
|
[]
|
|
221
222
|
);
|
|
222
|
-
const [
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
const [De, Ke] = H(0), [Be, We] = H({}), u = k ?? Be, ee = s(() => {
|
|
224
|
+
xe(m.current), Ke((e) => e + 1), he.current?.(
|
|
225
|
+
Me(m.current, we.current)
|
|
225
226
|
);
|
|
226
|
-
}, []), te =
|
|
227
|
-
|
|
228
|
-
}, []),
|
|
227
|
+
}, []), te = s(() => {
|
|
228
|
+
C.current !== null && (window.clearTimeout(C.current), C.current = null);
|
|
229
|
+
}, []), b = s(
|
|
229
230
|
(e) => {
|
|
230
|
-
const t =
|
|
231
|
-
if (t !==
|
|
232
|
-
if ((e.type === "query/page" || e.type === "query/page-size") && (
|
|
233
|
-
te(),
|
|
234
|
-
|
|
231
|
+
const t = Ee(m.current, e);
|
|
232
|
+
if (t !== m.current) {
|
|
233
|
+
if ((e.type === "query/page" || e.type === "query/page-size") && (Z.current = !1), m.current = t, _e(e), Mt(e) && $.current > 0) {
|
|
234
|
+
te(), C.current = window.setTimeout(() => {
|
|
235
|
+
C.current = null, ee();
|
|
235
236
|
}, $.current);
|
|
236
237
|
return;
|
|
237
238
|
}
|
|
@@ -239,15 +240,15 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
239
240
|
}
|
|
240
241
|
},
|
|
241
242
|
[te, ee]
|
|
242
|
-
),
|
|
243
|
+
), Le = s(
|
|
243
244
|
(e) => {
|
|
244
|
-
|
|
245
|
+
j({
|
|
245
246
|
type: "view/preset",
|
|
246
247
|
density: e.view.density,
|
|
247
248
|
columnWidths: e.view.columnWidths,
|
|
248
249
|
columnVisibility: e.view.columnVisibility,
|
|
249
250
|
columnOrder: e.view.columnOrder
|
|
250
|
-
}),
|
|
251
|
+
}), b({
|
|
251
252
|
type: "query/preset",
|
|
252
253
|
search: e.query.search,
|
|
253
254
|
sorting: e.query.sorting,
|
|
@@ -255,93 +256,105 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
255
256
|
pageSize: e.query.pageSize
|
|
256
257
|
});
|
|
257
258
|
},
|
|
258
|
-
[
|
|
259
|
-
), Qe =
|
|
259
|
+
[b]
|
|
260
|
+
), Qe = s(
|
|
260
261
|
(e, t) => ({
|
|
261
|
-
version:
|
|
262
|
+
version: Gt,
|
|
262
263
|
id: e,
|
|
263
264
|
name: t,
|
|
264
265
|
query: {
|
|
265
|
-
search:
|
|
266
|
-
sorting: [...
|
|
267
|
-
filters: [...
|
|
268
|
-
pageSize:
|
|
266
|
+
search: m.current.search,
|
|
267
|
+
sorting: [...m.current.sorting],
|
|
268
|
+
filters: [...m.current.filters],
|
|
269
|
+
pageSize: m.current.pagination.pageSize
|
|
269
270
|
},
|
|
270
271
|
view: {
|
|
271
|
-
density:
|
|
272
|
-
columnWidths: { ...
|
|
273
|
-
columnVisibility: { ...
|
|
274
|
-
columnOrder: [...
|
|
272
|
+
density: P.current.density,
|
|
273
|
+
columnWidths: { ...P.current.columnWidths },
|
|
274
|
+
columnVisibility: { ...P.current.columnVisibility },
|
|
275
|
+
columnOrder: [...P.current.columnOrder]
|
|
275
276
|
}
|
|
276
277
|
}),
|
|
277
278
|
[]
|
|
278
|
-
),
|
|
279
|
-
|
|
280
|
-
}, []),
|
|
281
|
-
(e, t) =>
|
|
279
|
+
), He = s((e) => {
|
|
280
|
+
j(e);
|
|
281
|
+
}, []), Xe = s(
|
|
282
|
+
(e, t) => b({
|
|
282
283
|
type: "query/sort",
|
|
283
284
|
columnId: e,
|
|
284
|
-
additive:
|
|
285
|
-
maxSortedColumns:
|
|
285
|
+
additive: Ft(p, t),
|
|
286
|
+
maxSortedColumns: d
|
|
286
287
|
}),
|
|
287
|
-
[
|
|
288
|
-
),
|
|
289
|
-
(e, t) =>
|
|
288
|
+
[b, p, d]
|
|
289
|
+
), ke = s(
|
|
290
|
+
(e, t) => b({
|
|
290
291
|
type: "query/sort-set",
|
|
291
292
|
columnId: e,
|
|
292
293
|
desc: t,
|
|
293
294
|
additive: !1,
|
|
294
|
-
maxSortedColumns:
|
|
295
|
+
maxSortedColumns: d
|
|
295
296
|
}),
|
|
296
|
-
[
|
|
297
|
-
),
|
|
298
|
-
() =>
|
|
299
|
-
[
|
|
300
|
-
), be =
|
|
297
|
+
[b, d]
|
|
298
|
+
), Je = s(
|
|
299
|
+
() => b({ type: "query/sort-clear" }),
|
|
300
|
+
[b]
|
|
301
|
+
), be = h([]), ve = h([]), V = s(
|
|
301
302
|
(e) => {
|
|
302
|
-
|
|
303
|
+
k || We(e), de?.(e), ge?.(
|
|
303
304
|
ve.current.filter((t) => e[t.id]).map((t) => t.original)
|
|
304
305
|
);
|
|
305
306
|
},
|
|
306
|
-
[
|
|
307
|
-
),
|
|
307
|
+
[k, de, ge]
|
|
308
|
+
), je = s(
|
|
308
309
|
(e, t) => {
|
|
309
310
|
const r = { ...u };
|
|
310
|
-
t ? r[e] = !0 : delete r[e],
|
|
311
|
+
t ? r[e] = !0 : delete r[e], V(r);
|
|
311
312
|
},
|
|
312
|
-
[u,
|
|
313
|
-
),
|
|
313
|
+
[u, V]
|
|
314
|
+
), Ue = s(
|
|
314
315
|
(e, t) => {
|
|
315
316
|
const r = { ...u };
|
|
316
317
|
for (const a of e)
|
|
317
318
|
t ? r[a] = !0 : delete r[a];
|
|
318
|
-
|
|
319
|
+
V(r);
|
|
319
320
|
},
|
|
320
|
-
[u,
|
|
321
|
-
),
|
|
321
|
+
[u, V]
|
|
322
|
+
), Ye = s(
|
|
322
323
|
(e) => {
|
|
323
324
|
const t = { ...u };
|
|
324
325
|
for (const r of be.current)
|
|
325
326
|
e ? t[r.id] = !0 : delete t[r.id];
|
|
326
|
-
|
|
327
|
+
V(t);
|
|
327
328
|
},
|
|
328
|
-
[u,
|
|
329
|
-
),
|
|
330
|
-
() =>
|
|
329
|
+
[u, V]
|
|
330
|
+
), Ze = n(
|
|
331
|
+
() => o.all().map((e) => ({
|
|
331
332
|
id: e.id,
|
|
332
333
|
// `accessorFn` e non `accessorKey`: la chiave di TanStack interpreta il
|
|
333
334
|
// punto come profondità (`a.b` legge `row.a.b`), e va bene, ma
|
|
334
335
|
// `undefined` per una colonna senza accessor va dichiarato una volta
|
|
335
336
|
// sola invece che come ramo condizionale su ogni proprietà.
|
|
336
|
-
accessorFn: e.accessorKey ? (t) =>
|
|
337
|
+
accessorFn: e.accessorKey ? (t) => At(t, e.accessorKey) : () => {
|
|
337
338
|
},
|
|
338
339
|
enableSorting: e.sortable,
|
|
339
|
-
enableGlobalFilter: e.searchable
|
|
340
|
+
enableGlobalFilter: e.searchable,
|
|
341
|
+
// ⚠️ NESSUN `enableColumnFilter` ACCANTO, e l'assenza e' una scelta: il
|
|
342
|
+
// default (`true` per una colonna con `accessorFn`) e' gia' quello
|
|
343
|
+
// giusto, perche' la popolazione e' ristretta a monte dai badge che
|
|
344
|
+
// esistono davvero. Un secondo cancello scarterebbe in silenzio un badge
|
|
345
|
+
// che i chip continuano a mostrare — la classe di difetto che D2 chiude.
|
|
346
|
+
filterFn: Pt
|
|
340
347
|
})),
|
|
341
|
-
[
|
|
342
|
-
),
|
|
343
|
-
|
|
344
|
-
|
|
348
|
+
[o]
|
|
349
|
+
), $e = T.sorting, et = n(
|
|
350
|
+
() => A || S !== void 0 ? [] : mt(
|
|
351
|
+
T.filters,
|
|
352
|
+
new Set(o.all().map((e) => e.id))
|
|
353
|
+
),
|
|
354
|
+
[A, S, T.filters, o]
|
|
355
|
+
), Re = se && !A, D = ut({
|
|
356
|
+
data: v,
|
|
357
|
+
columns: Ze,
|
|
345
358
|
// ⚠️ INOLTRATA SOLO SE C'È. `getRowId: undefined` non equivale a non
|
|
346
359
|
// passarla: TanStack distingue la chiave assente dal valore `undefined` in
|
|
347
360
|
// alcune opzioni, e passare esplicitamente `undefined` è il modo di
|
|
@@ -350,19 +363,21 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
350
363
|
// chi non passa la prop.
|
|
351
364
|
...ue === void 0 ? {} : { getRowId: ue },
|
|
352
365
|
state: {
|
|
353
|
-
sorting:
|
|
354
|
-
globalFilter:
|
|
366
|
+
sorting: $e,
|
|
367
|
+
globalFilter: T.search,
|
|
368
|
+
columnFilters: et,
|
|
355
369
|
columnVisibility: q.view.columnVisibility,
|
|
356
|
-
pagination:
|
|
370
|
+
pagination: T.pagination
|
|
357
371
|
},
|
|
358
372
|
// La griglia guida lo stato, quindi TanStack non deve tenerne una copia:
|
|
359
373
|
// senza questi la libreria costruisce il proprio stato interno e i due
|
|
360
374
|
// divergono al primo click.
|
|
361
|
-
onSortingChange:
|
|
362
|
-
onGlobalFilterChange:
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
375
|
+
onSortingChange: N,
|
|
376
|
+
onGlobalFilterChange: N,
|
|
377
|
+
onColumnFiltersChange: N,
|
|
378
|
+
onColumnVisibilityChange: N,
|
|
379
|
+
onPaginationChange: N,
|
|
380
|
+
globalFilterFn: Tt,
|
|
366
381
|
// ⚠️ LA RIGA CHE MANCAVA, e senza la quale il filtro globale SALTAVA
|
|
367
382
|
// colonne dichiarate ricercabili. Riprodotto contro il TanStack vero.
|
|
368
383
|
//
|
|
@@ -378,35 +393,38 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
378
393
|
// correttamente: non veniva mai raggiunto, perché la colonna era già stata
|
|
379
394
|
// scartata a monte. Il registro sa quali colonne sono ricercabili — è la
|
|
380
395
|
// sua ragione di esistere — quindi decide lui.
|
|
381
|
-
getColumnCanGlobalFilter: (e) =>
|
|
396
|
+
getColumnCanGlobalFilter: (e) => o.get(e.id)?.searchable ?? !1,
|
|
382
397
|
// `enableMultiSort` / `enableMultiRemove` NON sono qui, e la loro assenza è
|
|
383
398
|
// deliberata: TanStack le legge solo da `column.toggleSorting` e
|
|
384
399
|
// `table.setSorting`, che questa griglia non chiama mai — l'ordinamento
|
|
385
400
|
// arriva dal riduttore e `getSortedRowModel` onora l'array che riceve
|
|
386
401
|
// qualunque cosa dicano quei flag. Dichiararle sembrerebbe un vincolo e non
|
|
387
402
|
// vincolerebbe niente; il tetto vero è `maxSortedColumns` nel riduttore.
|
|
388
|
-
getCoreRowModel:
|
|
389
|
-
getFilteredRowModel:
|
|
390
|
-
getSortedRowModel:
|
|
403
|
+
getCoreRowModel: Et(),
|
|
404
|
+
getFilteredRowModel: Vt(),
|
|
405
|
+
getSortedRowModel: qt(),
|
|
391
406
|
// La paginazione è OPZIONALE e spenta per default: P1 rendeva tutte le
|
|
392
407
|
// righe, e continuare a farlo è ciò che tiene onesto il criterio «filtrare
|
|
393
408
|
// riduce le righe e cancellare le ripristina esatte» — con una paginazione
|
|
394
409
|
// di mezzo quel conteggio misura la pagina, non l'insieme.
|
|
395
|
-
|
|
396
|
-
// `
|
|
397
|
-
|
|
398
|
-
manualPagination:
|
|
410
|
+
//
|
|
411
|
+
// E in `serverMode` non si taglia affatto: vedi `tagliaInLocale`.
|
|
412
|
+
...Re ? { getPaginationRowModel: It() } : {},
|
|
413
|
+
// `manualPagination` quando NON tagliamo qui: dice a TanStack di non
|
|
414
|
+
// tagliare niente — o perché non siamo paginati, o perché ha già tagliato il
|
|
415
|
+
// server. In modalità paginata CLIENT è il row model qui sopra a tagliare.
|
|
416
|
+
manualPagination: !Re,
|
|
399
417
|
// ⚠️ IN MODALITA' SERVER LA GRIGLIA NON RIFILTRA NE' RIORDINA IN LOCALE — P10.
|
|
400
418
|
// Il server ha gia' fatto entrambe le cose: rifarle qui significa applicare
|
|
401
419
|
// due volte criteri che non coincidono mai del tutto, e mostrare MENO righe
|
|
402
420
|
// di quelle che il server ha mandato — righe che l'utente ha chiesto e che
|
|
403
421
|
// spariscono senza spiegazione.
|
|
404
|
-
manualFiltering:
|
|
405
|
-
manualSorting:
|
|
422
|
+
manualFiltering: A,
|
|
423
|
+
manualSorting: A,
|
|
406
424
|
// Quante righe esistono in TUTTO, quando lo sa solo il server.
|
|
407
|
-
...
|
|
408
|
-
}),
|
|
409
|
-
() =>
|
|
425
|
+
...X?.totalItems === void 0 ? {} : { rowCount: X.totalItems }
|
|
426
|
+
}), tt = n(
|
|
427
|
+
() => o.all().map((e) => {
|
|
410
428
|
const t = q.query.sorting.findIndex(
|
|
411
429
|
(a) => a.id === e.id
|
|
412
430
|
), r = t === -1 ? void 0 : q.query.sorting[t];
|
|
@@ -424,21 +442,21 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
424
442
|
headerClassName: e.headerClassName
|
|
425
443
|
};
|
|
426
444
|
}),
|
|
427
|
-
[
|
|
428
|
-
), l =
|
|
429
|
-
be.current = l.rows, ve.current =
|
|
430
|
-
const
|
|
445
|
+
[o, q.query.sorting]
|
|
446
|
+
), l = D.getRowModel();
|
|
447
|
+
be.current = l.rows, ve.current = D.getPrePaginationRowModel().rows;
|
|
448
|
+
const K = n(
|
|
431
449
|
() => l.rows.map((e) => e.original),
|
|
432
450
|
[l]
|
|
433
|
-
),
|
|
451
|
+
), F = n(
|
|
434
452
|
() => l.rows.map((e) => ({
|
|
435
453
|
id: e.id,
|
|
436
|
-
className: typeof
|
|
437
|
-
cells:
|
|
454
|
+
className: typeof O == "function" ? O(Ae(e)) : O,
|
|
455
|
+
cells: o.all().map((t) => {
|
|
438
456
|
const r = t.accessorKey ? e.getValue(t.id) : void 0, a = t.rowMenu?.(e.original);
|
|
439
457
|
return {
|
|
440
458
|
columnId: t.id,
|
|
441
|
-
content: t.cell ? t.cell(r, e.original,
|
|
459
|
+
content: t.cell ? t.cell(r, e.original, Ae(e)) : Nt(r),
|
|
442
460
|
// Le voci si calcolano QUI, per riga: `rowMenu` è una funzione della
|
|
443
461
|
// riga perché le voci dipendono dalla riga — una commessa chiusa non
|
|
444
462
|
// si elimina. `undefined` per ogni colonna che non è quella del menu.
|
|
@@ -447,17 +465,17 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
447
465
|
};
|
|
448
466
|
})
|
|
449
467
|
})),
|
|
450
|
-
[
|
|
451
|
-
),
|
|
468
|
+
[o, l, O]
|
|
469
|
+
), B = n(
|
|
452
470
|
() => new Map(
|
|
453
471
|
oe(i).filter((e) => e.accessorKey !== void 0).map((e) => [e.id, e.accessorKey])
|
|
454
472
|
),
|
|
455
473
|
[i]
|
|
456
|
-
),
|
|
457
|
-
() => (ce ?? []).filter((e) =>
|
|
458
|
-
[ce,
|
|
459
|
-
),
|
|
460
|
-
rows:
|
|
474
|
+
), W = n(
|
|
475
|
+
() => (ce ?? []).filter((e) => B.has(e)),
|
|
476
|
+
[ce, B]
|
|
477
|
+
), f = n(() => W.length === 0 ? [] : yt({
|
|
478
|
+
rows: F,
|
|
461
479
|
// ⚠️ PARALLELE A `rows`, ed è il motivo per cui si passa il row model e non
|
|
462
480
|
// `data`: `rows` è già filtrato e ordinato, mentre `data` è la prop grezza.
|
|
463
481
|
// Raggruppare la prop grezza vorrebbe dire gruppi che contengono righe che
|
|
@@ -467,30 +485,30 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
467
485
|
// seconda copia della stessa espressione: i gruppi e i totali devono
|
|
468
486
|
// parlare delle stesse righe, o il totale del footer e la somma dei totali
|
|
469
487
|
// di gruppo divergono per una ragione che nessuno vedrebbe.
|
|
470
|
-
data:
|
|
471
|
-
groupBy:
|
|
488
|
+
data: K,
|
|
489
|
+
groupBy: W,
|
|
472
490
|
// Il VALORE raggruppante, letto dal row model per non ridurre a stringa
|
|
473
491
|
// qualcosa che la cella rende in un altro modo.
|
|
474
492
|
valueOf: (e, t) => {
|
|
475
|
-
const r =
|
|
493
|
+
const r = B.get(t);
|
|
476
494
|
if (r !== void 0)
|
|
477
495
|
return l.rows[e]?.original?.[r];
|
|
478
496
|
}
|
|
479
|
-
}), [
|
|
480
|
-
() =>
|
|
497
|
+
}), [F, l, K, B, W]), re = n(() => ht(f), [f]), Ce = n(
|
|
498
|
+
() => wt(re),
|
|
481
499
|
[re]
|
|
482
|
-
), [
|
|
483
|
-
|
|
500
|
+
), [rt, nt] = H(null);
|
|
501
|
+
rt !== Ce && (nt(Ce), f.length > 0 && j({
|
|
484
502
|
type: "view/group-expansion-seed",
|
|
485
|
-
seeded:
|
|
503
|
+
seeded: bt(f, Ne),
|
|
486
504
|
// I percorsi che esistono ADESSO. Servono al riduttore per due cose che
|
|
487
505
|
// solo lui può fare: tenere le voci dei gruppi sopravvissuti e potare
|
|
488
506
|
// quelle dei gruppi spariti. La specifica da sola non basta — dice cosa
|
|
489
507
|
// fare di un albero vergine, non cosa è già stato deciso.
|
|
490
508
|
paths: re
|
|
491
509
|
}));
|
|
492
|
-
const
|
|
493
|
-
(e) =>
|
|
510
|
+
const Fe = R.groupExpansion, Se = s(
|
|
511
|
+
(e) => o.all().map((t) => ({
|
|
494
512
|
columnId: t.id,
|
|
495
513
|
// ⚠️ VUOTA quando la colonna non dichiara un aggregato — criterio 2. Il
|
|
496
514
|
// default sbagliato sarebbe il valore della prima figlia: su una colonna
|
|
@@ -498,41 +516,41 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
498
516
|
// presentato come proprietà del gruppo.
|
|
499
517
|
content: t.aggregatedCell ? t.aggregatedCell(e.data) : null
|
|
500
518
|
})),
|
|
501
|
-
[
|
|
502
|
-
),
|
|
503
|
-
(e) =>
|
|
519
|
+
[o]
|
|
520
|
+
), L = s(
|
|
521
|
+
(e) => o.all().map((t) => ({
|
|
504
522
|
columnId: t.id,
|
|
505
|
-
content:
|
|
523
|
+
content: gt(t, e)
|
|
506
524
|
})),
|
|
507
|
-
[
|
|
508
|
-
),
|
|
509
|
-
() => K
|
|
510
|
-
[
|
|
511
|
-
), ne =
|
|
525
|
+
[o]
|
|
526
|
+
), it = n(
|
|
527
|
+
() => L(K),
|
|
528
|
+
[L, K]
|
|
529
|
+
), ne = s(
|
|
512
530
|
(e) => {
|
|
513
531
|
const t = [];
|
|
514
532
|
for (const r of e) {
|
|
515
533
|
const a = l.rowsById[r];
|
|
516
534
|
a !== void 0 && t.push(a.original);
|
|
517
535
|
}
|
|
518
|
-
return
|
|
536
|
+
return L(t);
|
|
519
537
|
},
|
|
520
|
-
[
|
|
538
|
+
[L, l]
|
|
521
539
|
), ie = n(
|
|
522
540
|
() => l.rows.filter((e) => u[e.id]).map((e) => e.id),
|
|
523
541
|
[l, u]
|
|
524
|
-
),
|
|
542
|
+
), ot = n(
|
|
525
543
|
() => ne(ie),
|
|
526
544
|
[ne, ie]
|
|
527
|
-
),
|
|
528
|
-
tree:
|
|
529
|
-
expansion:
|
|
545
|
+
), st = n(() => f.length === 0 ? F.map((e) => ({ kind: "row", row: e })) : Ve({
|
|
546
|
+
tree: f,
|
|
547
|
+
expansion: Fe,
|
|
530
548
|
// LE CELLE DELLA RIGA DI GRUPPO: una per colonna VISIBILE, come una riga di
|
|
531
549
|
// dati, mai una che ne occupa molte. Vedi il commento in `grid-grouping.ts`.
|
|
532
550
|
cellsFor: Se
|
|
533
|
-
}), [
|
|
534
|
-
(e) =>
|
|
535
|
-
tree:
|
|
551
|
+
}), [f, Fe, Se, F]), lt = s(
|
|
552
|
+
(e) => f.length === 0 ? F.length : Ve({
|
|
553
|
+
tree: f,
|
|
536
554
|
expansion: e
|
|
537
555
|
// ⚠️ SENZA `cellsFor`: qui si conta, non si rende. Passandola, ogni
|
|
538
556
|
// `aggregatedCell` girava su tutte le righe del proprio gruppo —
|
|
@@ -540,59 +558,61 @@ const qt = (i) => i.type === "query/search" || i.type === "query/filters", Wt =
|
|
|
540
558
|
// La sequenza delle righe non dipende dalle celle, quindi il numero
|
|
541
559
|
// è lo stesso.
|
|
542
560
|
}).length,
|
|
543
|
-
[
|
|
561
|
+
[f, F]
|
|
544
562
|
);
|
|
545
563
|
return {
|
|
546
|
-
headers:
|
|
547
|
-
rows:
|
|
548
|
-
leafCount:
|
|
549
|
-
rowHeaderColumnId:
|
|
564
|
+
headers: tt,
|
|
565
|
+
rows: F,
|
|
566
|
+
leafCount: o.all().length,
|
|
567
|
+
rowHeaderColumnId: o.rowHeaderId(),
|
|
550
568
|
// L'albero, per il `<thead>`. È la sola cosa che P5 aggiunge alla superficie
|
|
551
569
|
// dell'engine: la STRUTTURA delle intestazioni sta qui, lo STATO di
|
|
552
570
|
// ordinamento in `headers`, e `parts/grid-table.tsx` li unisce per id.
|
|
553
571
|
// L'albero POTATO: il `<thead>` deve vedere le stesse colonne del corpo.
|
|
554
|
-
columnTree:
|
|
555
|
-
bodyRows:
|
|
556
|
-
footerCells:
|
|
572
|
+
columnTree: U,
|
|
573
|
+
bodyRows: st,
|
|
574
|
+
footerCells: it,
|
|
557
575
|
aggregateCells: ne,
|
|
558
|
-
selectionCells:
|
|
576
|
+
selectionCells: ot,
|
|
559
577
|
// La DICHIARAZIONE e non il contenuto: un aggregato che rende `null` non è
|
|
560
578
|
// una colonna senza aggregato, e chi decide se il `<tfoot>` esiste deve
|
|
561
579
|
// poter distinguere «nessuna colonna dichiara un totale» da «i totali sono
|
|
562
580
|
// vuoti».
|
|
563
|
-
hasAggregates:
|
|
564
|
-
groupByColumnIds:
|
|
565
|
-
bodyRowCountWith:
|
|
566
|
-
applyPreset:
|
|
581
|
+
hasAggregates: o.all().some((e) => e.aggregate !== void 0),
|
|
582
|
+
groupByColumnIds: W,
|
|
583
|
+
bodyRowCountWith: lt,
|
|
584
|
+
applyPreset: Le,
|
|
567
585
|
readPreset: Qe,
|
|
568
586
|
state: q,
|
|
569
|
-
emissions:
|
|
570
|
-
querySignature:
|
|
571
|
-
pageCount:
|
|
572
|
-
filteredRowCount:
|
|
587
|
+
emissions: De,
|
|
588
|
+
querySignature: ze,
|
|
589
|
+
pageCount: se ? D.getPageCount() : 1,
|
|
590
|
+
filteredRowCount: D.getPrePaginationRowModel().rows.length,
|
|
573
591
|
selection: u,
|
|
574
|
-
toggleRow:
|
|
575
|
-
toggleRows:
|
|
576
|
-
toggleAllRows:
|
|
592
|
+
toggleRow: je,
|
|
593
|
+
toggleRows: Ue,
|
|
594
|
+
toggleAllRows: Ye,
|
|
577
595
|
selectedPageRowIds: ie,
|
|
578
596
|
allRowsSelected: l.rows.length > 0 && l.rows.every((e) => u[e.id]),
|
|
579
597
|
someRowsSelected: l.rows.some((e) => u[e.id]) && !l.rows.every((e) => u[e.id]),
|
|
580
|
-
applyAndEmit:
|
|
581
|
-
applyView:
|
|
582
|
-
sort:
|
|
583
|
-
setSortDirection:
|
|
584
|
-
clearSort:
|
|
598
|
+
applyAndEmit: b,
|
|
599
|
+
applyView: He,
|
|
600
|
+
sort: Xe,
|
|
601
|
+
setSortDirection: ke,
|
|
602
|
+
clearSort: Je
|
|
585
603
|
};
|
|
586
|
-
},
|
|
587
|
-
},
|
|
588
|
-
(
|
|
604
|
+
}, N = () => {
|
|
605
|
+
}, At = (i, v) => v.split(".").reduce(
|
|
606
|
+
(p, d) => p?.[d],
|
|
589
607
|
i
|
|
590
|
-
),
|
|
591
|
-
const
|
|
592
|
-
if (
|
|
593
|
-
const
|
|
594
|
-
return
|
|
595
|
-
},
|
|
608
|
+
), Tt = (i, v, p) => {
|
|
609
|
+
const d = String(p ?? "").toLowerCase();
|
|
610
|
+
if (d === "") return !0;
|
|
611
|
+
const M = i.getValue(v);
|
|
612
|
+
return M == null ? !1 : String(M).toLowerCase().includes(d);
|
|
613
|
+
}, Pt = (i, v, p) => p.every(
|
|
614
|
+
(d) => ft(d, i.getValue(v))
|
|
615
|
+
), Nt = (i) => i == null ? "" : String(i), Ae = (i) => i;
|
|
596
616
|
export {
|
|
597
|
-
|
|
617
|
+
kt as useGridEngine
|
|
598
618
|
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { usableBadges as b } from "./grid-filter-compile.js";
|
|
3
|
+
import { gridBadgeFilterType as p } from "./grid-filter-operators.js";
|
|
4
|
+
const A = (t, r) => {
|
|
5
|
+
const e = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const n of b(t)) {
|
|
7
|
+
if (!r.has(n.columnId)) continue;
|
|
8
|
+
const o = e.get(n.columnId);
|
|
9
|
+
o === void 0 ? e.set(n.columnId, [n]) : o.push(n);
|
|
10
|
+
}
|
|
11
|
+
return [...e].map(([n, o]) => ({ id: n, value: o }));
|
|
12
|
+
}, _ = /[.*+?^${}()|[\]\\]/g, c = (t) => new RegExp(
|
|
13
|
+
`^${t.replace(_, "\\$&").replace(/%/g, ".*").replace(/_/g, ".")}$`,
|
|
14
|
+
"iu"
|
|
15
|
+
), g = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?)?$/, i = (t) => {
|
|
16
|
+
if (t == null) return Number.NaN;
|
|
17
|
+
let r = String(t).replace(/Z+$/, "");
|
|
18
|
+
const e = r.indexOf("+");
|
|
19
|
+
e !== -1 && r.includes("T") && (r = r.slice(0, e));
|
|
20
|
+
const n = g.exec(r);
|
|
21
|
+
return n === null ? Number.NaN : Date.UTC(
|
|
22
|
+
Number(n[1]),
|
|
23
|
+
Number(n[2]) - 1,
|
|
24
|
+
Number(n[3]),
|
|
25
|
+
Number(n[4] ?? "0"),
|
|
26
|
+
Number(n[5] ?? "0"),
|
|
27
|
+
Number(n[6] ?? "0"),
|
|
28
|
+
Number(`${n[7] ?? ""}000`.slice(0, 3))
|
|
29
|
+
);
|
|
30
|
+
}, u = (t) => Array.isArray(t) ? t : [t], d = (t, r) => Array.isArray(t) ? t.some((e) => u(r).includes(e)) : u(r).includes(t), a = (t) => t == null || Array.isArray(t) && t.length === 0, f = (t, r) => {
|
|
31
|
+
const e = i(t);
|
|
32
|
+
return Number.isFinite(e) && e === i(r);
|
|
33
|
+
}, m = (t, r) => {
|
|
34
|
+
const e = i(t);
|
|
35
|
+
return Number.isFinite(e) && e < i(r);
|
|
36
|
+
}, N = (t, r) => {
|
|
37
|
+
const e = i(t);
|
|
38
|
+
return Number.isFinite(e) && e > i(r);
|
|
39
|
+
}, s = (t, r, e) => t != null && c(e(String(r ?? ""))).test(String(t)), v = {
|
|
40
|
+
// `eq` su una data e' `col = parse_datetime(v)`, su tutto il resto `col = v`.
|
|
41
|
+
// Divergenze D ed E.
|
|
42
|
+
eq: (t, r, e) => e === "date" || e === "datetime" ? f(t, r) : t != null && t === r,
|
|
43
|
+
// ⚠️ IL GUARD `!= null` E' LA DIVERGENZA A, RICONCILIATA. Toglierlo fa passare
|
|
44
|
+
// le righe con la cella vuota, che in SQL non passano.
|
|
45
|
+
ne: (t, r) => t != null && t !== r,
|
|
46
|
+
lt: (t, r) => t != null && Number(t) < Number(r),
|
|
47
|
+
le: (t, r) => t != null && Number(t) <= Number(r),
|
|
48
|
+
gt: (t, r) => t != null && Number(t) > Number(r),
|
|
49
|
+
ge: (t, r) => t != null && Number(t) >= Number(r),
|
|
50
|
+
like: (t, r) => s(t, r, (e) => `%${e}%`),
|
|
51
|
+
// Divergenza A, di nuovo: la negazione NON vale su una cella vuota.
|
|
52
|
+
n_like: (t, r) => t != null && !s(t, r, (e) => `%${e}%`),
|
|
53
|
+
starts_with: (t, r) => s(t, r, (e) => `${e}%`),
|
|
54
|
+
ends_with: (t, r) => s(t, r, (e) => `%${e}`),
|
|
55
|
+
date_before: m,
|
|
56
|
+
date_time_before: m,
|
|
57
|
+
date_after: N,
|
|
58
|
+
date_time_after: N,
|
|
59
|
+
array_overlap: (t, r) => t != null && d(t, r),
|
|
60
|
+
n_array_overlap: (t, r) => t != null && !d(t, r),
|
|
61
|
+
eq_null: (t) => a(t),
|
|
62
|
+
n_eq_null: (t) => !a(t),
|
|
63
|
+
checked: (t) => t === !0,
|
|
64
|
+
unchecked: (t) => t === !1
|
|
65
|
+
}, E = (t, r) => v[t.operator](
|
|
66
|
+
r,
|
|
67
|
+
t.value,
|
|
68
|
+
p(t)
|
|
69
|
+
);
|
|
70
|
+
export {
|
|
71
|
+
A as buildGridClientColumnFilters,
|
|
72
|
+
E as gridBadgeMatches
|
|
73
|
+
};
|