@12-apps/ui 6.65.0 → 6.66.1

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.
Files changed (159) hide show
  1. package/dist/_internal/components/data-display/Badge/Badge.js +2 -2
  2. package/dist/_internal/components/data-display/Chart/Chart.js +4 -4
  3. package/dist/_internal/components/data-display/Chart/SpecChart.js +5 -5
  4. package/dist/_internal/components/data-display/Chart/chart-context.js +2 -2
  5. package/dist/_internal/components/data-display/Chart/chart-renderers.js +3 -3
  6. package/dist/_internal/components/data-display/DataViews/DataViewsBoard.js +1 -1
  7. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +14 -13
  8. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +16 -15
  9. package/dist/_internal/components/data-display/DataViews/base-card.js +2 -2
  10. package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +5 -5
  11. package/dist/_internal/components/data-display/DataViews/base-list-card-layout.js +6 -6
  12. package/dist/_internal/components/data-display/DataViews/base-list-card-slots.js +4 -4
  13. package/dist/_internal/components/data-display/DataViews/base-list-card.js +8 -8
  14. package/dist/_internal/components/data-display/DataViews/data-views-card-body.js +35 -0
  15. package/dist/_internal/components/data-display/DataViews/data-views-card-body.js.map +1 -0
  16. package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +4 -4
  17. package/dist/_internal/components/data-display/DataViews/data-views-display-tabs.js +3 -3
  18. package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +6 -5
  19. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +12 -11
  20. package/dist/_internal/components/data-display/DataViews/data-views-layout-context.js +2 -2
  21. package/dist/_internal/components/data-display/DataViews/data-views-scope-tabs.js +3 -3
  22. package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +6 -6
  23. package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +5 -5
  24. package/dist/_internal/components/data-display/DataViews/data-views-types.js +1 -1
  25. package/dist/_internal/components/data-display/DataViews/list-card-rails.js +3 -3
  26. package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +2 -2
  27. package/dist/_internal/components/data-display/DataViews/use-saved-views-controller.js +2 -2
  28. package/dist/_internal/components/data-display/Sheet/Sheet.body.js +2 -2
  29. package/dist/_internal/components/data-display/Sheet/Sheet.js +3 -3
  30. package/dist/_internal/components/data-display/Sheet/Sheet.parts.js +1 -1
  31. package/dist/_internal/components/data-display/index.js +10 -10
  32. package/dist/_internal/components/index.js +18 -18
  33. package/dist/_internal/components/navigation/AppHeader/AppHeader.details.js +4 -4
  34. package/dist/_internal/components/navigation/SectionNav/SectionNav.bar.js +6 -6
  35. package/dist/_internal/components/navigation/SectionNav/SectionNav.js +9 -9
  36. package/dist/_internal/components/navigation/SectionNav/SectionNav.parts.js +3 -3
  37. package/dist/_internal/components/navigation/SectionNav/SectionNav.primary.js +4 -4
  38. package/dist/_internal/components/navigation/SectionNav/SectionNav.rail.js +5 -5
  39. package/dist/_internal/components/navigation/SectionNav/SectionNav.railCreate.js +4 -4
  40. package/dist/_internal/components/navigation/SectionNav/SectionNav.sheet.js +4 -4
  41. package/dist/_internal/components/navigation/index.js +13 -13
  42. package/dist/charts.js +5 -5
  43. package/dist/{chunk-FL2DRDTS.js → chunk-2VMIHFMX.js} +2 -2
  44. package/dist/{chunk-OR6JKOQ5.js → chunk-33XGC4YU.js} +2 -2
  45. package/dist/{chunk-BQNYX3VM.js → chunk-35L5YMYQ.js} +2 -2
  46. package/dist/{chunk-LDCHE6QS.js → chunk-3WLS7Z7D.js} +3 -3
  47. package/dist/{chunk-VUVPEKDE.js → chunk-5MMAV7XK.js} +2 -2
  48. package/dist/{chunk-RNLLDK5L.js → chunk-67XUBKJC.js} +3 -3
  49. package/dist/{chunk-CZVAVX5L.js → chunk-6ATXOJVQ.js} +2 -2
  50. package/dist/{chunk-GLBK6X5Z.js → chunk-7J5JR33K.js} +2 -2
  51. package/dist/{chunk-NHHWNA5W.js → chunk-7TCOYCD7.js} +2 -2
  52. package/dist/chunk-7TCOYCD7.js.map +1 -0
  53. package/dist/{chunk-ANJAAGN3.js → chunk-AKGXNC4R.js} +2 -2
  54. package/dist/{chunk-5357QUVE.js → chunk-AMLHEKSV.js} +5 -5
  55. package/dist/{chunk-HR7YTBR7.js → chunk-BI7YMVKZ.js} +2 -2
  56. package/dist/{chunk-OZGKIHAA.js → chunk-BRBQNPM2.js} +2 -2
  57. package/dist/{chunk-YOEZTW4T.js → chunk-FNFDXJWR.js} +3 -3
  58. package/dist/chunk-FNFDXJWR.js.map +1 -0
  59. package/dist/{chunk-ULBOCFST.js → chunk-J6DYHBID.js} +7 -7
  60. package/dist/{chunk-H5OVOWHY.js → chunk-JKNKGGOG.js} +5 -3
  61. package/dist/{chunk-H5OVOWHY.js.map → chunk-JKNKGGOG.js.map} +1 -1
  62. package/dist/{chunk-MQNL5UME.js → chunk-JXW6DZDN.js} +2 -2
  63. package/dist/{chunk-GVEWFA3Z.js → chunk-JY5YINUH.js} +21 -53
  64. package/dist/chunk-JY5YINUH.js.map +1 -0
  65. package/dist/{chunk-MO52X5WW.js → chunk-JYMXYSDP.js} +2 -2
  66. package/dist/{chunk-PEHYS7TU.js → chunk-K6KBASL2.js} +2 -2
  67. package/dist/{chunk-EMHG2ZQN.js → chunk-KDQEJAKH.js} +2 -2
  68. package/dist/{chunk-T3SZYWWN.js → chunk-L6MGAPBX.js} +2 -2
  69. package/dist/{chunk-YLBZ3SKU.js → chunk-L7OWFJUU.js} +6 -5
  70. package/dist/chunk-L7OWFJUU.js.map +1 -0
  71. package/dist/{chunk-5YDTSJNY.js → chunk-LMIOWP6H.js} +2 -2
  72. package/dist/{chunk-GGG6TDET.js → chunk-M6V7Y7CP.js} +3 -3
  73. package/dist/chunk-M6V7Y7CP.js.map +1 -0
  74. package/dist/{chunk-6DO672AG.js → chunk-N5PVD4J5.js} +7 -7
  75. package/dist/{chunk-LHHAYZ55.js → chunk-O2P2KTKN.js} +1 -1
  76. package/dist/chunk-O2P2KTKN.js.map +1 -0
  77. package/dist/{chunk-NMGNKIXF.js → chunk-QQWW6T4Q.js} +2 -2
  78. package/dist/{chunk-R52N5UCS.js → chunk-SB2SEGR6.js} +2 -2
  79. package/dist/{chunk-OT5JBIBT.js → chunk-SUZLGB5I.js} +3 -3
  80. package/dist/chunk-SZSS5KKX.js +51 -0
  81. package/dist/chunk-SZSS5KKX.js.map +1 -0
  82. package/dist/{chunk-JG733RMP.js → chunk-TZC6KIK2.js} +5 -5
  83. package/dist/{chunk-B7PKBFA4.js → chunk-UZ2CEJOO.js} +2 -2
  84. package/dist/{chunk-HG7MCNRR.js → chunk-V45JGJ3W.js} +4 -4
  85. package/dist/{chunk-HG7MCNRR.js.map → chunk-V45JGJ3W.js.map} +1 -1
  86. package/dist/{chunk-E2VVEXEX.js → chunk-VJB63DUM.js} +2 -2
  87. package/dist/{chunk-CJ7KAM3S.js → chunk-XBTSKYYY.js} +4 -4
  88. package/dist/{chunk-IZBQL4HZ.js → chunk-ZDT4ZGP4.js} +3 -3
  89. package/dist/data-display/Badge.js +2 -2
  90. package/dist/data-display/CardKit.js +3 -3
  91. package/dist/data-display/Chart.js +5 -5
  92. package/dist/data-display/DataViews.js +22 -21
  93. package/dist/data-display/Sheet.js +3 -3
  94. package/dist/navigation/AppHeader.js +4 -4
  95. package/dist/navigation/SectionNav.js +9 -9
  96. package/dist/types/components/data-display/DataViews/DataViewsBoard.d.ts +2 -1
  97. package/dist/types/components/data-display/DataViews/DataViewsBoard.d.ts.map +1 -1
  98. package/dist/types/components/data-display/DataViews/DataViewsGrid.d.ts +10 -0
  99. package/dist/types/components/data-display/DataViews/DataViewsGrid.d.ts.map +1 -1
  100. package/dist/types/components/data-display/DataViews/DataViewsTableBase.d.ts +7 -0
  101. package/dist/types/components/data-display/DataViews/DataViewsTableBase.d.ts.map +1 -1
  102. package/dist/types/components/data-display/DataViews/data-views-card-body.d.ts +26 -0
  103. package/dist/types/components/data-display/DataViews/data-views-card-body.d.ts.map +1 -0
  104. package/dist/types/components/data-display/DataViews/data-views-grid-bodies.d.ts +2 -0
  105. package/dist/types/components/data-display/DataViews/data-views-grid-bodies.d.ts.map +1 -1
  106. package/dist/types/components/data-display/DataViews/data-views-grid-parts.d.ts +2 -0
  107. package/dist/types/components/data-display/DataViews/data-views-grid-parts.d.ts.map +1 -1
  108. package/dist/types/components/data-display/DataViews/data-views-types.d.ts +2 -1
  109. package/dist/types/components/data-display/DataViews/data-views-types.d.ts.map +1 -1
  110. package/dist/types/components/data-display/Sheet/Sheet.parts.d.ts.map +1 -1
  111. package/dist/types/components/data-display/Sheet/Sheet.types.d.ts +2 -0
  112. package/dist/types/components/data-display/Sheet/Sheet.types.d.ts.map +1 -1
  113. package/package.json +1 -1
  114. package/src/components/data-display/DataViews/DataViewsBoard.tsx +4 -3
  115. package/src/components/data-display/DataViews/DataViewsGrid.tsx +10 -0
  116. package/src/components/data-display/DataViews/DataViewsTableBase.tsx +7 -0
  117. package/src/components/data-display/DataViews/RESPONSIVE-BEHAVIOUR.md +27 -1
  118. package/src/components/data-display/DataViews/data-views-card-body.tsx +73 -0
  119. package/src/components/data-display/DataViews/data-views-grid-bodies.tsx +29 -77
  120. package/src/components/data-display/DataViews/data-views-grid-parts.tsx +3 -1
  121. package/src/components/data-display/DataViews/data-views-types.ts +2 -1
  122. package/src/components/data-display/Sheet/Sheet.md +1 -1
  123. package/src/components/data-display/Sheet/Sheet.parts.tsx +4 -2
  124. package/src/components/data-display/Sheet/Sheet.tsx +1 -1
  125. package/src/components/data-display/Sheet/Sheet.types.ts +2 -0
  126. package/dist/chunk-GGG6TDET.js.map +0 -1
  127. package/dist/chunk-GVEWFA3Z.js.map +0 -1
  128. package/dist/chunk-LHHAYZ55.js.map +0 -1
  129. package/dist/chunk-NHHWNA5W.js.map +0 -1
  130. package/dist/chunk-YLBZ3SKU.js.map +0 -1
  131. package/dist/chunk-YOEZTW4T.js.map +0 -1
  132. /package/dist/{chunk-FL2DRDTS.js.map → chunk-2VMIHFMX.js.map} +0 -0
  133. /package/dist/{chunk-OR6JKOQ5.js.map → chunk-33XGC4YU.js.map} +0 -0
  134. /package/dist/{chunk-BQNYX3VM.js.map → chunk-35L5YMYQ.js.map} +0 -0
  135. /package/dist/{chunk-LDCHE6QS.js.map → chunk-3WLS7Z7D.js.map} +0 -0
  136. /package/dist/{chunk-VUVPEKDE.js.map → chunk-5MMAV7XK.js.map} +0 -0
  137. /package/dist/{chunk-RNLLDK5L.js.map → chunk-67XUBKJC.js.map} +0 -0
  138. /package/dist/{chunk-CZVAVX5L.js.map → chunk-6ATXOJVQ.js.map} +0 -0
  139. /package/dist/{chunk-GLBK6X5Z.js.map → chunk-7J5JR33K.js.map} +0 -0
  140. /package/dist/{chunk-ANJAAGN3.js.map → chunk-AKGXNC4R.js.map} +0 -0
  141. /package/dist/{chunk-5357QUVE.js.map → chunk-AMLHEKSV.js.map} +0 -0
  142. /package/dist/{chunk-HR7YTBR7.js.map → chunk-BI7YMVKZ.js.map} +0 -0
  143. /package/dist/{chunk-OZGKIHAA.js.map → chunk-BRBQNPM2.js.map} +0 -0
  144. /package/dist/{chunk-ULBOCFST.js.map → chunk-J6DYHBID.js.map} +0 -0
  145. /package/dist/{chunk-MQNL5UME.js.map → chunk-JXW6DZDN.js.map} +0 -0
  146. /package/dist/{chunk-MO52X5WW.js.map → chunk-JYMXYSDP.js.map} +0 -0
  147. /package/dist/{chunk-PEHYS7TU.js.map → chunk-K6KBASL2.js.map} +0 -0
  148. /package/dist/{chunk-EMHG2ZQN.js.map → chunk-KDQEJAKH.js.map} +0 -0
  149. /package/dist/{chunk-T3SZYWWN.js.map → chunk-L6MGAPBX.js.map} +0 -0
  150. /package/dist/{chunk-5YDTSJNY.js.map → chunk-LMIOWP6H.js.map} +0 -0
  151. /package/dist/{chunk-6DO672AG.js.map → chunk-N5PVD4J5.js.map} +0 -0
  152. /package/dist/{chunk-NMGNKIXF.js.map → chunk-QQWW6T4Q.js.map} +0 -0
  153. /package/dist/{chunk-R52N5UCS.js.map → chunk-SB2SEGR6.js.map} +0 -0
  154. /package/dist/{chunk-OT5JBIBT.js.map → chunk-SUZLGB5I.js.map} +0 -0
  155. /package/dist/{chunk-JG733RMP.js.map → chunk-TZC6KIK2.js.map} +0 -0
  156. /package/dist/{chunk-B7PKBFA4.js.map → chunk-UZ2CEJOO.js.map} +0 -0
  157. /package/dist/{chunk-E2VVEXEX.js.map → chunk-VJB63DUM.js.map} +0 -0
  158. /package/dist/{chunk-CJ7KAM3S.js.map → chunk-XBTSKYYY.js.map} +0 -0
  159. /package/dist/{chunk-IZBQL4HZ.js.map → chunk-ZDT4ZGP4.js.map} +0 -0
@@ -13,8 +13,8 @@ import {
13
13
  useDataViewsLayout,
14
14
  type DataViewsDensity,
15
15
  } from "./data-views-layout-context";
16
- import { cardGridTracks } from "./data-views-grid-helpers";
17
16
  import { DataViewsBoard, type BoardConfig } from "./DataViewsBoard";
17
+ import { CardBody } from "./data-views-card-body";
18
18
  import { SelectAllStrip } from "./data-views-select-all-strip";
19
19
  import { ListCardGroup, type ListGroupConfig } from "./list-card-rails";
20
20
  import type { DataViewCardSelection } from "./data-views-types";
@@ -31,6 +31,8 @@ interface GridBodyProps<T extends Record<string, unknown>> {
31
31
  getRowId: (row: T) => string | number;
32
32
  selectedIds: Array<string | number>;
33
33
  onChangeSelected: (ids: Array<string | number>) => void;
34
+ /** False ⇒ no checkbox column at all: no header select-all, no row box, no width. */
35
+ selectable: boolean;
34
36
  sortBy: GridSort[];
35
37
  onChangeSortBy: (next: GridSort[]) => void;
36
38
  /** "server" defers ordering to the backend (rows render as-is); "client" sorts in-grid. */
@@ -42,13 +44,14 @@ interface GridBodyProps<T extends Record<string, unknown>> {
42
44
  emptyState?: React.ReactNode;
43
45
  }
44
46
 
45
- /** The dense DataGrid with multi-select, wrapped in the scrollable table region. */
47
+ /** The dense DataGrid with multi-select (unless opted out), wrapped in the scrollable table region. */
46
48
  function GridBody<T extends Record<string, unknown>>({
47
49
  rows,
48
50
  columns,
49
51
  getRowId,
50
52
  selectedIds,
51
53
  onChangeSelected,
54
+ selectable,
52
55
  sortBy,
53
56
  onChangeSortBy,
54
57
  sortMode,
@@ -79,7 +82,7 @@ function GridBody<T extends Record<string, unknown>>({
79
82
  density="compact"
80
83
  rowHeight={36}
81
84
  headerHeight={36}
82
- selection={{ mode: "multi", selectedRowIds: selectedIds, onChangeSelected }}
85
+ selection={selectable ? { mode: "multi", selectedRowIds: selectedIds, onChangeSelected } : { mode: "none" }}
83
86
  sorting={{ mode: sortMode, sortBy, onChangeSortBy }}
84
87
  expansion={toGridExpansion(rowDetail, copy)}
85
88
  data-testid={dataTestId}
@@ -91,71 +94,6 @@ function GridBody<T extends Record<string, unknown>>({
91
94
  );
92
95
  }
93
96
 
94
- /* ── Body (cards) ────────────────────────────────────────────────────────── */
95
-
96
- interface CardBodyProps<T extends Record<string, unknown>> {
97
- rows: T[];
98
- renderCard: (row: T, selection: DataViewCardSelection) => React.ReactNode;
99
- getRowId: (row: T) => string | number;
100
- selectedIds: Set<string | number>;
101
- onToggleId: (id: string | number) => void;
102
- /** The width each card asks for; `auto-fill` turns it into a column count. */
103
- targetCardWidth: number;
104
- /** Content scale (padding + type) handed to each card, from the zoom slider. */
105
- cardScale: number;
106
- dataTestId?: string;
107
- emptyState?: React.ReactNode;
108
- }
109
-
110
- /**
111
- * The "Grade" layout: the filtered/sorted rows rendered as an auto-filling grid
112
- * of entity-supplied cards. Reuses `rows` (= `c.matched`), so search/filter/sort
113
- * apply; the column width comes from the zoom slider, and each card is handed its
114
- * selection state so it can drive its own checkbox (BaseCard) — the same
115
- * selection model as the table.
116
- */
117
- function CardBody<T extends Record<string, unknown>>({
118
- rows,
119
- renderCard,
120
- getRowId,
121
- selectedIds,
122
- onToggleId,
123
- targetCardWidth,
124
- cardScale,
125
- dataTestId,
126
- emptyState,
127
- }: CardBodyProps<T>): React.JSX.Element {
128
- if (rows.length === 0) {
129
- return <Box sx={{ mt: 1.5 }}>{emptyState}</Box>;
130
- }
131
- return (
132
- <Box
133
- sx={{
134
- mt: 1.5,
135
- display: "grid",
136
- // Fixed inter-card gap — deliberately NOT scaled by the zoom slider, so
137
- // only the cards grow while the space between them stays constant.
138
- gap: 1.5,
139
- ...cardGridTracks(targetCardWidth),
140
- }}
141
- data-testid={dataTestId ? `${dataTestId}-cards` : "data-views-cards"}
142
- >
143
- {rows.map((row) => {
144
- const id = getRowId(row);
145
- return (
146
- <Box key={id}>
147
- {renderCard(row, {
148
- selected: selectedIds.has(id),
149
- onToggleSelect: () => onToggleId(id),
150
- scale: cardScale,
151
- })}
152
- </Box>
153
- );
154
- })}
155
- </Box>
156
- );
157
- }
158
-
159
97
  /* ── Body (list) ─────────────────────────────────────────────────────────── */
160
98
 
161
99
  interface ListBodyProps<T extends Record<string, unknown>> {
@@ -163,7 +101,8 @@ interface ListBodyProps<T extends Record<string, unknown>> {
163
101
  renderListRow: (row: T, selection: DataViewCardSelection) => React.ReactNode;
164
102
  getRowId: (row: T) => string | number;
165
103
  selectedIds: Set<string | number>;
166
- onToggleId: (id: string | number) => void;
104
+ /** Absent on a grid that is not selectable: rows get no toggle, so no checkbox. */
105
+ onToggleId?: (id: string | number) => void;
167
106
  /** Gap between rows, from the density preference. */
168
107
  rowGap: number;
169
108
  /** The list's shared column config. See {@link ListGroupConfig}. */
@@ -207,7 +146,7 @@ function ListBody<T extends Record<string, unknown>>({
207
146
  const testId = dataTestId ? `${dataTestId}-list` : "data-views-list";
208
147
  const selectionFor = (id: string | number): DataViewCardSelection => ({
209
148
  selected: selectedIds.has(id),
210
- onToggleSelect: () => onToggleId(id),
149
+ onToggleSelect: onToggleId && (() => onToggleId(id)),
211
150
  scale: 1,
212
151
  });
213
152
 
@@ -218,7 +157,10 @@ function ListBody<T extends Record<string, unknown>>({
218
157
  cells={group.cells}
219
158
  metaColumns={group.metaColumns}
220
159
  rails={group.rails}
221
- reserveGutters={group.reserveGutters}
160
+ // A list nobody can select holds no checkbox gutter open: reserving
161
+ // one is how a selectable and a read-only row line up, and here no
162
+ // row is selectable. Gutters a row DOES use still get their track.
163
+ reserveGutters={onToggleId ? group.reserveGutters : false}
222
164
  density={density}
223
165
  gap={rowGap}
224
166
  dataTestId={testId}
@@ -268,6 +210,8 @@ interface GridMainProps<T extends Record<string, unknown>> {
268
210
  board?: BoardConfig<T>;
269
211
  /** Opt-in expandable rows — the TABLE only; the headerless layouts ignore it. */
270
212
  rowDetail?: DataViewRowDetail<T>;
213
+ /** False ⇒ no selection anywhere: no checkboxes, no select-all strip. */
214
+ selectable: boolean;
271
215
  dataTestId?: string;
272
216
  emptyState?: React.ReactNode;
273
217
  testIdPrefix: string;
@@ -283,14 +227,18 @@ interface GridMainProps<T extends Record<string, unknown>> {
283
227
  function Headerless<T extends Record<string, unknown>>({
284
228
  c,
285
229
  getRowId,
230
+ selectable,
286
231
  testIdPrefix,
287
232
  children,
288
233
  }: {
289
234
  c: DataViewsController<T>;
290
235
  getRowId: (row: T) => string | number;
236
+ selectable: boolean;
291
237
  testIdPrefix: string;
292
238
  children: React.ReactNode;
293
239
  }): React.JSX.Element {
240
+ // Not selectable ⇒ no strip: a select-all over rows that cannot be selected.
241
+ if (!selectable) return <>{children}</>;
294
242
  return (
295
243
  <>
296
244
  <SelectAllStrip
@@ -313,9 +261,12 @@ function Headerless<T extends Record<string, unknown>>({
313
261
  * which is also what keeps either function inside the line budget.
314
262
  */
315
263
  function headerlessBody<T extends Record<string, unknown>>(
316
- { c, getRowId, renderCard, renderListRow, listGroup, board, dataTestId, emptyState }: GridMainProps<T>,
264
+ { c, getRowId, renderCard, renderListRow, listGroup, board, selectable, dataTestId, emptyState }: GridMainProps<T>,
317
265
  { layout, zoom, density }: ReturnType<typeof useDataViewsLayout>,
318
266
  ): React.ReactNode | null {
267
+ // ONE switch for every headerless body: no toggle reaches a card or row, and
268
+ // BaseCard / BaseListCard draw no checkbox for a record without one.
269
+ const onToggleId = selectable ? c.toggleId : undefined;
319
270
  if (layout === "list" && renderListRow) {
320
271
  return (
321
272
  <ListBody
@@ -323,7 +274,7 @@ function headerlessBody<T extends Record<string, unknown>>(
323
274
  renderListRow={renderListRow}
324
275
  getRowId={getRowId}
325
276
  selectedIds={c.selectedIds}
326
- onToggleId={c.toggleId}
277
+ onToggleId={onToggleId}
327
278
  rowGap={DENSITY_ROW_PADDING[density]}
328
279
  group={listGroup}
329
280
  density={density}
@@ -340,7 +291,7 @@ function headerlessBody<T extends Record<string, unknown>>(
340
291
  getRowId={getRowId}
341
292
  renderCard={renderCard}
342
293
  selectedIds={c.selectedIds}
343
- onToggleId={c.toggleId}
294
+ onToggleId={onToggleId}
344
295
  // DENSITY, not zoom: the board's own knob in the Exibição tab is how
345
296
  // wide its columns are, and it is the only sizing control the board
346
297
  // has since the zoom slider was removed.
@@ -356,7 +307,7 @@ function headerlessBody<T extends Record<string, unknown>>(
356
307
  renderCard={renderCard}
357
308
  getRowId={getRowId}
358
309
  selectedIds={c.selectedIds}
359
- onToggleId={c.toggleId}
310
+ onToggleId={onToggleId}
360
311
  targetCardWidth={cardTargetWidthFor(zoom, density)}
361
312
  cardScale={cardScaleForZoom(zoom)}
362
313
  dataTestId={dataTestId}
@@ -369,12 +320,12 @@ function headerlessBody<T extends Record<string, unknown>>(
369
320
 
370
321
  /** Picks the body from the layout context: board, list, cards, or the dense grid. */
371
322
  export function GridMain<T extends Record<string, unknown>>(props: GridMainProps<T>): React.JSX.Element {
372
- const { c, getRowId, dataTestId, emptyState, testIdPrefix } = props;
323
+ const { c, getRowId, selectable, dataTestId, emptyState, testIdPrefix } = props;
373
324
  const layoutState = useDataViewsLayout();
374
325
  const body = headerlessBody(props, layoutState);
375
326
  if (body) {
376
327
  return (
377
- <Headerless c={c} getRowId={getRowId} testIdPrefix={testIdPrefix}>
328
+ <Headerless c={c} getRowId={getRowId} selectable={selectable} testIdPrefix={testIdPrefix}>
378
329
  {body}
379
330
  </Headerless>
380
331
  );
@@ -386,6 +337,7 @@ export function GridMain<T extends Record<string, unknown>>(props: GridMainProps
386
337
  getRowId={getRowId}
387
338
  selectedIds={[...c.selectedIds]}
388
339
  onChangeSelected={(ids) => c.setSelectedIds(new Set(ids))}
340
+ selectable={selectable}
389
341
  sortBy={c.state.sortBy}
390
342
  onChangeSortBy={(next: GridSort[]) => c.patch({ sortBy: next })}
391
343
  sortMode={c.serverMode ? "server" : "client"}
@@ -200,6 +200,8 @@ interface GridShellProps<T extends Record<string, unknown>> {
200
200
  listGroup?: ListGroupConfig<T>;
201
201
  /** Opt-in expandable rows, drawn by the table layout only. */
202
202
  rowDetail?: DataViewRowDetail<T>;
203
+ /** Row selection, on by default. False ⇒ no checkboxes in any layout, no select-all. */
204
+ selectable?: boolean;
203
205
  /** The page-level partition rendered as tabs under the toolbar. */
204
206
  scopes?: ScopeConfig[];
205
207
  /**
@@ -304,7 +306,7 @@ function ShellStack<T extends Record<string, unknown>>({
304
306
  renderListRow={props.renderListRow}
305
307
  listGroup={props.listGroup}
306
308
  board={props.board}
307
- rowDetail={props.rowDetail}
309
+ rowDetail={props.rowDetail} selectable={props.selectable !== false}
308
310
  dataTestId={dataTestId}
309
311
  testIdPrefix={testIdPrefix}
310
312
  emptyState={body}
@@ -48,7 +48,8 @@ export interface RowAction<T extends Record<string, unknown>> {
48
48
  /** Selection state handed to a card renderer so it can drive its own checkbox. */
49
49
  export interface DataViewCardSelection {
50
50
  selected: boolean;
51
- onToggleSelect: () => void;
51
+ /** Absent when the grid is `selectable={false}`: pass it on as-is, and no checkbox is drawn. */
52
+ onToggleSelect?: () => void;
52
53
  /**
53
54
  * Card SIZE multiplier from the toolbar zoom slider. The grid sizes the card's
54
55
  * width to `base × scale`; a card passes this to {@link BaseCard} `scale` so its
@@ -41,7 +41,7 @@ A vertical sheet's **width** is one rule rather than one per preset: `min(100%,
41
41
  - **pulse** (boolean): Enable pulse animation effect
42
42
  - **rounded** (boolean): Apply rounded corners to the sheet
43
43
  - **elevation** (number): Shadow depth level (0-24)
44
- - **animated** (boolean, default true): `false` opens and closes the panel at once, with no slide
44
+ - **animated** (boolean, default true): `false` opens and closes the panel at once, with no slide, and shows and hides its veil with no fade (`SheetOverlay` takes the same prop)
45
45
 
46
46
  ### Interactive Behavior
47
47
 
@@ -212,13 +212,15 @@ const OVERLAY_DEFAULTS: Partial<SheetOverlayProps> = {
212
212
  };
213
213
 
214
214
  export const SheetOverlay: React.FC<SheetOverlayProps> = (props) => {
215
- const { open, onClick, className, style, blur } = withDefaults(props, OVERLAY_DEFAULTS);
215
+ const { open, onClick, className, style, blur, animated } = withDefaults(props, OVERLAY_DEFAULTS);
216
216
  const theme = useTheme();
217
+ const timeout = animated === false ? 0 : 300;
217
218
 
218
219
  return (
219
- <Fade in={open} timeout={300}>
220
+ <Fade in={open} timeout={timeout}>
220
221
  <Backdrop
221
222
  open={Boolean(open)}
223
+ transitionDuration={timeout}
222
224
  onClick={onClick}
223
225
  className={className}
224
226
  sx={{
@@ -162,7 +162,7 @@ export const Sheet: React.FC<SheetProps> = (props) => {
162
162
  return (
163
163
  <>
164
164
  {showOverlay && isOpen && (
165
- <SheetOverlay open={isOpen} onClick={handleOverlayClick} blur={variant === 'glass'} />
165
+ <SheetOverlay open={isOpen} onClick={handleOverlayClick} blur={variant === 'glass'} animated={resolved.animated} />
166
166
  )}
167
167
 
168
168
  {isSwipeable ? (
@@ -97,4 +97,6 @@ export interface SheetOverlayProps {
97
97
  className?: string;
98
98
  style?: CSSProperties;
99
99
  blur?: boolean;
100
+ /** False: the veil appears and leaves at once, with no fade. */
101
+ animated?: boolean;
100
102
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/DataViewsGrid.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nimport { type SortFieldDefinition } from \"../../layout/ContentToolbar\";\n\nimport { GridShell } from \"./data-views-grid-parts\";\nimport type { SelectionExtraRender } from \"./data-views-selection-extra\";\nimport type { ListGroupConfig } from \"./list-card-rails\";\nimport type { DisplayPanelView } from \"./data-views-display-panel\";\nimport type { BoardConfig } from \"./DataViewsBoard\";\nimport type { DataViewExport } from \"./data-views-export\";\nimport type { ScopeConfig } from \"./data-views-scopes\";\nimport type { DataViewCardSelection } from \"./data-views-types\";\nimport {\n type DataViewColumn,\n type DataViewsLayout,\n type DataViewServer,\n type DataViewState,\n type DataViewSyncState,\n type FilterFieldConfig,\n type RangeFieldConfig,\n type RowAction,\n} from \"./data-views-types\";\nimport type { DataViewRowDetail } from \"./data-views-row-detail\";\nimport { useDataViewsState } from \"./use-data-views-state\";\n\ninterface DataViewsGridProps<T extends Record<string, unknown>> {\n rows: T[];\n columns: DataViewColumn<T>[];\n fields: FilterFieldConfig<T>[];\n /** Optional numeric min/max range filters (advanced \"filter by amount\"). */\n rangeFields?: RangeFieldConfig<T>[];\n getRowId: (row: T) => string | number;\n /** Row click opens the row; clicks in the actions column are ignored. */\n onRowClick?: (row: T) => void;\n emptyState?: React.ReactNode;\n dataTestId?: string;\n testIdPrefix?: string;\n /** Seed/replace the view state. A NEW reference re-applies (used to apply a saved view). */\n appliedState?: DataViewState;\n /**\n * URL-driven controls (search/pills/ranges/sort) that RE-APPLY on reference\n * change while preserving column visibility — for server-mode lists that keep\n * their filter/sort/page in the address bar (see {@link DataViewSyncState}).\n */\n syncState?: DataViewSyncState;\n /** Notified on every view-state change (so a save-view modal can read it). */\n onStateChange?: (state: DataViewState) => void;\n /**\n * Notified with the currently-visible (search + filter matched) rows whenever\n * they change — so a page-level Export exports what the user sees, not the\n * full dataset.\n */\n onVisibleRowsChange?: (rows: T[]) => void;\n /** Saved-views UI injected into the toolbar (FUT-89). */\n toolbarRightSlot?: React.ReactNode;\n /**\n * The saved-view chrome for the Exibir panel: its VISÃO header (with the\n * views dropdown) and its Redefinir / Salvar visão footer. Supplied by\n * `DataViewsTableBase`; without it the panel is tabs only.\n */\n displayView?: DisplayPanelView;\n /** Big page title in the header row (next to the funnel + gear). */\n title?: string;\n /** Primary page actions rendered at the header's right. */\n headerActions?: React.ReactNode;\n /**\n * Sort options for the toolbar's \"Sort By\" dropdown. Defaults to the sortable\n * columns (asc/desc each) when omitted.\n */\n sortFields?: SortFieldDefinition[];\n /**\n * Per-sort-field VALUE KIND (`\"currency\" | \"number\" | \"date\" | \"text\"`), so the\n * Exibir panel phrases the direction in that column's own terms. \"Crescente\"\n * on a currency column is a puzzle; \"Menor → maior\" is not. Defaults to text.\n */\n sortKinds?: Record<string, string>;\n /**\n * Opt-in \"Exportar\" control. The grid hands the host the current query,\n * UNPAGINATED, and the host re-queries — the grid never fetches, and an\n * export therefore follows the filters rather than the loaded page.\n */\n exportConfig?: DataViewExport;\n /**\n * A single, reusable list of row actions that drives BOTH the per-row \"⋮\"\n * kebab AND the bulk-actions menu (tabwoah model). When provided, the grid\n * appends a kebab actions column automatically (unless the page already\n * defines one) and shows an \"Ações\" bulk menu when rows are selected.\n */\n rowActions?: RowAction<T>[];\n /**\n * Extra leading control rendered in the auto kebab column, before the \"⋮\"\n * (e.g. a favourite star). Only used when `rowActions` is set.\n */\n rowActionsLeading?: (row: T) => React.ReactNode;\n /**\n * Low-level override for the selection bulk control. Prefer `rowActions`.\n * Receives the selected rows + a `clearSelection` callback.\n */\n bulkActions?: (selectedRows: T[], clearSelection: () => void) => React.ReactNode;\n /**\n * A control rendered beside the \"N selected\" count, for changing WHAT is\n * selected rather than what happens to it — a \"select all N matching the\n * filter\" widening, above all.\n *\n * Called only while something is selected, and handed\n * {@link SelectionExtraContext.allOnPageSelected} so the control can follow\n * the convention operators already know: the widening appears once the whole\n * page is ticked, and not before.\n */\n selectionExtra?: SelectionExtraRender<T>;\n /**\n * Render a bespoke per-row menu in the actions column instead of the auto\n * kebab — typically an entity's self-contained 3-dots menu that owns its own\n * popups, so the SAME menu component drives both the table row and the card.\n * `rowActions`/`bulkActions` still drive the multi-row bulk menu.\n */\n renderRowMenu?: (row: T) => React.ReactNode;\n /**\n * Opt-in expandable rows (table layout only): a chevron in a leading column\n * opens `render(row)` under the row. See {@link DataViewRowDetail}.\n */\n rowDetail?: DataViewRowDetail<T>;\n /**\n * Opt-in \"Grade\" (cards) layout. When provided, a Grade/Tabela toggle appears in\n * the toolbar and the cards body renders each visible row via this function\n * (reusing the same search/filter/sort). Omit ⇒ table only (unchanged).\n */\n renderCard?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /**\n * Opt-in \"Quadro\" (board) layout: the loaded page laid out as columns of one\n * grouping field. Reuses `renderCard`, so supplying a board WITHOUT one simply\n * offers no board. Best when the groups are states in a state machine — derive\n * these groups and `scopes` from ONE definition so the two cannot drift.\n */\n board?: BoardConfig<T>;\n /**\n * Opt-in \"Lista\" layout (FUT-733): one FULL-WIDTH row per record, rendered by\n * the entity — a marker, a title, a subtitle and a value on the right. Sits\n * between the table (compare columns) and the cards (browse); omit ⇒ not\n * offered, exactly as `renderCard` gates the cards.\n */\n renderListRow?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /**\n * The Lista's SHARED COLUMNS — declared once for the list, obeyed by every row.\n *\n * This is what makes a list line up by construction rather than by convention.\n * Omit it and each row still resolves its own tracks from its own config,\n * which agrees with its neighbours right up until one row carries an unusually\n * wide value.\n */\n listGroup?: ListGroupConfig<T>;\n /**\n * The page-level partition rendered as an exclusive tab strip above the\n * toolbar, with server-supplied counts. Requires `server`: a scope is applied\n * at the backend, never in the browser.\n */\n scopes?: ScopeConfig[];\n /** The row field the scopes partition by — used only to reject a pill over the same field. */\n scopeFieldId?: string;\n /** Which layout to show first when the user has expressed no preference (default \"table\"). */\n defaultLayout?: DataViewsLayout;\n /**\n * Ignore the remembered cross-screen layout preference and pin to\n * `defaultLayout`. For tables that exist to SHOW a layout — stories, docs,\n * screenshots — not for real screens.\n */\n ignoreStoredLayout?: boolean;\n /**\n * Opt into the responsive inline filter UX (collapsible filter row on wide\n * screens, modal on narrow). Default `false` keeps the classic slide-in panel.\n */\n inlineFilters?: boolean;\n /** Keep the inline search visible with no filter fields and no active search. */\n alwaysShowSearch?: boolean;\n /** Server-mode wiring; when set the grid is backend-driven (no client filter/sort/paginate). */\n server?: DataViewServer;\n}\n\n/**\n * Surface the visible (filtered) rows so a page-level Export matches the view.\n *\n * Pages pass inline column/field arrays, so `matched` is a fresh reference on\n * every render even when its contents are unchanged. The guard is a signature\n * over the rows' *contents* (not just their ids), so the callback — and the\n * parent `useState` it usually drives — fires only when the visible data\n * actually changes. Ids alone are not enough: a server refresh can change a\n * visible row's values while keeping its id, and the export must reflect them.\n * Content-equal renders keep the same signature, so this never loops.\n */\nfunction useVisibleRowsNotifier<T>(matched: T[], onVisibleRowsChange?: (rows: T[]) => void): void {\n const visibleSignature = JSON.stringify(matched);\n const lastSignature = useRef<string | null>(null);\n useEffect(() => {\n if (lastSignature.current === visibleSignature) return;\n lastSignature.current = visibleSignature;\n onVisibleRowsChange?.(matched);\n }, [visibleSignature, matched, onVisibleRowsChange]);\n}\n\n/**\n * The reusable admin DataViews table (FUT-88): the shared ContentToolbar +\n * TableFilter panel over `@12-apps/ui` DataGrid. State + derived data live in\n * {@link useDataViewsState}; the render lives in `GridShell`. This component just\n * connects the two. Reused across every admin list page.\n */\nexport function DataViewsGrid<T extends Record<string, unknown>>(\n props: DataViewsGridProps<T>,\n): React.JSX.Element {\n // Forwarded as a whole rather than restated name-by-name twice: this component\n // only ADDS the controller, and a 25-name destructure repeated in the JSX is\n // where a newly added prop silently stops being passed on.\n const { rangeFields = [], testIdPrefix = \"table\" } = props;\n const controller = useDataViewsState({ ...props, rangeFields, testIdPrefix });\n useVisibleRowsNotifier(controller.matched, props.onVisibleRowsChange);\n return <GridShell {...props} c={controller} rangeFields={rangeFields} testIdPrefix={testIdPrefix} />;\n}\n"],"mappings":";;;;;;;;AAEA,SAAS,WAAW,cAAc;AAsNzB;AAzBT,SAAS,uBAA0B,SAAc,qBAAiD;AAChG,QAAM,mBAAmB,KAAK,UAAU,OAAO;AAC/C,QAAM,gBAAgB,OAAsB,IAAI;AAChD,YAAU,MAAM;AACd,QAAI,cAAc,YAAY,iBAAkB;AAChD,kBAAc,UAAU;AACxB,0BAAsB,OAAO;AAAA,EAC/B,GAAG,CAAC,kBAAkB,SAAS,mBAAmB,CAAC;AACrD;AAQO,SAAS,cACd,OACmB;AAInB,QAAM,EAAE,cAAc,CAAC,GAAG,eAAe,QAAQ,IAAI;AACrD,QAAM,aAAa,kBAAkB,EAAE,GAAG,OAAO,aAAa,aAAa,CAAC;AAC5E,yBAAuB,WAAW,SAAS,MAAM,mBAAmB;AACpE,SAAO,oBAAC,aAAW,GAAG,OAAO,GAAG,YAAY,aAA0B,cAA4B;AACpG;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/data-views-grid-bodies.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\n\nimport { DataGrid, type GridColumn, type GridSort } from \"../DataGrid\";\nimport { Box } from \"../../../mui/Box\";\n\nimport {\n cardTargetWidthFor,\n cardScaleForZoom,\n DENSITY_BOARD_SCALE,\n DENSITY_ROW_PADDING,\n useDataViewsLayout,\n type DataViewsDensity,\n} from \"./data-views-layout-context\";\nimport { cardGridTracks } from \"./data-views-grid-helpers\";\nimport { DataViewsBoard, type BoardConfig } from \"./DataViewsBoard\";\nimport { SelectAllStrip } from \"./data-views-select-all-strip\";\nimport { ListCardGroup, type ListGroupConfig } from \"./list-card-rails\";\nimport type { DataViewCardSelection } from \"./data-views-types\";\nimport { toGridExpansion, type DataViewRowDetail } from \"./data-views-row-detail\";\nimport type { DataViewsController } from \"./use-data-views-state\";\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport { sxRem } from \"../../../tokens/relative\";\n\n/* ── Body (grid) ─────────────────────────────────────────────────────────── */\n\ninterface GridBodyProps<T extends Record<string, unknown>> {\n rows: T[];\n columns: GridColumn<T>[];\n getRowId: (row: T) => string | number;\n selectedIds: Array<string | number>;\n onChangeSelected: (ids: Array<string | number>) => void;\n sortBy: GridSort[];\n onChangeSortBy: (next: GridSort[]) => void;\n /** \"server\" defers ordering to the backend (rows render as-is); \"client\" sorts in-grid. */\n sortMode: \"client\" | \"server\";\n /** Vertical cell padding, from the density preference. */\n rowPadding: number;\n rowDetail?: DataViewRowDetail<T>;\n dataTestId?: string;\n emptyState?: React.ReactNode;\n}\n\n/** The dense DataGrid with multi-select, wrapped in the scrollable table region. */\nfunction GridBody<T extends Record<string, unknown>>({\n rows,\n columns,\n getRowId,\n selectedIds,\n onChangeSelected,\n sortBy,\n onChangeSortBy,\n sortMode,\n rowPadding,\n rowDetail,\n dataTestId,\n emptyState,\n}: GridBodyProps<T>): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <Box\n sx={{\n width: \"100%\",\n overflowX: \"auto\",\n mt: 1.5,\n // Tabwoah-style dense rows: MUI's default TableCell padding keeps rows\n // tall regardless of rowHeight, so the DENSITY preference is applied\n // here rather than through `rowHeight`, which it would fight.\n \"& .MuiTableCell-root\": { py: rowPadding, fontSize: sxRem(13) },\n \"& .MuiTableCell-head\": { py: 0.5, fontSize: sxRem(12) },\n }}\n >\n <DataGrid<T>\n rows={rows}\n columns={columns}\n getRowId={(row) => getRowId(row)}\n virtualizeRows={false}\n density=\"compact\"\n rowHeight={36}\n headerHeight={36}\n selection={{ mode: \"multi\", selectedRowIds: selectedIds, onChangeSelected }}\n sorting={{ mode: sortMode, sortBy, onChangeSortBy }}\n expansion={toGridExpansion(rowDetail, copy)}\n data-testid={dataTestId}\n emptyState={emptyState}\n emptyText={copy.grid.emptyFilteredTitle}\n copy={copy.grid}\n />\n </Box>\n );\n}\n\n/* ── Body (cards) ────────────────────────────────────────────────────────── */\n\ninterface CardBodyProps<T extends Record<string, unknown>> {\n rows: T[];\n renderCard: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n getRowId: (row: T) => string | number;\n selectedIds: Set<string | number>;\n onToggleId: (id: string | number) => void;\n /** The width each card asks for; `auto-fill` turns it into a column count. */\n targetCardWidth: number;\n /** Content scale (padding + type) handed to each card, from the zoom slider. */\n cardScale: number;\n dataTestId?: string;\n emptyState?: React.ReactNode;\n}\n\n/**\n * The \"Grade\" layout: the filtered/sorted rows rendered as an auto-filling grid\n * of entity-supplied cards. Reuses `rows` (= `c.matched`), so search/filter/sort\n * apply; the column width comes from the zoom slider, and each card is handed its\n * selection state so it can drive its own checkbox (BaseCard) — the same\n * selection model as the table.\n */\nfunction CardBody<T extends Record<string, unknown>>({\n rows,\n renderCard,\n getRowId,\n selectedIds,\n onToggleId,\n targetCardWidth,\n cardScale,\n dataTestId,\n emptyState,\n}: CardBodyProps<T>): React.JSX.Element {\n if (rows.length === 0) {\n return <Box sx={{ mt: 1.5 }}>{emptyState}</Box>;\n }\n return (\n <Box\n sx={{\n mt: 1.5,\n display: \"grid\",\n // Fixed inter-card gap — deliberately NOT scaled by the zoom slider, so\n // only the cards grow while the space between them stays constant.\n gap: 1.5,\n ...cardGridTracks(targetCardWidth),\n }}\n data-testid={dataTestId ? `${dataTestId}-cards` : \"data-views-cards\"}\n >\n {rows.map((row) => {\n const id = getRowId(row);\n return (\n <Box key={id}>\n {renderCard(row, {\n selected: selectedIds.has(id),\n onToggleSelect: () => onToggleId(id),\n scale: cardScale,\n })}\n </Box>\n );\n })}\n </Box>\n );\n}\n\n/* ── Body (list) ─────────────────────────────────────────────────────────── */\n\ninterface ListBodyProps<T extends Record<string, unknown>> {\n rows: T[];\n renderListRow: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n getRowId: (row: T) => string | number;\n selectedIds: Set<string | number>;\n onToggleId: (id: string | number) => void;\n /** Gap between rows, from the density preference. */\n rowGap: number;\n /** The list's shared column config. See {@link ListGroupConfig}. */\n group?: ListGroupConfig<T>;\n /** The density every row answers, handed to the group when there is one. */\n density: DataViewsDensity;\n dataTestId?: string;\n emptyState?: React.ReactNode;\n}\n\n/**\n * The \"Lista\" layout: one FULL-WIDTH row per record, rendered by the entity — a\n * marker, a title, a subtitle and a value on the right is the shape it was\n * designed for (FUT-733).\n *\n * It sits between the table and the cards rather than replacing either: the\n * table is for comparing many columns, the cards are for browsing, and the list\n * is for scanning a queue on a narrow screen. Selection is the SAME model as\n * both — each row receives its selection state, so it can drive its own\n * checkbox and the bulk menu behaves identically in every layout.\n *\n * `scale` is handed over as 1 rather than the zoom multiplier, and the zoom\n * slider is hidden in this layout: a full-width row has no card size to\n * multiply. See {@link DataViewsZoomSlider}.\n */\nfunction ListBody<T extends Record<string, unknown>>({\n rows,\n renderListRow,\n getRowId,\n selectedIds,\n onToggleId,\n rowGap,\n group,\n density,\n dataTestId,\n emptyState,\n}: ListBodyProps<T>): React.JSX.Element {\n if (rows.length === 0) {\n return <Box sx={{ mt: 1.5 }}>{emptyState}</Box>;\n }\n const testId = dataTestId ? `${dataTestId}-list` : \"data-views-list\";\n const selectionFor = (id: string | number): DataViewCardSelection => ({\n selected: selectedIds.has(id),\n onToggleSelect: () => onToggleId(id),\n scale: 1,\n });\n\n if (group) {\n return (\n <Box sx={{ mt: 1.5 }}>\n <ListCardGroup\n cells={group.cells}\n metaColumns={group.metaColumns}\n rails={group.rails}\n reserveGutters={group.reserveGutters}\n density={density}\n gap={rowGap}\n dataTestId={testId}\n >\n {rows.map((row) => {\n const id = getRowId(row);\n // A FRAGMENT, NOT A BOX. The row is subgrid over the group's tracks\n // (`gridColumn: span railCount`), which only resolves while the card\n // is a DIRECT child of the group's grid. One wrapper element and the\n // span is measured against a grid that isn't there, so every rail\n // collapses — the exact failure the group exists to prevent.\n return (\n <React.Fragment key={id}>\n {renderListRow(row, selectionFor(id))}\n </React.Fragment>\n );\n })}\n </ListCardGroup>\n </Box>\n );\n }\n\n return (\n <Box\n sx={{ mt: 1.5, display: \"flex\", flexDirection: \"column\", gap: rowGap }}\n data-testid={testId}\n >\n {rows.map((row) => {\n const id = getRowId(row);\n return <Box key={id}>{renderListRow(row, selectionFor(id))}</Box>;\n })}\n </Box>\n );\n}\n\n/* ── Body selector (board vs list vs cards vs table, from context) ───────── */\n\ninterface GridMainProps<T extends Record<string, unknown>> {\n c: DataViewsController<T>;\n getRowId: (row: T) => string | number;\n renderCard?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /** Opt-in \"Lista\" layout — one full-width row per record. */\n renderListRow?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /** The Lista's shared columns. Omitted, each row resolves its own tracks. */\n listGroup?: ListGroupConfig<T>;\n /** Opt-in \"Quadro\" (board) layout — needs `renderCard`, since it reuses the card. */\n board?: BoardConfig<T>;\n /** Opt-in expandable rows — the TABLE only; the headerless layouts ignore it. */\n rowDetail?: DataViewRowDetail<T>;\n dataTestId?: string;\n emptyState?: React.ReactNode;\n testIdPrefix: string;\n}\n\n/**\n * The headerless layouts, each preceded by its own select-all.\n *\n * The strip is rendered HERE rather than inside each body so all three agree on\n * where it sits and what it says — and so the table, which already has one in\n * its `<thead>`, never gets a second.\n */\nfunction Headerless<T extends Record<string, unknown>>({\n c,\n getRowId,\n testIdPrefix,\n children,\n}: {\n c: DataViewsController<T>;\n getRowId: (row: T) => string | number;\n testIdPrefix: string;\n children: React.ReactNode;\n}): React.JSX.Element {\n return (\n <>\n <SelectAllStrip\n rows={c.matched}\n getRowId={getRowId}\n selectedIds={c.selectedIds}\n onChange={(next) => c.setSelectedIds(next)}\n testIdPrefix={testIdPrefix}\n />\n {children}\n </>\n );\n}\n\n/**\n * The body for one of the three HEADERLESS layouts, or `null` for the table.\n *\n * Split out from {@link GridMain} so the select-all strip is wrapped around the\n * result exactly once instead of being repeated identically in every branch —\n * which is also what keeps either function inside the line budget.\n */\nfunction headerlessBody<T extends Record<string, unknown>>(\n { c, getRowId, renderCard, renderListRow, listGroup, board, dataTestId, emptyState }: GridMainProps<T>,\n { layout, zoom, density }: ReturnType<typeof useDataViewsLayout>,\n): React.ReactNode | null {\n if (layout === \"list\" && renderListRow) {\n return (\n <ListBody\n rows={c.matched}\n renderListRow={renderListRow}\n getRowId={getRowId}\n selectedIds={c.selectedIds}\n onToggleId={c.toggleId}\n rowGap={DENSITY_ROW_PADDING[density]}\n group={listGroup}\n density={density}\n dataTestId={dataTestId}\n emptyState={emptyState}\n />\n );\n }\n if (layout === \"board\" && board && renderCard) {\n return (\n <DataViewsBoard\n rows={c.matched}\n board={board}\n getRowId={getRowId}\n renderCard={renderCard}\n selectedIds={c.selectedIds}\n onToggleId={c.toggleId}\n // DENSITY, not zoom: the board's own knob in the Exibição tab is how\n // wide its columns are, and it is the only sizing control the board\n // has since the zoom slider was removed.\n cardScale={DENSITY_BOARD_SCALE[density]}\n dataTestId={dataTestId}\n />\n );\n }\n if (layout === \"cards\" && renderCard) {\n return (\n <CardBody\n rows={c.matched}\n renderCard={renderCard}\n getRowId={getRowId}\n selectedIds={c.selectedIds}\n onToggleId={c.toggleId}\n targetCardWidth={cardTargetWidthFor(zoom, density)}\n cardScale={cardScaleForZoom(zoom)}\n dataTestId={dataTestId}\n emptyState={emptyState}\n />\n );\n }\n return null;\n}\n\n/** Picks the body from the layout context: board, list, cards, or the dense grid. */\nexport function GridMain<T extends Record<string, unknown>>(props: GridMainProps<T>): React.JSX.Element {\n const { c, getRowId, dataTestId, emptyState, testIdPrefix } = props;\n const layoutState = useDataViewsLayout();\n const body = headerlessBody(props, layoutState);\n if (body) {\n return (\n <Headerless c={c} getRowId={getRowId} testIdPrefix={testIdPrefix}>\n {body}\n </Headerless>\n );\n }\n return (\n <GridBody\n rows={c.matched}\n columns={c.gridColumns}\n getRowId={getRowId}\n selectedIds={[...c.selectedIds]}\n onChangeSelected={(ids) => c.setSelectedIds(new Set(ids))}\n sortBy={c.state.sortBy}\n onChangeSortBy={(next: GridSort[]) => c.patch({ sortBy: next })}\n sortMode={c.serverMode ? \"server\" : \"client\"}\n rowPadding={DENSITY_ROW_PADDING[layoutState.density]}\n rowDetail={props.rowDetail}\n dataTestId={dataTestId}\n emptyState={emptyState}\n />\n );\n}\n\n"],"mappings":"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","names":["default"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/data-views-types.ts"],"sourcesContent":["import type { GridColumn, GridSort } from \"../DataGrid\";\nimport type { DropdownMenuItem } from \"../../navigation/DropdownMenu\";\n// A re-export does not bring the name into local scope, and the state/query\n// types below are written in terms of it.\nimport type { RangeValue } from \"./data-views-range-types\";\n\n/**\n * Shared types for the reusable admin \"DataViews\" table (FUT-88): a filter bar\n * (search-across-all + typed multi-select pills + column visibility) whose full\n * state — filters + sort + visible columns — a saved view (FUT-89) can capture\n * and restore. Each table declares its own columns + filter fields.\n */\n\n/**\n * A single, reusable row action. ONE definition drives BOTH the per-row \"⋮\"\n * kebab menu and the bulk-actions menu (tabwoah model): `onSelect` receives the\n * affected rows — `[row]` from a kebab, the whole selection from the bulk menu.\n * Single-item-only actions (e.g. \"Editar\") set `bulk: false` so they never\n * appear in the bulk menu.\n */\nexport interface RowAction<T extends Record<string, unknown>> {\n /** Stable id (also the `data-testid` suffix). */\n id: string;\n /** Menu label. In the kebab, `rowLabel` overrides it per row when provided. */\n label: string;\n /** Menu-item colour (e.g. `\"error\"` for destructive). */\n color?: DropdownMenuItem[\"color\"];\n /**\n * Runs on the affected rows: `[row]` from the kebab, all selected from bulk.\n * May be async — the bulk menu awaits it and only clears the selection once it\n * settles (keeping the selection on failure so the user can retry).\n */\n onSelect: (rows: T[]) => void | Promise<void>;\n /** Include in the bulk menu (2+ selected). Defaults to `true`. */\n bulk?: boolean;\n /** Include in the per-row kebab. Defaults to `true`. */\n row?: boolean;\n /** Per-row kebab label (e.g. `row.active ? \"Inativar\" : \"Ativar\"`). */\n rowLabel?: (row: T) => string;\n /**\n * Per-row applicability (e.g. state-machine guards). `false` hides the\n * kebab item for that row; in bulk the action runs only on the passing\n * rows and is dropped when none pass. Defaults to applicable.\n */\n isVisible?: (row: T) => boolean;\n}\n\n/** Selection state handed to a card renderer so it can drive its own checkbox. */\nexport interface DataViewCardSelection {\n selected: boolean;\n onToggleSelect: () => void;\n /**\n * Card SIZE multiplier from the toolbar zoom slider. The grid sizes the card's\n * width to `base × scale`; a card passes this to {@link BaseCard} `scale` so its\n * padding + typography grow by the same factor — the whole card scales together\n * (scale 2 → ~2× size). The proportion (aspect ratio) stays constant.\n */\n scale: number;\n}\n\n/**\n * The fixed proportions a {@link BaseCard} tile can take. The tile keeps this\n * ratio and grows in BOTH width and height as the zoom slider widens its grid\n * track, so a grid of cards stays visually uniform.\n */\nexport type CardAspectRatio = \"1:1\" | \"4:3\" | \"16:9\" | \"9:16\" | \"3:4\" | \"3:1\";\n\n/** Numeric width/height factor for each {@link CardAspectRatio} (CSS `aspect-ratio`). */\nexport const CARD_ASPECT_RATIOS: Record<CardAspectRatio, number> = {\n \"1:1\": 1,\n \"4:3\": 4 / 3,\n \"16:9\": 16 / 9,\n \"9:16\": 9 / 16,\n \"3:4\": 3 / 4,\n /**\n * The BANNER tile: too short for media, sized for two lines of text.\n *\n * Every other ratio here is picture-shaped, because they were added for cards\n * built around a thumbnail. A card whose subject is a NAME and a NUMBER — a\n * cart, an order, anything triaged rather than browsed — spends most of a 4:3\n * tile on an icon that repeats down the whole grid and says nothing.\n */\n \"3:1\": 3,\n};\n\n/** One selectable value inside a filter pill. */\nexport interface FilterOption {\n value: string;\n label: string;\n /**\n * Parent option's `value`, for a hierarchical field (`control: \"category\"`).\n * Ignored by the flat controls, so one option list serves both.\n */\n parentId?: string | null;\n}\n\n/** A typed, multi-select filter pill's configuration for a table. */\nexport interface FilterFieldConfig<T extends Record<string, unknown>> {\n /** Stable id; also the default row key when no `accessor` is given. */\n id: string;\n label: string;\n options: FilterOption[];\n /**\n * How the field renders in the filter panel. `checkboxes` (default) is best for\n * a few fixed options; `multiselect` is a compact dropdown for long option\n * lists; `category` is the hierarchical picker, for an option set that nests\n * via {@link FilterOption.parentId} — it renders parents as headings and\n * their children as the selectable rows, rather than flattening the tree into\n * one indistinguishable list.\n */\n control?: \"checkboxes\" | \"multiselect\" | \"category\";\n /**\n * Lets a `control: \"category\"` pill select a PARENT category, not just its\n * children. Off by default, which keeps the leaf-only reading: the parent is\n * a heading and its subcategories are the rows you tick.\n *\n * Turn it on where \"the whole category\" is a filter a user would reach for —\n * selecting a parent then selects every subcategory under it, and the chip\n * tray collapses that back to one chip bearing the category's name.\n *\n * A category with NO children is selectable either way: it is itself the\n * leaf, so the flag has nothing to gate there.\n */\n allowParentSelection?: boolean;\n /**\n * Forces this filter to render as a searchable multi-select dropdown with the\n * search box always shown, regardless of how many options are currently\n * loaded. Use it when the option set can grow unbounded (e.g. a filter backed\n * by a relation such as Roles or Categories). Omit it for a closed value set\n * (an enum), which keeps the plain checkbox / \"search only when long\"\n * behavior.\n */\n searchEnabled?: boolean;\n /**\n * Reads the row's value(s) for this field. A row matches the pill when any of\n * its values is selected. Defaults to `String(row[id])`.\n */\n accessor?: (row: T) => string | string[];\n}\n\n/**\n * The range types live in `data-views-range-types` (this file outgrew the\n * size gate); they are re-exported here so `data-views-types` stays the one\n * name every consumer imports the model from.\n */\nexport type {\n DayRangeFieldConfig,\n NumberRangeFieldConfig,\n RangeFieldConfig,\n RangeFieldKind,\n RangePreset,\n RangeValue,\n} from \"./data-views-range-types\";\n\n/** A grid column that can join the search scan and the column-visibility menu. */\nexport interface DataViewColumn<T extends Record<string, unknown>> extends GridColumn<T> {\n /** Included in \"search across all columns\" (needs a resolvable value). */\n searchable?: boolean;\n /** Toggleable in the column-visibility menu. Defaults to true; set false for\n * structural columns (image thumbnail, actions kebab) that must always show. */\n hideable?: boolean;\n}\n\n/**\n * The DataViews layout (view type): the dense \"Tabela\" grid, the \"Grade\" of\n * cards, the \"Lista\" of full-width rows, or the \"Quadro\" board of state columns.\n * Lives with the state types rather than with the toggle because a saved view\n * CAPTURES it — see {@link DataViewState.layout}.\n */\nexport type DataViewsLayout = \"cards\" | \"list\" | \"board\" | \"table\";\n\n/** The full, serializable state a view captures. */\n/**\n * A view-state patch: the fields to merge, or an updater given the CURRENT\n * state. Use the updater whenever the patch derives from state it also changes\n * — a render-scoped snapshot would let two calls in one tick read the same\n * `prev` and the second overwrite the first, which is exactly what a control\n * emitting one call per changed value does.\n */\nexport type DataViewStatePatch =\n | Partial<DataViewState>\n | ((prev: DataViewState) => Partial<DataViewState>);\n\nexport interface DataViewState {\n /** Free text matched across every `searchable` column. */\n search: string;\n /** fieldId → selected option values (empty/absent = no constraint). */\n pills: Record<string, string[]>;\n /** fieldId → selected numeric range (min/max). Absent/empty = no constraint. */\n ranges?: Record<string, RangeValue>;\n sortBy: GridSort[];\n /** Column ids kept visible; a hideable column not listed here is hidden. */\n visibleColumns: string[];\n /**\n * Column ids in READING ORDER. Absent ⇒ the order the table declared them in.\n *\n * Order is view state for the same reason visibility is: reordering columns is\n * an operator deciding what to read FIRST, and a saved view that restored the\n * columns but not their order would restore half a decision. Ids the table no\n * longer declares are ignored at render, and ids missing from a stale list\n * fall in after it — so a column added since the view was saved appears rather\n * than disappearing.\n */\n order?: string[];\n /**\n * The active SCOPE id — the page-level partition the scope tabs select (see\n * `ScopeConfig`). Optional, so a table that declares no scopes is untouched\n * and no view persisted before scopes existed breaks.\n *\n * Stored raw and RESOLVED at read time: the declared scopes change underneath\n * a saved view, and a view naming a scope that has since been removed must\n * fall back at render rather than break.\n */\n scope?: string;\n /**\n * The layout the view was saved in. Optional — absent means \"whatever the user\n * is currently using\", which is the pre-existing behaviour.\n *\n * A LAYOUT FALLBACK IS NOT A LAYOUT CHANGE: a table rendered as stacked rows\n * on a phone still has `\"table\"` stored here, so the view round-trips the\n * stored layout regardless of the device that opened it.\n */\n layout?: DataViewsLayout;\n}\n\n/** A pristine view state showing every given column and no filters/sort. */\nexport function emptyViewState(visibleColumns: string[]): DataViewState {\n return { search: \"\", pills: {}, ranges: {}, sortBy: [], visibleColumns };\n}\n\n/**\n * The URL-driven slice of {@link DataViewState} — the controls a server-mode host\n * mirrors from the address bar (search / pills / ranges / sort), WITHOUT\n * `visibleColumns`. Supplying it re-applies those controls when its reference\n * changes (e.g. browser back/forward or a deep-link on the same route), merging\n * over the current state so a user's show/hide column choices are preserved —\n * unlike `appliedState`, which fully replaces the state to apply a saved view.\n */\nexport type DataViewSyncState = Pick<\n DataViewState,\n \"search\" | \"pills\" | \"ranges\" | \"sortBy\" | \"scope\"\n>;\n\n/**\n * The query a SERVER-mode table emits whenever its filter/sort/page changes\n * (FUT-180). The host translates this into a backend request (see\n * `@12-apps/shared-helpers/search`) — in server mode the grid never filters,\n * sorts, or paginates in the browser; it renders exactly the page the server\n * returned. `page` is 1-based to match the backend contract.\n */\nexport interface DataViewQuery {\n search: string;\n pills: Record<string, string[]>;\n ranges: Record<string, RangeValue>;\n sortBy: GridSort[];\n /**\n * The RESOLVED active scope (see `resolveScope`), when the table declares\n * scopes. The KEY IS ABSENT for a table that declares none — not present-and-\n * undefined — so a host that never opted in sees a byte-identical query.\n *\n * It carries the resolved id, never the stored one: a deep link or a saved\n * view naming a scope the table no longer declares must not put that id on the\n * wire, where the backend would reject it and replace the page with an error.\n */\n scope?: string;\n page: number;\n pageSize: number;\n}\n\n/**\n * Server-mode wiring for a DataViews table. Its PRESENCE flips the table from\n * in-browser filtering/sorting/pagination to backend-driven: the rows handed in\n * are already the current page, `totalCount` is the unpaginated total, and\n * `onQueryChange` fires (search debounced by the host) whenever the user changes\n * a filter, the sort, or the page. This is the ONLY way an admin list should run\n * (see the app-side `DataViewsTable` wrapper, which requires it).\n */\nexport interface DataViewServer {\n /** Total matched rows across all pages (drives the counter + page count). */\n totalCount: number;\n /** Current 1-based page. */\n page: number;\n /** Rows per page. */\n pageSize: number;\n /** Fired when the effective query changes; the host re-fetches the page. */\n onQueryChange: (query: DataViewQuery) => void;\n /**\n * Per-scope totals for the scope tabs: `scopeId → count`. THE ONLY PIECE OF\n * THIS CONTRACT THAT CANNOT BE DONE CLIENT-SIDE — a count computed from the\n * loaded page is wrong the moment there is a second page, which is the\n * concrete bug scopes exist to fix.\n *\n * Compute it over search + pills + ranges while IGNORING the active scope.\n * Computed WITH the scope applied, every inactive tab reads zero, which is\n * worse than no counts at all.\n *\n * Omit it and the tabs render with no numbers. Nothing is ever invented from\n * the loaded rows to fill the gap.\n */\n scopeCounts?: Record<string, number>;\n}\n\n/**\n * Coerce an opaque persisted state (a saved view's JSON) into a valid\n * DataViewState, filling any missing part with a sane default. Guards against\n * legacy/older shapes so applying a saved view never renders an empty grid.\n */\nexport function coerceViewState(raw: unknown, allColumnIds: string[]): DataViewState {\n const value = (raw ?? {}) as Partial<DataViewState>;\n return {\n search: typeof value.search === \"string\" ? value.search : \"\",\n pills: asRecord<string[]>(value.pills),\n ranges: asRecord<RangeValue>(value.ranges),\n sortBy: Array.isArray(value.sortBy) ? value.sortBy : [],\n visibleColumns: Array.isArray(value.visibleColumns) ? value.visibleColumns : allColumnIds,\n order: Array.isArray(value.order) ? value.order : undefined,\n // Carried through UNVALIDATED against the declared scopes on purpose: a view\n // written before scopes existed has none (⇒ undefined ⇒ the first declared\n // scope at render), and one naming a since-removed scope keeps its stored id\n // here and falls back at READ time. Resolving here would rewrite the view.\n scope: typeof value.scope === \"string\" ? value.scope : undefined,\n layout: isLayout(value.layout) ? value.layout : undefined,\n };\n}\n\n/** Every layout this build knows how to render — the guard's single source. */\nexport const DATA_VIEWS_LAYOUTS: readonly DataViewsLayout[] = [\"cards\", \"list\", \"board\", \"table\"];\n\n/** Is this a layout this build knows how to render? Guards persisted JSON. */\nfunction isLayout(value: unknown): value is DataViewsLayout {\n return DATA_VIEWS_LAYOUTS.includes(value as DataViewsLayout);\n}\n\n/** A persisted map, or an empty one — persisted JSON can hold anything. */\nfunction asRecord<V>(value: unknown): Record<string, V> {\n return value !== null && typeof value === \"object\" ? (value as Record<string, V>) : {};\n}\n\n/** A saved view as consumed by the DataViews UI (FUT-89), with ownership flag. */\nexport interface SavedViewSummary {\n id: string;\n name: string;\n description: string | null;\n state: DataViewState;\n shared: boolean;\n pinned: boolean;\n isDefault: boolean;\n /** True when the current user owns the view (may edit/delete/pin/share it). */\n isOwner: boolean;\n}\n\n/** A saved-view repository record (opaque `state`), as returned to server pages. */\ninterface SavedViewRecordLike {\n id: string;\n name: string;\n description: string | null;\n state: unknown;\n shared: boolean;\n pinned: boolean;\n isDefault: boolean;\n isOwner: boolean;\n}\n\n/** Map a repository record to the client summary (opaque state → DataViewState). */\nexport function toSavedViewSummary(record: SavedViewRecordLike): SavedViewSummary {\n return { ...record, state: (record.state ?? {}) as DataViewState };\n}\n\n/**\n * Dependency-injection contracts for the framework-agnostic DataViewsTableBase:\n * the host app supplies saved-view persistence + router side-effects so the ui\n * component imports neither a backend (server actions) nor a framework router.\n */\n\n/** A saved view's editable fields, as the host persists them. */\nexport interface DataViewSaveInput {\n name: string;\n description: string;\n state: DataViewState;\n shared: boolean;\n pinned: boolean;\n isDefault: boolean;\n}\n\n/** Result of a persistence call — mirrors the app's `Result` without importing it. */\nexport type DataViewMutationResult = { ok: true } | { ok: false; error: string };\n\n/** Injected saved-view persistence (create/update/delete), wired by the host. */\nexport interface DataViewPersistence {\n create: (input: DataViewSaveInput) => Promise<DataViewMutationResult>;\n update: (id: string, input: DataViewSaveInput) => Promise<DataViewMutationResult>;\n remove: (id: string) => Promise<DataViewMutationResult>;\n}\n\n/** Injected router side-effects: sync the `?view=` param and re-fetch on change. */\nexport interface DataViewRouter {\n syncViewParam: (viewId: string | null) => void;\n refresh: () => void;\n}\n"],"mappings":";AAoEO,IAAM,qBAAsD;AAAA,EACjE,OAAO;AAAA,EACP,OAAO,IAAI;AAAA,EACX,QAAQ,KAAK;AAAA,EACb,QAAQ,IAAI;AAAA,EACZ,OAAO,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASX,OAAO;AACT;AA+IO,SAAS,eAAe,gBAAyC;AACtE,SAAO,EAAE,QAAQ,IAAI,OAAO,CAAC,GAAG,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,eAAe;AACzE;AA+EO,SAAS,gBAAgB,KAAc,cAAuC;AACnF,QAAM,QAAS,OAAO,CAAC;AACvB,SAAO;AAAA,IACL,QAAQ,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS;AAAA,IAC1D,OAAO,SAAmB,MAAM,KAAK;AAAA,IACrC,QAAQ,SAAqB,MAAM,MAAM;AAAA,IACzC,QAAQ,MAAM,QAAQ,MAAM,MAAM,IAAI,MAAM,SAAS,CAAC;AAAA,IACtD,gBAAgB,MAAM,QAAQ,MAAM,cAAc,IAAI,MAAM,iBAAiB;AAAA,IAC7E,OAAO,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,IAKlD,OAAO,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ;AAAA,IACvD,QAAQ,SAAS,MAAM,MAAM,IAAI,MAAM,SAAS;AAAA,EAClD;AACF;AAGO,IAAM,qBAAiD,CAAC,SAAS,QAAQ,SAAS,OAAO;AAGhG,SAAS,SAAS,OAA0C;AAC1D,SAAO,mBAAmB,SAAS,KAAwB;AAC7D;AAGA,SAAS,SAAY,OAAmC;AACtD,SAAO,UAAU,QAAQ,OAAO,UAAU,WAAY,QAA8B,CAAC;AACvF;AA4BO,SAAS,mBAAmB,QAA+C;AAChF,SAAO,EAAE,GAAG,QAAQ,OAAQ,OAAO,SAAS,CAAC,EAAoB;AACnE;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/DataViewsBoard.tsx"],"sourcesContent":["\"use client\";\n\nimport { useTheme, type Theme } from \"@mui/material/styles/index.js\";\n\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport { Box } from \"../../../mui/Box\";\nimport { Text } from \"../../typography/Text\";\n\nimport type { DataViewCardSelection } from \"./data-views-types\";\nimport { rem } from \"../../../tokens/relative\";\n\n/**\n * One column of the board — a single value of the grouping field, with the label\n * to print above it.\n *\n * `tone` is an opaque palette hint (e.g. `\"success\"`, `\"error\"`) the host picks\n * from its own state machine; the board only uses it to colour the column's\n * header accent, so an unknown tone degrades to the default rather than throwing.\n */\nexport interface BoardGroup {\n value: string;\n label: string;\n tone?: string;\n}\n\n/**\n * The BOARD layout's configuration: the same page the table rendered, laid out\n * as columns of the entity's own card.\n *\n * The board earns its place when a page's buckets are STATES IN A STATE MACHINE\n * rather than arbitrary groupings — payments are exactly that shape. Derive\n * `groups` and the table's `scopes` from ONE state-machine definition: two\n * hand-maintained lists of the same states will drift, and the drift shows up as\n * a column the tabs do not have.\n */\nexport interface BoardConfig<T extends Record<string, unknown>> {\n /** The row field whose value places a card in a column. */\n groupBy: keyof T & string;\n /** The columns, in DELIBERATE reading order — the board renders this order. */\n groups: BoardGroup[];\n /** Optional numeric row field summed per column (e.g. the amount in cents). */\n sumBy?: keyof T & string;\n /**\n * Formats a column's total. The PAGE's own formatter, so the board prints\n * money in the store's currency instead of inventing a second convention.\n * Omitted ⇒ the raw sum.\n */\n formatSum?: (total: number) => string;\n /** Header for the column collecting rows in states the front end does not declare. */\n extraLabel?: string;\n}\n\n/** The default header for the catch-all column (see {@link groupRows}). */\nconst DEFAULT_EXTRA_LABEL = \"Sem etapa\";\n\n/** One resolved column: a declared group (or the catch-all) plus its loaded rows. */\ninterface ResolvedColumn<T> {\n key: string;\n label: string;\n tone?: string;\n rows: T[];\n}\n\n/** Read the grouping value off a row as a string (`null`/`undefined` ⇒ \"\"). */\nfunction groupValueOf<T extends Record<string, unknown>>(row: T, groupBy: string): string {\n const value = row[groupBy];\n return value === null || value === undefined ? \"\" : String(value);\n}\n\n/**\n * Place every loaded row into a column, in the DECLARED order, and collect the\n * leftovers into one extra column at the end.\n *\n * Nothing is ever dropped, and that is the point: a state added to the back end\n * before the front end knows about it must not make rows disappear. On a\n * payments board, a silently dropped row is money nobody can see. The extra\n * column only exists when something landed in it — once every row is placed it\n * is gone, so the common case shows exactly the declared columns.\n *\n * A declared group with no rows KEEPS its position: the order is a reading\n * order chosen by the host, not a ranking derived from the data.\n */\nexport function groupRows<T extends Record<string, unknown>>(\n rows: T[],\n board: BoardConfig<T>,\n): ResolvedColumn<T>[] {\n const declared = new Map<string, T[]>(board.groups.map((group) => [group.value, []]));\n const extra: T[] = [];\n for (const row of rows) {\n const bucket = declared.get(groupValueOf(row, board.groupBy));\n if (bucket) bucket.push(row);\n else extra.push(row);\n }\n const columns: ResolvedColumn<T>[] = board.groups.map((group) => ({\n key: group.value,\n label: group.label,\n tone: group.tone,\n rows: declared.get(group.value) ?? [],\n }));\n if (extra.length > 0) {\n columns.push({ key: \"__extra__\", label: board.extraLabel ?? DEFAULT_EXTRA_LABEL, rows: extra });\n }\n return columns;\n}\n\n/** A column's total over the rows ON THIS PAGE, or null when nothing is summed. */\nfunction sumOf<T extends Record<string, unknown>>(rows: T[], sumBy: string | undefined): number | null {\n if (!sumBy) return null;\n return rows.reduce((total, row) => {\n const value = row[sumBy];\n return typeof value === \"number\" && Number.isFinite(value) ? total + value : total;\n }, 0);\n}\n\n/** Map a `tone` hint onto a theme colour, defaulting for anything unrecognised. */\nfunction toneColor(tone: string | undefined): string {\n const known = [\"primary\", \"secondary\", \"success\", \"warning\", \"error\", \"info\"];\n return tone && known.includes(tone) ? `${tone}.main` : \"divider\";\n}\n\n/**\n * A column's heading: the label, the page count and the optional sum.\n *\n * The count is labelled \"nesta página\" EVERYWHERE it appears, including in its\n * accessible name. The scope tabs above the board show whole-query totals from\n * the server; an unlabelled column count would contradict them the moment there\n * is a second page, and the reader has no way to tell which number to believe.\n */\nfunction BoardColumnHeader({\n label,\n count,\n sum,\n testId,\n}: {\n label: string;\n count: number;\n /** The formatted total, or null when the board sums nothing. */\n sum: string | null;\n testId: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <Box sx={{ display: \"flex\", flexDirection: \"column\", gap: 0.25 }}>\n <Box sx={{ display: \"flex\", alignItems: \"baseline\", justifyContent: \"space-between\", gap: 1 }}>\n <Text variant=\"caption\" as=\"span\">\n <Box component=\"span\" sx={{ fontWeight: 600 }}>\n {label}\n </Box>\n </Text>\n <Text variant=\"caption\" as=\"span\">\n <Box\n component=\"span\"\n data-testid={`${testId}-count`}\n aria-label={copy.board.countOnPage(label, count)}\n sx={{ color: \"text.secondary\", whiteSpace: \"nowrap\" }}\n >\n {copy.board.onThisPage(count)}\n </Box>\n </Text>\n </Box>\n {sum !== null && (\n <Text variant=\"caption\" as=\"span\">\n <Box\n component=\"span\"\n data-testid={`${testId}-sum`}\n aria-label={copy.board.pageSum(sum)}\n sx={{ color: \"text.secondary\" }}\n >\n {sum}\n </Box>\n </Text>\n )}\n </Box>\n );\n}\n\ninterface BoardColumnProps<T extends Record<string, unknown>> {\n column: ResolvedColumn<T>;\n board: BoardConfig<T>;\n getRowId: (row: T) => string | number;\n renderCard: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n selectedIds: Set<string | number>;\n onToggleId: (id: string | number) => void;\n cardScale: number;\n /** The column's width as CSS, already through the type scale. */\n width: string;\n testId: string;\n}\n\n/** One column: its header over a vertical stack of the entity's own cards. */\nfunction BoardColumn<T extends Record<string, unknown>>({\n column,\n board,\n getRowId,\n renderCard,\n selectedIds,\n onToggleId,\n cardScale,\n width,\n testId,\n}: BoardColumnProps<T>): React.JSX.Element {\n const copy = useDataViewsCopy();\n const total = sumOf(column.rows, board.sumBy);\n return (\n <Box\n data-testid={testId}\n sx={{\n flex: `0 0 ${width}`,\n minWidth: width,\n display: \"flex\",\n flexDirection: \"column\",\n gap: 1,\n borderTop: (theme: Theme) => `${rem(theme, 3)} solid`,\n borderStyle: \"solid\",\n borderColor: toneColor(column.tone),\n borderRadius: 1,\n bgcolor: \"action.hover\",\n p: 1,\n }}\n >\n <BoardColumnHeader\n label={column.label}\n count={column.rows.length}\n sum={total === null ? null : String(board.formatSum ? board.formatSum(total) : total)}\n testId={testId}\n />\n {column.rows.length === 0 ? (\n <Text variant=\"caption\" as=\"p\">\n <Box component=\"span\" data-testid={`${testId}-empty`} sx={{ color: \"text.disabled\" }}>\n {copy.board.emptyColumn}\n </Box>\n </Text>\n ) : (\n column.rows.map((row) => {\n const id = getRowId(row);\n return (\n <Box key={id}>\n {renderCard(row, {\n selected: selectedIds.has(id),\n onToggleSelect: () => onToggleId(id),\n scale: cardScale,\n })}\n </Box>\n );\n })\n )}\n </Box>\n );\n}\n\ninterface DataViewsBoardProps<T extends Record<string, unknown>> {\n /** The LOADED page, verbatim. The board never filters, sorts or paginates. */\n rows: T[];\n board: BoardConfig<T>;\n getRowId: (row: T) => string | number;\n renderCard: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n selectedIds: Set<string | number>;\n onToggleId: (id: string | number) => void;\n /** Card size multiplier from the toolbar zoom slider — the same knob as cards. */\n cardScale: number;\n dataTestId?: string;\n}\n\n/**\n * The board's column width: 240 design px at scale 1, which the zoom slider\n * multiplies, and never narrower than 220 however far the zoom is wound down\n * (touch targets) — through the type scale.\n */\nconst columnWidth = (theme: Theme, cardScale: number): string =>\n rem(theme, Math.max(220, Math.round(240 * cardScale)));\n\n/**\n * The BOARD layout: the loaded page distributed into columns of the entity's own\n * card. A rendering, not a second source of truth — switching to it emits no\n * query, and it shows exactly the rows the table showed.\n *\n * The board scrolls HORIZONTALLY inside its own box rather than letting the page\n * scroll sideways, and its columns keep a usable width at every zoom, so a phone\n * gets a real board instead of six unreadable slivers.\n *\n * Cards are not draggable between columns. Dragging a payment from Recusado to\n * Pago is a domain transition with side effects, not a re-render: it needs the\n * allowed-transition set from the state machine plus a confirmation, and\n * `RowAction.isVisible` is the existing per-row guard to reuse when that lands.\n */\nexport function DataViewsBoard<T extends Record<string, unknown>>({\n rows,\n board,\n getRowId,\n renderCard,\n selectedIds,\n onToggleId,\n cardScale,\n dataTestId,\n}: DataViewsBoardProps<T>): React.JSX.Element {\n const copy = useDataViewsCopy();\n const theme = useTheme();\n const columns = groupRows(rows, board);\n const width = columnWidth(theme, cardScale);\n const testId = dataTestId ? `${dataTestId}-board` : \"data-views-board\";\n return (\n <Box sx={{ mt: 1.5 }}>\n <Text variant=\"caption\" as=\"p\">\n <Box component=\"span\" data-testid={`${testId}-scale-note`} sx={{ color: \"text.secondary\" }}>\n {copy.board.pageScopeNote}\n </Box>\n </Text>\n <Box\n data-testid={testId}\n sx={{\n mt: 1,\n display: \"flex\",\n alignItems: \"flex-start\",\n gap: 1.5,\n // The BOARD scrolls, not the page: without `maxWidth` the flex row\n // widens its parent and the whole document scrolls sideways.\n maxWidth: \"100%\",\n overflowX: \"auto\",\n pb: 1,\n }}\n >\n {columns.map((column) => (\n <BoardColumn\n key={column.key}\n column={column}\n board={board}\n getRowId={getRowId}\n renderCard={renderCard}\n selectedIds={selectedIds}\n onToggleId={onToggleId}\n cardScale={cardScale}\n width={width}\n testId={`${testId}-column-${column.key}`}\n />\n ))}\n </Box>\n </Box>\n );\n}\n"],"mappings":"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","names":["default"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/data-views-grid-parts.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nimport { TableFilter } from \"../../layout/TableFilter\";\nimport { Stack } from \"../../../mui/Stack\";\n\nimport {\n DataViewsLayoutProvider,\n useDataViewsLayout,\n type DataViewsLayout,\n} from \"./data-views-layout-context\";\nimport { FilterDialog, GridFilterPanel } from \"./data-views-filter-panel\";\nimport { GridMain } from \"./data-views-grid-bodies\";\nimport type { ListGroupConfig } from \"./list-card-rails\";\nimport { InlineFilterControls } from \"./data-views-inline-bar\";\nimport { toOverflowFields, useFilterOverflow, type OverflowSplit } from \"./data-views-overflow\";\nimport { GridHeaderRow } from \"./data-views-header-row\";\nimport { allOnPageSelected, resolveSelectionExtra, type SelectionExtraRender } from \"./data-views-selection-extra\";\nimport { ShellToolbar } from \"./data-views-shell-toolbar\";\nimport type { DisplayPanelView } from \"./data-views-display-panel\";\nimport type { DataViewExport } from \"./data-views-export\";\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport { DataViewsEmpty } from \"./data-views-empty\";\nimport { DataViewsPagination } from \"./data-views-pagination\";\nimport type { BoardConfig } from \"./DataViewsBoard\";\nimport { type ScopeConfig } from \"./data-views-scopes\";\nimport { ScopeTabsSlot } from \"./data-views-scope-tabs\";\nimport { togglePillValues } from \"./data-views-grid-helpers\";\nimport type {\n DataViewCardSelection,\n FilterFieldConfig,\n RangeFieldConfig,\n RangeValue,\n RowAction,\n} from \"./data-views-types\";\nimport type { DataViewRowDetail } from \"./data-views-row-detail\";\nimport type { DataViewsController } from \"./use-data-views-state\";\n\n/* ── Layout ⇄ view-state mirror ──────────────────────────────────────────── */\n\n/**\n * Mirrors the LIVE layout back into the view state, so saving a view captures\n * the layout the user is actually looking at.\n *\n * A component rather than a callback prop because only something INSIDE the\n * provider can read the layout, and only the controller can write the state.\n * The `stored !== layout` guard is what makes it idempotent: `c` is rebuilt on\n * every render, so an unguarded effect here would patch → re-render → patch.\n *\n * It re-fires when `stored` changes too, which is the case that matters after\n * applying a saved view: `appliedState` REPLACES the state, so a view saved\n * before layouts were captured clears `state.layout`, and the mirror puts the\n * current one back rather than leaving the next save with nothing to store.\n */\nfunction LayoutStateSync<T extends Record<string, unknown>>({\n c,\n}: {\n c: DataViewsController<T>;\n}): null {\n const { layout } = useDataViewsLayout();\n const stored = c.state.layout;\n useEffect(() => {\n if (stored !== layout) c.patch({ layout });\n // `c` is intentionally out of the deps — see the guard above.\n }, [layout, stored]);\n return null;\n}\n\n/* ── Shell (composes toolbar + body + panel) ─────────────────────────────── */\n\n/** The shared filter-surface props consumed by the inline bar, panel, and modal. */\ninterface FilterSurfaceProps<T extends Record<string, unknown>> {\n testIdPrefix: string;\n search: string;\n fields: FilterFieldConfig<T>[];\n rangeFields: RangeFieldConfig<T>[];\n pills: Record<string, string[]>;\n ranges: Record<string, RangeValue>;\n onSearchChange: (value: string) => void;\n onTogglePill: (fieldId: string, value: string, checked: boolean) => void;\n onChangeRange: (fieldId: string, range: RangeValue) => void;\n onClearField: (fieldId: string) => void;\n onClearAll: () => void;\n}\n\n/**\n * Derives the responsive filter state (which surface to show at this width) and\n * the filter-mutation handlers from the controller — pulled out of `GridShell`\n * so the component body stays under the size/complexity budget.\n */\n/** Grouped rather than positional: the list had grown past readability. */\ninterface GridShellFiltersArgs<T extends Record<string, unknown>> {\n c: DataViewsController<T>;\n inlineFilters: boolean;\n fields: FilterFieldConfig<T>[];\n rangeFields: RangeFieldConfig<T>[];\n testIdPrefix: string;\n alwaysShowSearch: boolean;\n /** Is \"Exportar\" on the bar? Half the right cluster's cost when it is not. */\n hasExport: boolean;\n}\n\nfunction useGridShellFilters<T extends Record<string, unknown>>({\n c,\n inlineFilters,\n fields,\n rangeFields,\n testIdPrefix,\n alwaysShowSearch,\n hasExport,\n}: GridShellFiltersArgs<T>): {\n showInline: boolean;\n useModal: boolean;\n inlineVisible: boolean;\n filterProps: FilterSurfaceProps<T>;\n split: OverflowSplit<T>;\n onControlOpenChange: (open: boolean) => void;\n} {\n const { state } = c;\n // How many filter controls are open. A COUNT, not a flag: closing one popover\n // by opening another overlaps, and a boolean would unfreeze mid-handover.\n const [openControls, setOpenControls] = useState(0);\n // ONE measurement for the whole shell: the filter row decides which controls\n // it can keep, and the TOOLBAR needs the same answer to drop its labels.\n // Measuring twice would let the two disagree at the crossover width.\n const split = useFilterOverflow(\n toOverflowFields(fields, rangeFields),\n state.pills,\n c.ranges,\n openControls > 0,\n hasExport,\n );\n // THE BAR RENDERS AT EVERY WIDTH. It used to be swapped for a full-screen\n // filter MODAL below `lg`, which was the responsive strategy before the\n // measured ladder existed — and the two now do the same job, badly together:\n // the modal took the bar away at 1199px, and with it the very degradation\n // (labels off, search collapsed, filters into \"Mais\") that exists to make a\n // narrow bar work. So a 900px window got no bar and no ladder, and the\n // operator lost the search entirely rather than gaining a compact one.\n //\n // The ladder is measured, so it already covers every width the modal did.\n const showInline = inlineFilters;\n const useModal = false;\n const inlineVisible =\n showInline &&\n (alwaysShowSearch || fields.length > 0 || rangeFields.length > 0 || state.search !== \"\");\n const filterProps: FilterSurfaceProps<T> = {\n testIdPrefix,\n search: state.search,\n fields,\n rangeFields,\n pills: state.pills,\n ranges: c.ranges,\n onSearchChange: (value) => c.patch({ search: value }),\n // Updater, never the render's `state` — see {@link DataViewStatePatch}.\n onTogglePill: (fieldId, value, checked) =>\n c.patch(({ pills }) => ({ pills: { ...pills, [fieldId]: togglePillValues(pills[fieldId], value, checked) } })),\n onChangeRange: (fieldId, range) => c.patch({ ranges: { ...c.ranges, [fieldId]: range } }),\n onClearField: (fieldId) => c.patch({ pills: { ...state.pills, [fieldId]: [] } }),\n onClearAll: () => c.patch({ search: \"\", pills: {}, ranges: {} }),\n };\n return {\n showInline,\n useModal,\n inlineVisible,\n filterProps,\n split,\n onControlOpenChange: (open) =>\n setOpenControls((count) => Math.max(0, count + (open ? 1 : -1))),\n };\n}\n\ninterface GridShellProps<T extends Record<string, unknown>> {\n c: DataViewsController<T>;\n rows: T[];\n fields: FilterFieldConfig<T>[];\n rangeFields: RangeFieldConfig<T>[];\n getRowId: (row: T) => string | number;\n testIdPrefix: string;\n dataTestId?: string;\n emptyState?: React.ReactNode;\n toolbarRightSlot?: React.ReactNode;\n rowActions?: RowAction<T>[];\n bulkActions?: (selectedRows: T[], clearSelection: () => void) => React.ReactNode;\n /** A widening control beside the selection count — see `resolveSelectionExtra`. */\n selectionExtra?: SelectionExtraRender<T>;\n /** Opt-in \"Grade\" (cards) layout: renders each row as an entity-supplied card.\n * Omit ⇒ table only (no layout toggle). */\n renderCard?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /**\n * Opt-in \"Quadro\" (board) layout. Needs `renderCard` too — the board reuses\n * the entity's card, so a board config without one simply offers no board\n * rather than failing: that is a config gap, not a runtime error.\n */\n board?: BoardConfig<T>;\n /** Opt-in \"Lista\" layout: one full-width, entity-rendered row per record. */\n renderListRow?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /** The Lista's shared columns, so its rows line up by construction. */\n listGroup?: ListGroupConfig<T>;\n /** Opt-in expandable rows, drawn by the table layout only. */\n rowDetail?: DataViewRowDetail<T>;\n /** The page-level partition rendered as tabs under the toolbar. */\n scopes?: ScopeConfig[];\n /**\n * The row field the scopes partition by. Compared against the board's\n * `groupBy` so the two are not shown as the same partition twice.\n */\n scopeFieldId?: string;\n /** Per-sort-field value kind, so directions read in the column's own terms. */\n sortKinds?: Record<string, string>;\n /** The saved-view chrome bracketing the Exibir panel. */\n displayView?: DisplayPanelView;\n /** Injected export — the host re-queries; the grid never fetches. */\n exportConfig?: DataViewExport;\n /** Page title in the grid's own header row, above the scopes + toolbar. */\n title?: string;\n /** Primary page actions rendered at the header row's right. */\n headerActions?: React.ReactNode;\n /** Which layout to show first when the user has expressed no preference (default \"table\"). */\n defaultLayout?: DataViewsLayout;\n /** Pin to `defaultLayout`, ignoring the remembered preference (stories/docs). */\n ignoreStoredLayout?: boolean;\n /**\n * Opt into the responsive inline filter UX: a collapsible filter row below the\n * toolbar on wide screens and a modal on narrow ones. When false (default),\n * keep the classic slide-in filter panel opened by the \"Filtros\" button.\n */\n inlineFilters?: boolean;\n /**\n * Keep the inline search box on screen even with no filter fields and no\n * active search. Without it a grid whose only filter IS the search (`fields`\n * empty) can never OFFER one: the row renders only once a search exists, so\n * the operator would have to hand-edit the URL to start one. Default false —\n * every grid that ships filter fields already shows the row.\n */\n alwaysShowSearch?: boolean;\n}\n\n/** The scrollable content region: scope tabs, toolbar, filter bar, body, pager. */\nfunction ShellStack<T extends Record<string, unknown>>({\n props,\n filters,\n}: {\n props: GridShellProps<T>;\n filters: ReturnType<typeof useGridShellFilters<T>>;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n const { c, testIdPrefix, dataTestId, scopes = [], emptyState, inlineFilters = false } = props;\n const { showInline, useModal, inlineVisible, filterProps, split, onControlOpenChange } = filters;\n // The grid renders the FILTERED empty state itself — it is the only party\n // that knows a filter is applied. See {@link DataViewsEmpty}.\n const body = (\n <DataViewsEmpty\n filtered={c.activeFilterCount > 0 || c.state.search !== \"\"}\n onClearFilters={() => c.patch({ search: \"\", pills: {}, ranges: {} })}\n emptyState={emptyState}\n testIdPrefix={testIdPrefix}\n />\n );\n return (\n <TableFilter\n copy={copy.tableFilter} open={c.filterOpen} onOpenChange={c.setFilterOpen}\n hasActiveFilters={c.activeFilterCount > 0}\n >\n <Stack spacing={0} data-testid={dataTestId ? `${dataTestId}-container` : undefined}>\n <GridHeaderRow title={props.title} headerActions={props.headerActions} testIdPrefix={testIdPrefix} />\n <ShellToolbar\n c={c}\n rows={props.rows}\n sortKinds={props.sortKinds}\n displayView={props.displayView}\n exportConfig={props.exportConfig}\n filterControls={\n inlineVisible ? (\n <InlineFilterControls\n {...filterProps} split={split} activeFilterCount={c.activeFilterCount}\n onControlOpenChange={onControlOpenChange}\n />\n ) : undefined\n }\n barRef={split.barRef}\n testIdPrefix={testIdPrefix}\n rowActions={props.rowActions}\n bulkActions={props.bulkActions}\n selectionExtra={resolveSelectionExtra(props)}\n allOnPageSelected={allOnPageSelected(c, props.getRowId)}\n toolbarRightSlot={props.toolbarRightSlot}\n compactControls={showInline && split.compactControls}\n counterHidden={showInline && split.counterHidden}\n showInline={showInline}\n />\n <ScopeTabsSlot\n scopes={scopes} scopeFieldId={props.scopeFieldId} board={props.board}\n value={c.scope} onChange={c.setScope} counts={c.scopeCounts}\n testIdPrefix={testIdPrefix}\n />\n <TableFilter.Layout>\n <TableFilter.Main>\n <GridMain\n c={c}\n getRowId={props.getRowId}\n renderCard={props.renderCard}\n renderListRow={props.renderListRow}\n listGroup={props.listGroup}\n board={props.board}\n rowDetail={props.rowDetail}\n dataTestId={dataTestId}\n testIdPrefix={testIdPrefix}\n emptyState={body}\n />\n <DataViewsPagination c={c} testIdPrefix={testIdPrefix} />\n </TableFilter.Main>\n {inlineFilters ? null : <GridFilterPanel {...filterProps} />}\n </TableFilter.Layout>\n {useModal && <FilterDialog open={c.filterOpen} onClose={() => c.setFilterOpen(false)} {...filterProps} />}\n </Stack>\n </TableFilter>\n );\n}\n\nexport function GridShell<T extends Record<string, unknown>>(props: GridShellProps<T>): React.JSX.Element {\n const {\n c,\n fields,\n rangeFields,\n testIdPrefix,\n renderCard,\n renderListRow,\n board,\n defaultLayout,\n ignoreStoredLayout,\n inlineFilters = false,\n alwaysShowSearch = false,\n } = props;\n const filters = useGridShellFilters({\n c,\n inlineFilters,\n fields,\n rangeFields,\n testIdPrefix,\n alwaysShowSearch,\n hasExport: props.exportConfig !== undefined,\n });\n return (\n <DataViewsLayoutProvider\n canUseCards={Boolean(renderCard)}\n canUseList={Boolean(renderListRow)}\n // The board reuses the entity's card, so it needs BOTH — a `board` with no\n // `renderCard` offers no board rather than throwing.\n canUseBoard={Boolean(board && renderCard)}\n defaultLayout={defaultLayout}\n ignoreStoredLayout={ignoreStoredLayout}\n viewLayout={c.state.layout}\n >\n <LayoutStateSync c={c} />\n <ShellStack props={props} filters={filters} />\n </DataViewsLayoutProvider>\n );\n}\n"],"mappings":"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","names":["default"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/DataViewsTableBase.tsx"],"sourcesContent":["\"use client\";\n\nimport type { SortFieldDefinition } from \"../../layout/ContentToolbar\";\nimport { useEffect, useRef, useState } from \"react\";\n\nimport { DataViewsGrid } from \"./DataViewsGrid\";\nimport type { SelectionExtraRender } from \"./data-views-selection-extra\";\nimport type { ListGroupConfig } from \"./list-card-rails\";\nimport type { DisplayPanelView } from \"./data-views-display-panel\";\nimport { viewStateKey } from \"./data-views-dirty\";\nimport { DeleteViewDialog } from \"./DeleteViewDialog\";\nimport type { BoardConfig } from \"./DataViewsBoard\";\nimport type { DataViewExport } from \"./data-views-export\";\nimport type { ScopeConfig } from \"./data-views-scopes\";\nimport type { DataViewRowDetail } from \"./data-views-row-detail\";\nimport { ViewDialogs, ViewMutationErrorAlert } from \"./data-views-table-parts\";\nimport { useUrlEchoGuard, useUrlSyncState } from \"./data-views-url-echo\";\nimport {\n useSavedViewsController,\n type SavedViewsController,\n} from \"./use-saved-views-controller\";\nimport {\n type DataViewColumn,\n type DataViewsLayout,\n type DataViewServer,\n type DataViewState,\n type DataViewPersistence,\n type DataViewRouter,\n type FilterFieldConfig,\n type RangeFieldConfig,\n type DataViewCardSelection,\n type RowAction,\n type SavedViewSummary,\n} from \"./data-views-types\";\n\nexport interface DataViewsTableBaseProps<T extends Record<string, unknown>> {\n /** The applied-view id read from the URL (`?view=`) on load, or null. */\n initialViewId?: string | null;\n /** Injected saved-view persistence (create/update/delete). */\n persistence: DataViewPersistence;\n /** Injected router side-effects (URL sync + refresh). */\n router: DataViewRouter;\n rows: T[];\n columns: DataViewColumn<T>[];\n fields: FilterFieldConfig<T>[];\n /** Optional numeric min/max range filters (advanced \"filter by amount\"). */\n rangeFields?: RangeFieldConfig<T>[];\n getRowId: (row: T) => string | number;\n onRowClick?: (row: T) => void;\n emptyState?: React.ReactNode;\n dataTestId?: string;\n testIdPrefix?: string;\n /** The user's + shared views for this table (from the server). */\n views: SavedViewSummary[];\n /** Big page title in the table header, beside the funnel + gear (GTA layout). */\n title?: string;\n /** Primary page actions (e.g. \"Novo produto\") rendered at the header's right. */\n headerActions?: React.ReactNode;\n /** Sort options for the toolbar's \"Sort By\" dropdown (defaults to sortable columns). */\n sortFields?: SortFieldDefinition[];\n /**\n * Per-sort-field VALUE KIND (`\"currency\" | \"number\" | \"date\" | \"text\"`), so the\n * Exibir panel phrases the direction in that column's own terms. \"Crescente\"\n * on a currency column is a puzzle; \"Menor → maior\" is not. Defaults to text.\n */\n sortKinds?: Record<string, string>;\n /**\n * Opt-in \"Exportar\" control. The grid hands the host the current query,\n * UNPAGINATED, and the host re-queries — the grid never fetches, and an\n * export therefore follows the filters rather than the loaded page.\n */\n exportConfig?: DataViewExport;\n /** Notified with the currently-visible (filtered) rows, so Export matches the view. */\n onVisibleRowsChange?: (rows: T[]) => void;\n /** Reusable row actions driving both the row \"⋮\" kebab and the bulk menu. */\n rowActions?: RowAction<T>[];\n /** Leading control in the auto kebab column (e.g. a favourite star). */\n rowActionsLeading?: (row: T) => React.ReactNode;\n /** Low-level bulk-action override; prefer `rowActions`. */\n bulkActions?: (selectedRows: T[], clearSelection: () => void) => React.ReactNode;\n /** A selection-widening control beside the count (e.g. \"select all matching\"). */\n selectionExtra?: SelectionExtraRender<T>;\n /** Bespoke per-row menu (an entity's self-contained 3-dots menu) instead of the auto kebab. */\n renderRowMenu?: (row: T) => React.ReactNode;\n /** Opt-in expandable rows (table layout only) — see {@link DataViewRowDetail}. */\n rowDetail?: DataViewRowDetail<T>;\n /** Opt-in \"Grade\" (cards) layout — a card renderer per row; adds a Grade/Tabela toggle. */\n renderCard?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /** Opt-in \"Quadro\" (board) layout — the loaded page as columns of one field. Needs `renderCard`. */\n board?: BoardConfig<T>;\n /** Opt-in \"Lista\" layout — one full-width, entity-rendered row per record (FUT-733). */\n renderListRow?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /** The Lista's shared columns, so its rows line up by construction not convention. */\n listGroup?: ListGroupConfig<T>;\n /** The page-level partition rendered as tabs under the toolbar (server mode only). */\n scopes?: ScopeConfig[];\n /** The row field the scopes partition by — used only to reject a pill over the same field. */\n scopeFieldId?: string;\n /** Which layout to show first when the user has expressed no preference (default \"table\"). */\n defaultLayout?: DataViewsLayout;\n /**\n * Ignore the remembered cross-screen layout preference and pin to\n * `defaultLayout`. For tables that exist to SHOW a layout — stories, docs,\n * screenshots — not for real screens.\n */\n ignoreStoredLayout?: boolean;\n /** Opt into the responsive inline filter UX (collapsible row / modal). Default keeps the panel. */\n inlineFilters?: boolean;\n /** Keep the inline search visible with no filter fields and no active search. */\n alwaysShowSearch?: boolean;\n /** Server-mode wiring (FUT-180): backend-driven rows/total + re-fetch. Omit for client mode. */\n server?: DataViewServer;\n /** Seed the initial view state (search/pills/ranges/sort) from the URL when no saved view applies. */\n initialState?: DataViewState;\n}\n\n/**\n * Resolve the initial applied view: a `?view=<id>` shortcut (FUT-90) wins, else\n * the user's default view, else none. Drives the seed state + trigger label.\n */\nfunction resolveInitialView(\n views: SavedViewSummary[],\n requestedId: string | null,\n): SavedViewSummary | undefined {\n const requested = requestedId ? views.find((view) => view.id === requestedId) : undefined;\n return requested ?? views.find((view) => view.isDefault);\n}\n\n/**\n * The Exibir panel's view chrome, assembled from the saved-views controller.\n * Extracted so {@link DataViewsTableBase} stays inside its size budget.\n */\nfunction buildDisplayView({\n ctl,\n views,\n activeView,\n dirty,\n onRequestDelete,\n}: {\n ctl: SavedViewsController;\n views: SavedViewSummary[];\n activeView: SavedViewSummary | undefined;\n dirty: boolean;\n /** Opens the confirmation; the delete itself happens on confirm. */\n onRequestDelete: (view: SavedViewSummary) => void;\n}): DisplayPanelView {\n return {\n activeViewName: ctl.activeViewName,\n dirty,\n onReset: () => (activeView ? ctl.applyView(activeView) : ctl.selectMain()),\n // Only offer \"Atualizar\" when there IS a view to update — otherwise the\n // single footer button is a plain \"Salvar visão\".\n onUpdate: activeView ? () => ctl.openEdit(activeView) : undefined,\n onSaveAs: ctl.openCreate,\n // The views themselves, not a dropdown: the panel renders them in its own\n // body (see `data-views-view-nav`), so there is no second popover to clip\n // the row actions.\n nav: {\n views,\n activeViewId: activeView?.id ?? null,\n onSelectView: (id) => {\n const next = id === null ? null : views.find((view) => view.id === id);\n if (next) ctl.applyView(next);\n else ctl.selectMain();\n },\n onEditView: ctl.openEdit,\n onPatchView: (view, changes) => void ctl.patchView(view, changes),\n onDeleteView: onRequestDelete,\n },\n };\n}\n\n/**\n * \"Are there unsaved changes?\" — the dot on Exibir, and whether Redefinir and\n * Salvar visão are live.\n *\n * Two things it gets right that the inline version did not. It compares\n * NORMALISED states (`viewStateKey`), so a filter applied and then removed is\n * clean again rather than clean-looking but flagged; and it re-baselines after\n * a SAVE, because the state just written is the clean one from that point.\n *\n * The baseline is the first state the grid emits for a view, not the view\n * itself: the grid normalises what it is handed — resolving visible columns,\n * filling in layout — so the applied view is never byte-identical to the state\n * it produces.\n */\nfunction useDirtyTracking(ctl: SavedViewsController): {\n dirty: boolean;\n noteState: (state: DataViewState) => void;\n markSaved: () => void;\n} {\n const [dirty, setDirty] = useState(false);\n const [savedAt, setSavedAt] = useState(0);\n const baseline = useRef<string | null>(null);\n const appliedKey = JSON.stringify(ctl.applied ?? null);\n\n useEffect(() => {\n baseline.current = null;\n setDirty(false);\n }, [appliedKey, savedAt]);\n\n return {\n dirty,\n noteState: (state) => {\n ctl.currentRef.current = state;\n const key = viewStateKey(state);\n if (baseline.current === null) {\n baseline.current = key;\n setDirty(false);\n return;\n }\n const next = key !== baseline.current;\n setDirty((prev) => (prev === next ? prev : next));\n },\n markSaved: () => setSavedAt((tick) => tick + 1),\n };\n}\n\n/**\n * The reusable admin table (FUT-89): the DataViews grid plus the full saved-views\n * experience — a views dropdown, a save/edit modal (with preview), and a manage\n * dialog. Framework-agnostic: saved-view persistence and router side-effects are\n * INJECTED (`persistence`, `router`, `initialViewId`) so the host app wires them\n * to its backend + framework router. A default view auto-applies on load; applying\n * a view restores its filters + columns + sort. Each table supplies its own columns\n * + filter fields; everything else is shared.\n */\nexport function DataViewsTableBase<T extends Record<string, unknown>>(\n props: DataViewsTableBaseProps<T>,\n): React.JSX.Element {\n // Forwarded as a whole rather than restated name-by-name twice (as in\n // DataViewsGrid): this component only ADDS the saved-views chrome, and a\n // 30-name destructure repeated in the JSX is exactly where a newly added prop\n // silently stops being passed on.\n const {\n initialViewId = null,\n persistence,\n router,\n columns,\n fields,\n testIdPrefix = \"table\",\n views,\n server,\n initialState,\n } = props;\n const initialView = resolveInitialView(views, initialViewId);\n const columnIds = columns.map((col) => col.id);\n const ctl = useSavedViewsController(persistence, router, columnIds, views, initialView, initialState);\n // Unsaved-changes flag for the Exibir panel. Set from `onStateChange` rather\n // than derived in render because the live state lives in a REF (the grid owns\n // it); the equality bail-out in `setDirty` is what stops the update loop.\n const { dirty, noteState, markSaved } = useDirtyTracking(ctl);\n // Deleting a saved view is confirmed, not immediate — see `DeleteViewDialog`.\n const [pendingDelete, setPendingDelete] = useState<SavedViewSummary | null>(null);\n // The view the panel's \"Redefinir\" restores: the applied one, or the built-in\n // \"Visão principal\" when none is.\n const activeView = views.find((view) => view.name === ctl.activeViewName);\n const displayView = buildDisplayView({\n ctl,\n views,\n activeView,\n dirty,\n onRequestDelete: setPendingDelete,\n });\n // Server-mode lists re-apply URL-derived controls on back/forward (not just\n // the mount seed), but never the grid's own query echoing back through the URL.\n const echo = useUrlEchoGuard(server);\n const syncState = useUrlSyncState(echo.server, initialState, echo.consumeEcho);\n\n return (\n <>\n <ViewMutationErrorAlert error={ctl.mutationError} onClose={ctl.clearMutationError} testIdPrefix={testIdPrefix} />\n <DataViewsGrid<T>\n {...props}\n server={echo.server}\n testIdPrefix={testIdPrefix}\n appliedState={ctl.applied}\n syncState={syncState}\n onStateChange={noteState}\n // The saved-views menu is NOT a toolbar control any more: it lives in\n // the Exibir panel's VISÃO header, passed through `displayView`.\n displayView={displayView}\n />\n {renderViewDialogs(ctl, fields, columns, views, testIdPrefix, markSaved)}\n <DeleteViewDialog\n view={pendingDelete}\n onClose={() => setPendingDelete(null)}\n onConfirm={(view) => {\n void ctl.handleDelete(view);\n setPendingDelete(null);\n }}\n testIdPrefix={testIdPrefix}\n />\n </>\n );\n}\n\n/** The save + manage saved-view dialogs, split out to keep the base thin. */\nfunction renderViewDialogs<T extends Record<string, unknown>>(\n ctl: SavedViewsController,\n fields: DataViewsTableBaseProps<T>[\"fields\"],\n columns: DataViewsTableBaseProps<T>[\"columns\"],\n views: SavedViewSummary[],\n testIdPrefix: string,\n onSaved: () => void,\n): React.JSX.Element {\n return (\n <ViewDialogs<T>\n saveOpen={ctl.saveOpen}\n onCloseSave={ctl.closeSave}\n currentState={ctl.editing ? ctl.editing.state : ctl.currentRef.current}\n fields={fields}\n columns={columns}\n editing={ctl.editing}\n onSave={async (payload) => {\n await ctl.handleSave(payload);\n // The saved state is the clean one from here on.\n onSaved();\n }}\n manageOpen={ctl.manageOpen}\n onCloseManage={() => ctl.setManageOpen(false)}\n views={views}\n onEditFromManage={ctl.editFromManage}\n onDelete={(view) => void ctl.handleDelete(view)}\n testIdPrefix={testIdPrefix}\n />\n );\n}\n"],"mappings":"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","names":[]}