@umodeapporg/ui 0.4.1 → 0.6.0

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 (26) hide show
  1. package/dist/components/Form/RichTextEditor.js +1 -0
  2. package/dist/components/Form/RichTextEditor.js.map +1 -1
  3. package/dist/components/UI/DataTable/DataTableBulkEditDialog.d.ts +3 -3
  4. package/dist/components/UI/DataTable/DataTableBulkEditDialog.js +179 -150
  5. package/dist/components/UI/DataTable/DataTableBulkEditDialog.js.map +1 -1
  6. package/dist/components/UI/DataTable/DataTableBulkEditRowsPicker.d.ts +13 -0
  7. package/dist/components/UI/DataTable/DataTableBulkEditRowsPicker.js +94 -0
  8. package/dist/components/UI/DataTable/DataTableBulkEditRowsPicker.js.map +1 -0
  9. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.d.ts +16 -0
  10. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js +79 -0
  11. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js.map +1 -0
  12. package/dist/components/UI/DataTable/bulkEditValues.d.ts +8 -0
  13. package/dist/components/UI/DataTable/bulkEditValues.js +13 -0
  14. package/dist/components/UI/DataTable/bulkEditValues.js.map +1 -0
  15. package/dist/components/UI/RichTextContent.js +4 -2
  16. package/dist/components/UI/RichTextContent.js.map +1 -1
  17. package/dist/helpers/richText.d.ts +9 -0
  18. package/dist/helpers/richText.js +37 -11
  19. package/dist/helpers/richText.js.map +1 -1
  20. package/dist/locales/en/ui.json.d.ts +13 -1
  21. package/dist/locales/en/ui.json.js +1 -1
  22. package/dist/locales/index.d.ts +24 -0
  23. package/dist/locales/pt/ui.json.d.ts +13 -1
  24. package/dist/locales/pt/ui.json.js +1 -1
  25. package/package.json +1 -1
  26. package/src/assets/styles/base.css +23 -1
@@ -1,31 +1,14 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useMemo, useState, useRef, useEffect } from "react";
3
3
  import { useTranslation } from "react-i18next";
4
- import DatePicker from "../../DateTime/DatePicker.js";
5
- import ControlledCheckbox from "../../Form/ControlledCheckbox.js";
6
4
  import ControlledDialog from "../../Form/ControlledDialog.js";
7
- import ControlledInput from "../../Form/ControlledInput.js";
8
5
  import { ControlledSelect } from "../../Form/ControlledSelect.js";
9
6
  import ControlledViewToggle from "../../Form/ControlledViewToggle.js";
10
- import "classnames";
11
- import "../../Form/ControlledButton.js";
12
- import "tailwind-merge";
13
- import "../ControlledMenu.js";
14
- import "motion/react";
15
- import "react-dom";
16
- import "@floating-ui/dom";
17
- import "../../../motion/motionPresets.js";
18
- import "../../../motion/motionTokens.js";
19
- import { normalizeText } from "../../../helpers/stringHelpers.js";
20
- import "../MaterialSymbol/index.js";
21
- import "date-fns";
22
- import "../../DateTime/DateRangePicker.js";
7
+ import ControlledTooltip from "../ControlledTooltip.js";
23
8
  import NoDataFound from "../NoDataFound/index.js";
24
- import "../../Search/filters/FiltersButton.js";
25
- import "../../../helpers/utilityHelpers.js";
26
- import "../ControlledPopover.js";
27
- import "../../Navigation/UiNavigationContext.js";
28
- import UnifiedSearchBar from "../../Search/UnifiedSearchBar.js";
9
+ import { isBulkValueEmpty, areBulkValuesEqual } from "./bulkEditValues.js";
10
+ import DataTableBulkEditRowsPicker from "./DataTableBulkEditRowsPicker.js";
11
+ import DataTableBulkEditValueEditor from "./DataTableBulkEditValueEditor.js";
29
12
  const resolveDefaultRowLabel = (row, columns) => {
30
13
  const label = (columns ?? []).filter((column) => column.type !== "actions" && !!column.accessor).map((column) => column.accessor?.(row)).find((value) => typeof value === "string" || typeof value === "number");
31
14
  return label == null || label === "" ? row.id : String(label);
@@ -41,158 +24,229 @@ const DataTableBulkEditDialog = ({
41
24
  }) => {
42
25
  const { t } = useTranslation("ui");
43
26
  const editableColumns = useMemo(() => (columns ?? []).filter((column) => !!column.editable), [columns]);
27
+ const [mode, setMode] = useState("same");
44
28
  const [columnId, setColumnId] = useState("");
45
29
  const [value, setValue] = useState("");
30
+ const [perItemColumnIds, setPerItemColumnIds] = useState([]);
31
+ const [drafts, setDrafts] = useState({});
46
32
  const [scope, setScope] = useState("loaded");
47
33
  const [pickedIds, setPickedIds] = useState([]);
48
- const [searchTerm, setSearchTerm] = useState("");
49
34
  const wasOpenRef = useRef(false);
50
35
  useEffect(() => {
51
36
  if (open === wasOpenRef.current) return;
52
37
  wasOpenRef.current = open;
53
38
  if (!open) return;
54
39
  const preselected = Array.from(initialTargetIds ?? []);
40
+ setMode("same");
55
41
  setColumnId(editableColumns[0]?.id ?? "");
56
42
  setValue("");
43
+ setPerItemColumnIds([]);
44
+ setDrafts({});
57
45
  setScope(preselected.length > 0 ? "picked" : "loaded");
58
46
  setPickedIds(preselected);
59
- setSearchTerm("");
60
47
  }, [open, editableColumns, initialTargetIds]);
61
48
  const selectedColumn = useMemo(
62
49
  () => editableColumns.find((column) => column.id === columnId),
63
50
  [editableColumns, columnId]
64
51
  );
65
52
  const editable = selectedColumn?.editable;
66
- const arrayValue = Array.isArray(value) ? value.map(String) : [];
67
- const stringValue = value == null || Array.isArray(value) ? "" : String(value);
68
- const isValueEmpty = Array.isArray(value) ? value.length === 0 : value == null || value === "";
53
+ const selectedPerItemColumns = useMemo(
54
+ () => perItemColumnIds.map((id) => editableColumns.find((column) => column.id === id)).filter((column) => !!column),
55
+ [perItemColumnIds, editableColumns]
56
+ );
69
57
  const resolveLabel = useMemo(
70
58
  () => getRowLabel ?? ((row) => resolveDefaultRowLabel(row, columns ?? [])),
71
59
  [getRowLabel, columns]
72
60
  );
73
- const eligibleRows = useMemo(
74
- () => (rows ?? []).filter((row) => !(editable?.disabled?.(row) ?? false)),
75
- [rows, editable]
76
- );
61
+ const eligibleRows = useMemo(() => {
62
+ if (mode === "same") return (rows ?? []).filter((row) => !(editable?.disabled?.(row) ?? false));
63
+ const referenceColumns = selectedPerItemColumns.length > 0 ? selectedPerItemColumns : editableColumns;
64
+ return (rows ?? []).filter(
65
+ (row) => referenceColumns.some((column) => !(column.editable?.disabled?.(row) ?? false))
66
+ );
67
+ }, [mode, rows, editable, selectedPerItemColumns, editableColumns]);
77
68
  const pickedIdSet = useMemo(() => new Set(pickedIds), [pickedIds]);
78
69
  const targetRows = useMemo(
79
70
  () => scope === "loaded" ? eligibleRows : eligibleRows.filter((row) => pickedIdSet.has(row.id)),
80
71
  [scope, eligibleRows, pickedIdSet]
81
72
  );
82
- const visibleRows = useMemo(() => {
83
- const term = normalizeText(searchTerm.trim());
84
- if (term === "") return eligibleRows;
85
- return eligibleRows.filter((row) => normalizeText(resolveLabel(row)).includes(term));
86
- }, [eligibleRows, searchTerm, resolveLabel]);
87
- const validationError = useMemo(() => {
88
- if (!editable?.validate) return null;
73
+ const isValueEmpty = isBulkValueEmpty(value);
74
+ const sameValueValidationError = useMemo(() => {
75
+ if (mode !== "same" || !editable?.validate) return null;
89
76
  const firstInvalid = targetRows.map((row) => editable.validate?.(value, row) ?? null).find((message) => !!message);
90
77
  return firstInvalid ?? null;
91
- }, [editable, targetRows, value]);
78
+ }, [mode, editable, targetRows, value]);
79
+ const perItemChanges = useMemo(() => {
80
+ if (mode !== "perItem") return [];
81
+ const changes = [];
82
+ targetRows.forEach((row) => {
83
+ const rowDraft = drafts[row.id];
84
+ if (!rowDraft) return;
85
+ selectedPerItemColumns.forEach((column) => {
86
+ if (!Object.prototype.hasOwnProperty.call(rowDraft, column.id)) return;
87
+ if (column.editable?.disabled?.(row) ?? false) return;
88
+ const draftValue = rowDraft[column.id];
89
+ if (areBulkValuesEqual(draftValue, column.editable?.getValue(row) ?? null)) return;
90
+ changes.push({ row, columnId: column.id, value: draftValue });
91
+ });
92
+ });
93
+ return changes;
94
+ }, [mode, targetRows, drafts, selectedPerItemColumns]);
95
+ const perItemValidationError = useMemo(() => {
96
+ const firstInvalid = perItemChanges.map((change) => {
97
+ const column = selectedPerItemColumns.find((entry) => entry.id === change.columnId);
98
+ return column?.editable?.validate?.(change.value, change.row) ?? null;
99
+ }).find((message) => !!message);
100
+ return firstInvalid ?? null;
101
+ }, [perItemChanges, selectedPerItemColumns]);
102
+ const setDraftValue = (rowId, targetColumnId, nextValue) => setDrafts((current) => ({
103
+ ...current,
104
+ [rowId]: { ...current[rowId], [targetColumnId]: nextValue }
105
+ }));
106
+ const resolveCellValue = (row, column) => {
107
+ const rowDraft = drafts[row.id];
108
+ if (rowDraft && Object.prototype.hasOwnProperty.call(rowDraft, column.id)) return rowDraft[column.id];
109
+ return column.editable?.getValue(row) ?? null;
110
+ };
111
+ const isCellDirty = (row, column) => {
112
+ const rowDraft = drafts[row.id];
113
+ if (!rowDraft || !Object.prototype.hasOwnProperty.call(rowDraft, column.id)) return false;
114
+ return !areBulkValuesEqual(rowDraft[column.id], column.editable?.getValue(row) ?? null);
115
+ };
116
+ const modeOptions = [
117
+ { value: "same", label: t("dataTable.bulkEdit.modeSame"), icon: "stacks" },
118
+ { value: "perItem", label: t("dataTable.bulkEdit.modePerItem"), icon: "edit_note" }
119
+ ];
92
120
  const scopeOptions = [
93
121
  { value: "loaded", label: t("dataTable.bulkEdit.scopeLoaded"), icon: "select_all", count: eligibleRows.length },
94
122
  { value: "picked", label: t("dataTable.bulkEdit.scopePicked"), icon: "checklist", count: pickedIdSet.size }
95
123
  ];
96
- const togglePicked = (rowId) => setPickedIds((current) => current.includes(rowId) ? current.filter((id) => id !== rowId) : [...current, rowId]);
97
- const areAllVisiblePicked = visibleRows.length > 0 && visibleRows.every((row) => pickedIdSet.has(row.id));
98
- const toggleAllVisible = () => setPickedIds((current) => {
99
- const visibleIds = visibleRows.map((row) => row.id);
100
- if (areAllVisiblePicked) return current.filter((id) => !visibleIds.includes(id));
101
- return Array.from(/* @__PURE__ */ new Set([...current, ...visibleIds]));
102
- });
103
124
  const handleApply = () => {
125
+ if (mode === "perItem") {
126
+ if (perItemChanges.length === 0) return;
127
+ onApply(perItemChanges);
128
+ onClose();
129
+ return;
130
+ }
104
131
  if (!selectedColumn || targetRows.length === 0) return;
105
132
  onApply(targetRows.map((row) => ({ row, columnId: selectedColumn.id, value })));
106
133
  onClose();
107
134
  };
108
- const renderValueEditor = () => {
109
- if (!editable) return null;
110
- if (editable.renderEditor) {
111
- return editable.renderEditor({ value, onChange: setValue, label: t("dataTable.bulkEdit.newValue") });
112
- }
113
- if (editable.type === "multiselect") {
114
- return /* @__PURE__ */ jsx(
115
- ControlledSelect,
116
- {
117
- className: "w-full",
118
- label: t("dataTable.bulkEdit.newValue"),
119
- options: Array.isArray(editable.options) ? editable.options : [],
120
- selectedValue: arrayValue,
121
- onChangeHook: (selected) => setValue(Array.isArray(selected) ? selected : [selected]),
122
- multiple: true,
123
- searchable: true,
124
- clearable: true
125
- }
126
- );
127
- }
128
- if (editable.type === "select") {
129
- return /* @__PURE__ */ jsx(
130
- ControlledSelect,
131
- {
132
- className: "w-full",
133
- label: t("dataTable.bulkEdit.newValue"),
134
- options: Array.isArray(editable.options) ? editable.options : [],
135
- selectedValue: stringValue,
136
- onChangeHook: (selected) => setValue(typeof selected === "string" ? selected : ""),
137
- multiple: false,
138
- searchable: true,
139
- clearable: true
140
- }
141
- );
135
+ const confirmLabel = mode === "perItem" ? perItemChanges.length === 0 ? t("dataTable.bulkEdit.confirmNoChanges") : t("dataTable.bulkEdit.confirmChanges", { count: perItemChanges.length }) : targetRows.length === 0 ? t("dataTable.bulkEdit.confirmEmpty") : t("dataTable.bulkEdit.confirm", { count: targetRows.length });
136
+ const disableConfirm = mode === "perItem" ? perItemChanges.length === 0 || !!perItemValidationError : !selectedColumn || targetRows.length === 0 || isValueEmpty || !!sameValueValidationError;
137
+ const renderPerItemMatrix = () => {
138
+ if (selectedPerItemColumns.length === 0) {
139
+ return /* @__PURE__ */ jsx(NoDataFound, { size: "sm", variant: "waiting", message: t("dataTable.bulkEdit.pickFieldsHint") });
142
140
  }
143
- if (editable.type === "date") {
144
- const parsed = stringValue ? new Date(stringValue) : void 0;
145
- const startValue = parsed && !Number.isNaN(parsed.getTime()) ? parsed : void 0;
146
- return /* @__PURE__ */ jsx(
147
- DatePicker,
148
- {
149
- label: t("dataTable.bulkEdit.newValue"),
150
- startValue,
151
- onUpdate: (nextValue) => setValue(nextValue ?? "")
152
- }
153
- );
141
+ if (targetRows.length === 0) {
142
+ return /* @__PURE__ */ jsx(NoDataFound, { size: "sm", variant: "waiting", message: t("dataTable.bulkEdit.noTargets") });
154
143
  }
155
- return /* @__PURE__ */ jsx(
156
- ControlledInput,
157
- {
158
- label: t("dataTable.bulkEdit.newValue"),
159
- value: stringValue,
160
- onChange: (event) => setValue(event.target.value),
161
- inputMode: editable.type === "number" ? "decimal" : void 0,
162
- error: !!validationError,
163
- errorMessage: validationError ?? void 0
164
- }
165
- );
144
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-col gap-2 rounded-xl border border-border bg-surface-sunken p-3", children: [
145
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-foreground-muted", children: t("dataTable.bulkEdit.perItemHint") }),
146
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("div", { className: "flex min-w-max flex-col gap-2", children: [
147
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
148
+ /* @__PURE__ */ jsx("span", { className: "w-52 shrink-0 text-xs font-medium uppercase tracking-wide text-foreground-subtle", children: t("dataTable.bulkEdit.itemColumn") }),
149
+ selectedPerItemColumns.map((column) => /* @__PURE__ */ jsx(
150
+ "span",
151
+ {
152
+ className: "w-64 shrink-0 truncate text-xs font-medium uppercase tracking-wide text-foreground-subtle",
153
+ children: column.header
154
+ },
155
+ column.id
156
+ ))
157
+ ] }),
158
+ /* @__PURE__ */ jsx("div", { className: "flex max-h-96 flex-col gap-2 overflow-y-auto pt-1", children: targetRows.map((row) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
159
+ /* @__PURE__ */ jsx(ControlledTooltip, { content: resolveLabel(row), className: "z-[9999]", placement: "top-start", children: /* @__PURE__ */ jsx("span", { className: "w-52 shrink-0 truncate text-sm text-foreground", children: resolveLabel(row) }) }),
160
+ selectedPerItemColumns.map((column) => {
161
+ const cellEditable = column.editable;
162
+ if (!cellEditable) return /* @__PURE__ */ jsx("span", { className: "w-64 shrink-0" }, column.id);
163
+ const isDisabled = cellEditable.disabled?.(row) ?? false;
164
+ const cellValue = resolveCellValue(row, column);
165
+ const dirty = isCellDirty(row, column);
166
+ const cellError = dirty ? cellEditable.validate?.(cellValue, row) ?? null : null;
167
+ return /* @__PURE__ */ jsxs("div", { className: "relative w-64 shrink-0", children: [
168
+ dirty && /* @__PURE__ */ jsx("span", { className: "absolute -right-1 -top-1 z-10 size-2 rounded-full bg-brand", "aria-hidden": true }),
169
+ /* @__PURE__ */ jsx(
170
+ DataTableBulkEditValueEditor,
171
+ {
172
+ editable: cellEditable,
173
+ value: cellValue,
174
+ onChange: (nextValue) => setDraftValue(row.id, column.id, nextValue),
175
+ disabled: isDisabled,
176
+ errorMessage: cellError
177
+ }
178
+ )
179
+ ] }, column.id);
180
+ })
181
+ ] }, row.id)) })
182
+ ] }) })
183
+ ] });
166
184
  };
167
185
  return /* @__PURE__ */ jsx(
168
186
  ControlledDialog,
169
187
  {
170
188
  open,
171
- size: "md",
189
+ size: mode === "perItem" ? "xl" : "md",
172
190
  title: t("dataTable.bulkEdit.title"),
173
- subtitle: t("dataTable.bulkEdit.subtitle"),
191
+ subtitle: mode === "perItem" ? t("dataTable.bulkEdit.subtitlePerItem") : t("dataTable.bulkEdit.subtitle"),
174
192
  onCancel: onClose,
175
193
  onConfirm: handleApply,
176
- confirmLabel: targetRows.length === 0 ? t("dataTable.bulkEdit.confirmEmpty") : t("dataTable.bulkEdit.confirm", { count: targetRows.length }),
194
+ confirmLabel,
177
195
  cancelLabel: t("actions.cancel"),
178
- disableConfirm: !selectedColumn || targetRows.length === 0 || isValueEmpty || !!validationError,
196
+ disableConfirm,
179
197
  children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
180
- /* @__PURE__ */ jsx(
198
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
199
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-foreground-subtle", children: t("dataTable.bulkEdit.modeLabel") }),
200
+ /* @__PURE__ */ jsx(
201
+ ControlledViewToggle,
202
+ {
203
+ options: modeOptions,
204
+ value: mode,
205
+ onChange: setMode,
206
+ showLabels: true,
207
+ size: "sm"
208
+ }
209
+ )
210
+ ] }),
211
+ mode === "same" ? /* @__PURE__ */ jsxs(Fragment, { children: [
212
+ /* @__PURE__ */ jsx(
213
+ ControlledSelect,
214
+ {
215
+ className: "w-full",
216
+ label: t("dataTable.bulkEdit.field"),
217
+ options: editableColumns.map((column) => ({ value: column.id, label: column.header })),
218
+ selectedValue: columnId,
219
+ onChangeHook: (selected) => {
220
+ setColumnId(typeof selected === "string" ? selected : "");
221
+ setValue("");
222
+ },
223
+ multiple: false,
224
+ searchable: true
225
+ }
226
+ ),
227
+ editable && /* @__PURE__ */ jsx(
228
+ DataTableBulkEditValueEditor,
229
+ {
230
+ editable,
231
+ value,
232
+ onChange: setValue,
233
+ label: t("dataTable.bulkEdit.newValue"),
234
+ errorMessage: sameValueValidationError
235
+ }
236
+ )
237
+ ] }) : /* @__PURE__ */ jsx(
181
238
  ControlledSelect,
182
239
  {
183
240
  className: "w-full",
184
- label: t("dataTable.bulkEdit.field"),
241
+ label: t("dataTable.bulkEdit.fields"),
185
242
  options: editableColumns.map((column) => ({ value: column.id, label: column.header })),
186
- selectedValue: columnId,
187
- onChangeHook: (selected) => {
188
- setColumnId(typeof selected === "string" ? selected : "");
189
- setValue("");
190
- },
191
- multiple: false,
192
- searchable: true
243
+ selectedValue: perItemColumnIds,
244
+ onChangeHook: (selected) => setPerItemColumnIds(Array.isArray(selected) ? selected : [selected].filter(Boolean)),
245
+ multiple: true,
246
+ searchable: true,
247
+ clearable: true
193
248
  }
194
249
  ),
195
- renderValueEditor(),
196
250
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
197
251
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-foreground-subtle", children: t("dataTable.bulkEdit.scopeLabel") }),
198
252
  /* @__PURE__ */ jsx(
@@ -206,41 +260,16 @@ const DataTableBulkEditDialog = ({
206
260
  }
207
261
  )
208
262
  ] }),
209
- scope === "picked" && /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-col gap-2 rounded-xl border border-border bg-surface-sunken p-3", children: [
210
- /* @__PURE__ */ jsx(
211
- UnifiedSearchBar,
212
- {
213
- mode: "callback",
214
- label: t("dataTable.bulkEdit.searchRows"),
215
- debounceTime: 200,
216
- minSearchLength: 1,
217
- showClearButton: true,
218
- filterCallback: setSearchTerm,
219
- onClearFilter: () => setSearchTerm("")
220
- }
221
- ),
222
- visibleRows.length === 0 ? /* @__PURE__ */ jsx(NoDataFound, { size: "sm", variant: "search", message: t("dataTable.bulkEdit.noRowsFound") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
223
- /* @__PURE__ */ jsx(
224
- ControlledCheckbox,
225
- {
226
- checked: areAllVisiblePicked,
227
- onChange: toggleAllVisible,
228
- label: t("dataTable.bulkEdit.selectAll", { count: visibleRows.length }),
229
- size: "sm"
230
- }
231
- ),
232
- /* @__PURE__ */ jsx("div", { className: "flex max-h-64 flex-col overflow-y-auto", children: visibleRows.map((row) => /* @__PURE__ */ jsx(
233
- ControlledCheckbox,
234
- {
235
- checked: pickedIdSet.has(row.id),
236
- onChange: () => togglePicked(row.id),
237
- label: resolveLabel(row),
238
- size: "sm"
239
- },
240
- row.id
241
- )) })
242
- ] })
243
- ] })
263
+ scope === "picked" && /* @__PURE__ */ jsx(
264
+ DataTableBulkEditRowsPicker,
265
+ {
266
+ rows: eligibleRows,
267
+ resolveLabel,
268
+ pickedIds,
269
+ onChange: setPickedIds
270
+ }
271
+ ),
272
+ mode === "perItem" && renderPerItemMatrix()
244
273
  ] })
245
274
  }
246
275
  );
@@ -1 +1 @@
1
- {"version":3,"file":"DataTableBulkEditDialog.js","sources":["../../../../src/components/UI/DataTable/DataTableBulkEditDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport DatePicker from '@/components/DateTime/DatePicker';\nimport ControlledCheckbox from '@/components/Form/ControlledCheckbox';\nimport ControlledDialog from '@/components/Form/ControlledDialog';\nimport ControlledInput from '@/components/Form/ControlledInput';\nimport { ControlledSelect } from '@/components/Form/ControlledSelect';\nimport ControlledViewToggle, { type IControlledViewToggleOption } from '@/components/Form/ControlledViewToggle';\nimport { UnifiedSearchBar } from '@/components/Search';\nimport NoDataFound from '@/components/UI/NoDataFound';\nimport { normalizeText } from '@/helpers/stringHelpers';\n\nimport type { IDataTableColumnConfig, IDataTableFillChange, IDataTableRow } from './types';\n\ntype TBulkEditScope = 'loaded' | 'picked';\n\ninterface IDataTableBulkEditDialogProps<T extends IDataTableRow> {\n open: boolean;\n onClose: () => void;\n /** Todas as colunas da tabela: só as editáveis viram opção de campo. */\n columns: Array<IDataTableColumnConfig<T>>;\n /** As linhas carregadas: a página atual na lista, o acumulado do scroll nos cards. */\n rows: Array<T>;\n /** Rótulo de cada linha na lista de escolha. Sem ele, a primeira coluna de texto responde. */\n getRowLabel?: (row: T) => string;\n /** Ids já escolhidos fora do diálogo: abre no modo \"escolher\" com eles marcados. */\n initialTargetIds?: ReadonlyArray<string>;\n onApply: (changes: Array<IDataTableFillChange<T>>) => void;\n}\n\n/**\n * Sem `getRowLabel`, o nome sai da primeira coluna que sabe devolver um valor\n * legível. Colunas que só desenham (`render`, sem `accessor`) não respondem, e é\n * por isso que uma tabela cuja identidade mora numa coluna `custom` precisa\n * declarar `getRowLabel`: aqui só sobraria o id.\n */\nconst resolveDefaultRowLabel = <T extends IDataTableRow>(row: T, columns: Array<IDataTableColumnConfig<T>>): string => {\n const label = (columns ?? [])\n .filter((column) => column.type !== 'actions' && !!column.accessor)\n .map((column) => column.accessor?.(row))\n .find((value) => typeof value === 'string' || typeof value === 'number');\n\n return label == null || label === '' ? row.id : String(label);\n};\n\n/**\n * Aplica um valor a um campo em várias linhas de uma vez. É a mesma edição de\n * célula da planilha, com o alcance escolhido pelo usuário: todas as linhas\n * carregadas ou um subconjunto marcado na lista.\n *\n * O diálogo não persiste nada. Ele devolve as células escritas por `onApply`, que\n * o host entrega ao `onFillCells` da fila de commit: um render, um flush e uma\n * requisição em lote, exatamente como o preenchimento por arraste.\n */\nconst DataTableBulkEditDialog = <T extends IDataTableRow>({\n open,\n onClose,\n columns,\n rows,\n getRowLabel,\n initialTargetIds,\n onApply,\n}: IDataTableBulkEditDialogProps<T>) => {\n const { t } = useTranslation('ui');\n\n const editableColumns = useMemo(() => (columns ?? []).filter((column) => !!column.editable), [columns]);\n\n const [columnId, setColumnId] = useState('');\n const [value, setValue] = useState<unknown>('');\n const [scope, setScope] = useState<TBulkEditScope>('loaded');\n const [pickedIds, setPickedIds] = useState<Array<string>>([]);\n const [searchTerm, setSearchTerm] = useState('');\n\n // Cada abertura recomeça do zero: um valor sobrando da vez anterior seria\n // aplicado em massa por um clique só. O reset acontece na transição de abrir,\n // nunca por uma nova referência de `columns` ou de `initialTargetIds`, que a\n // tela recria ao marcar uma linha atrás do diálogo.\n const wasOpenRef = useRef(false);\n useEffect(() => {\n if (open === wasOpenRef.current) return;\n wasOpenRef.current = open;\n if (!open) return;\n\n const preselected = Array.from(initialTargetIds ?? []);\n setColumnId(editableColumns[0]?.id ?? '');\n setValue('');\n setScope(preselected.length > 0 ? 'picked' : 'loaded');\n setPickedIds(preselected);\n setSearchTerm('');\n }, [open, editableColumns, initialTargetIds]);\n\n const selectedColumn = useMemo(\n () => editableColumns.find((column) => column.id === columnId),\n [editableColumns, columnId],\n );\n const editable = selectedColumn?.editable;\n\n const arrayValue = Array.isArray(value) ? value.map(String) : [];\n const stringValue = value == null || Array.isArray(value) ? '' : String(value);\n // Vazio não aplica: o diálogo abre sem valor, e um Enter distraído limparia o\n // campo em todo o alcance. Limpar continua sendo edição célula a célula.\n const isValueEmpty = Array.isArray(value) ? value.length === 0 : value == null || value === '';\n\n const resolveLabel = useMemo(\n () => getRowLabel ?? ((row: T) => resolveDefaultRowLabel(row, columns ?? [])),\n [getRowLabel, columns],\n );\n\n // Uma linha que o próprio editor recusa (variante, registro bloqueado) não entra\n // no alcance nem aparece na lista: prometer a alteração e descartá-la é pior.\n const eligibleRows = useMemo(\n () => (rows ?? []).filter((row) => !(editable?.disabled?.(row) ?? false)),\n [rows, editable],\n );\n\n const pickedIdSet = useMemo(() => new Set(pickedIds), [pickedIds]);\n\n const targetRows = useMemo(\n () => (scope === 'loaded' ? eligibleRows : eligibleRows.filter((row) => pickedIdSet.has(row.id))),\n [scope, eligibleRows, pickedIdSet],\n );\n\n const visibleRows = useMemo(() => {\n const term = normalizeText(searchTerm.trim());\n if (term === '') return eligibleRows;\n\n return eligibleRows.filter((row) => normalizeText(resolveLabel(row)).includes(term));\n }, [eligibleRows, searchTerm, resolveLabel]);\n\n const validationError = useMemo(() => {\n if (!editable?.validate) return null;\n\n const firstInvalid = targetRows.map((row) => editable.validate?.(value, row) ?? null).find((message) => !!message);\n\n return firstInvalid ?? null;\n }, [editable, targetRows, value]);\n\n const scopeOptions: Array<IControlledViewToggleOption<TBulkEditScope>> = [\n { value: 'loaded', label: t('dataTable.bulkEdit.scopeLoaded'), icon: 'select_all', count: eligibleRows.length },\n { value: 'picked', label: t('dataTable.bulkEdit.scopePicked'), icon: 'checklist', count: pickedIdSet.size },\n ];\n\n const togglePicked = (rowId: string) =>\n setPickedIds((current) => (current.includes(rowId) ? current.filter((id) => id !== rowId) : [...current, rowId]));\n\n const areAllVisiblePicked = visibleRows.length > 0 && visibleRows.every((row) => pickedIdSet.has(row.id));\n\n const toggleAllVisible = () =>\n setPickedIds((current) => {\n const visibleIds = visibleRows.map((row) => row.id);\n if (areAllVisiblePicked) return current.filter((id) => !visibleIds.includes(id));\n\n return Array.from(new Set([...current, ...visibleIds]));\n });\n\n const handleApply = () => {\n if (!selectedColumn || targetRows.length === 0) return;\n\n onApply(targetRows.map((row) => ({ row, columnId: selectedColumn.id, value })));\n onClose();\n };\n\n const renderValueEditor = () => {\n if (!editable) return null;\n\n if (editable.renderEditor) {\n return editable.renderEditor({ value, onChange: setValue, label: t('dataTable.bulkEdit.newValue') });\n }\n\n if (editable.type === 'multiselect') {\n return (\n <ControlledSelect\n className=\"w-full\"\n label={t('dataTable.bulkEdit.newValue')}\n options={Array.isArray(editable.options) ? editable.options : []}\n selectedValue={arrayValue}\n onChangeHook={(selected) => setValue(Array.isArray(selected) ? selected : [selected])}\n multiple\n searchable\n clearable\n />\n );\n }\n\n if (editable.type === 'select') {\n return (\n <ControlledSelect\n className=\"w-full\"\n label={t('dataTable.bulkEdit.newValue')}\n options={Array.isArray(editable.options) ? editable.options : []}\n selectedValue={stringValue}\n onChangeHook={(selected) => setValue(typeof selected === 'string' ? selected : '')}\n multiple={false}\n searchable\n clearable\n />\n );\n }\n\n if (editable.type === 'date') {\n const parsed = stringValue ? new Date(stringValue) : undefined;\n const startValue = parsed && !Number.isNaN(parsed.getTime()) ? parsed : undefined;\n\n return (\n <DatePicker\n label={t('dataTable.bulkEdit.newValue')}\n startValue={startValue}\n onUpdate={(nextValue) => setValue(nextValue ?? '')}\n />\n );\n }\n\n return (\n <ControlledInput\n label={t('dataTable.bulkEdit.newValue')}\n value={stringValue}\n onChange={(event) => setValue(event.target.value)}\n inputMode={editable.type === 'number' ? 'decimal' : undefined}\n error={!!validationError}\n errorMessage={validationError ?? undefined}\n />\n );\n };\n\n return (\n <ControlledDialog\n open={open}\n size=\"md\"\n title={t('dataTable.bulkEdit.title')}\n subtitle={t('dataTable.bulkEdit.subtitle')}\n onCancel={onClose}\n onConfirm={handleApply}\n confirmLabel={\n targetRows.length === 0\n ? t('dataTable.bulkEdit.confirmEmpty')\n : t('dataTable.bulkEdit.confirm', { count: targetRows.length })\n }\n cancelLabel={t('actions.cancel')}\n disableConfirm={!selectedColumn || targetRows.length === 0 || isValueEmpty || !!validationError}\n >\n <div className=\"flex flex-col gap-4\">\n <ControlledSelect\n className=\"w-full\"\n label={t('dataTable.bulkEdit.field')}\n options={editableColumns.map((column) => ({ value: column.id, label: column.header }))}\n selectedValue={columnId}\n onChangeHook={(selected) => {\n setColumnId(typeof selected === 'string' ? selected : '');\n setValue('');\n }}\n multiple={false}\n searchable\n />\n\n {renderValueEditor()}\n\n <div className=\"flex flex-col gap-2\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-foreground-subtle\">\n {t('dataTable.bulkEdit.scopeLabel')}\n </span>\n <ControlledViewToggle<TBulkEditScope>\n options={scopeOptions}\n value={scope}\n onChange={setScope}\n showLabels\n size=\"sm\"\n />\n </div>\n\n {scope === 'picked' && (\n <div className=\"flex min-h-0 flex-col gap-2 rounded-xl border border-border bg-surface-sunken p-3\">\n <UnifiedSearchBar\n mode=\"callback\"\n label={t('dataTable.bulkEdit.searchRows')}\n debounceTime={200}\n minSearchLength={1}\n showClearButton\n filterCallback={setSearchTerm}\n onClearFilter={() => setSearchTerm('')}\n />\n\n {visibleRows.length === 0 ? (\n <NoDataFound size=\"sm\" variant=\"search\" message={t('dataTable.bulkEdit.noRowsFound')} />\n ) : (\n <>\n <ControlledCheckbox\n checked={areAllVisiblePicked}\n onChange={toggleAllVisible}\n label={t('dataTable.bulkEdit.selectAll', { count: visibleRows.length })}\n size=\"sm\"\n />\n <div className=\"flex max-h-64 flex-col overflow-y-auto\">\n {visibleRows.map((row) => (\n <ControlledCheckbox\n key={row.id}\n checked={pickedIdSet.has(row.id)}\n onChange={() => togglePicked(row.id)}\n label={resolveLabel(row)}\n size=\"sm\"\n />\n ))}\n </div>\n </>\n )}\n </div>\n )}\n </div>\n </ControlledDialog>\n );\n};\n\nexport default DataTableBulkEditDialog;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DataTableBulkEditDialog.js","sources":["../../../../src/components/UI/DataTable/DataTableBulkEditDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledDialog from '@/components/Form/ControlledDialog';\nimport { ControlledSelect } from '@/components/Form/ControlledSelect';\nimport ControlledViewToggle, { type IControlledViewToggleOption } from '@/components/Form/ControlledViewToggle';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport NoDataFound from '@/components/UI/NoDataFound';\n\nimport { areBulkValuesEqual, isBulkValueEmpty } from './bulkEditValues';\nimport DataTableBulkEditRowsPicker from './DataTableBulkEditRowsPicker';\nimport DataTableBulkEditValueEditor from './DataTableBulkEditValueEditor';\nimport type { IDataTableColumnConfig, IDataTableFillChange, IDataTableRow } from './types';\n\ntype TBulkEditMode = 'same' | 'perItem';\ntype TBulkEditScope = 'loaded' | 'picked';\n\ninterface IDataTableBulkEditDialogProps<T extends IDataTableRow> {\n open: boolean;\n onClose: () => void;\n /** Todas as colunas da tabela: só as editáveis viram opção de campo. */\n columns: Array<IDataTableColumnConfig<T>>;\n /** As linhas carregadas: a página atual na lista, o acumulado do scroll nos cards. */\n rows: Array<T>;\n /** Rótulo de cada linha na lista de escolha. Sem ele, a primeira coluna de texto responde. */\n getRowLabel?: (row: T) => string;\n /** Ids já escolhidos fora do diálogo: abre no modo \"escolher\" com eles marcados. */\n initialTargetIds?: ReadonlyArray<string>;\n onApply: (changes: Array<IDataTableFillChange<T>>) => void;\n}\n\n/**\n * Sem `getRowLabel`, o nome sai da primeira coluna que sabe devolver um valor\n * legível. Colunas que só desenham (`render`, sem `accessor`) não respondem, e é\n * por isso que uma tabela cuja identidade mora numa coluna `custom` precisa\n * declarar `getRowLabel`: aqui só sobraria o id.\n */\nconst resolveDefaultRowLabel = <T extends IDataTableRow>(row: T, columns: Array<IDataTableColumnConfig<T>>): string => {\n const label = (columns ?? [])\n .filter((column) => column.type !== 'actions' && !!column.accessor)\n .map((column) => column.accessor?.(row))\n .find((value) => typeof value === 'string' || typeof value === 'number');\n\n return label == null || label === '' ? row.id : String(label);\n};\n\n/**\n * Bulk edit in two modes. \"Same value\" applies one value to one field across the\n * chosen scope; \"Values per item\" opens an item x fields matrix prefilled with\n * the current values, and only the changed cells are written.\n *\n * O diálogo não persiste nada. Ele devolve as células escritas por `onApply`, que\n * o host entrega ao `onFillCells` da fila de commit: um render, um flush e uma\n * requisição em lote, exatamente como o preenchimento por arraste.\n */\nconst DataTableBulkEditDialog = <T extends IDataTableRow>({\n open,\n onClose,\n columns,\n rows,\n getRowLabel,\n initialTargetIds,\n onApply,\n}: IDataTableBulkEditDialogProps<T>) => {\n const { t } = useTranslation('ui');\n\n const editableColumns = useMemo(() => (columns ?? []).filter((column) => !!column.editable), [columns]);\n\n const [mode, setMode] = useState<TBulkEditMode>('same');\n const [columnId, setColumnId] = useState('');\n const [value, setValue] = useState<unknown>('');\n const [perItemColumnIds, setPerItemColumnIds] = useState<Array<string>>([]);\n const [drafts, setDrafts] = useState<Record<string, Record<string, unknown>>>({});\n const [scope, setScope] = useState<TBulkEditScope>('loaded');\n const [pickedIds, setPickedIds] = useState<Array<string>>([]);\n\n // Cada abertura recomeça do zero: um valor ou rascunho sobrando da vez anterior\n // seria aplicado em massa por um clique só. O reset acontece na transição de\n // abrir, nunca por uma nova referência de `columns` ou de `initialTargetIds`,\n // que a tela recria ao marcar uma linha atrás do diálogo.\n const wasOpenRef = useRef(false);\n useEffect(() => {\n if (open === wasOpenRef.current) return;\n wasOpenRef.current = open;\n if (!open) return;\n\n const preselected = Array.from(initialTargetIds ?? []);\n setMode('same');\n setColumnId(editableColumns[0]?.id ?? '');\n setValue('');\n setPerItemColumnIds([]);\n setDrafts({});\n setScope(preselected.length > 0 ? 'picked' : 'loaded');\n setPickedIds(preselected);\n }, [open, editableColumns, initialTargetIds]);\n\n const selectedColumn = useMemo(\n () => editableColumns.find((column) => column.id === columnId),\n [editableColumns, columnId],\n );\n const editable = selectedColumn?.editable;\n\n const selectedPerItemColumns = useMemo(\n () =>\n perItemColumnIds\n .map((id) => editableColumns.find((column) => column.id === id))\n .filter((column): column is IDataTableColumnConfig<T> => !!column),\n [perItemColumnIds, editableColumns],\n );\n\n const resolveLabel = useMemo(\n () => getRowLabel ?? ((row: T) => resolveDefaultRowLabel(row, columns ?? [])),\n [getRowLabel, columns],\n );\n\n // Uma linha que os editores recusam (variante, registro bloqueado) não entra\n // no alcance nem aparece nas listas: prometer a alteração e descartá-la é pior.\n const eligibleRows = useMemo(() => {\n if (mode === 'same') return (rows ?? []).filter((row) => !(editable?.disabled?.(row) ?? false));\n\n const referenceColumns = selectedPerItemColumns.length > 0 ? selectedPerItemColumns : editableColumns;\n return (rows ?? []).filter((row) =>\n referenceColumns.some((column) => !(column.editable?.disabled?.(row) ?? false)),\n );\n }, [mode, rows, editable, selectedPerItemColumns, editableColumns]);\n\n const pickedIdSet = useMemo(() => new Set(pickedIds), [pickedIds]);\n\n const targetRows = useMemo(\n () => (scope === 'loaded' ? eligibleRows : eligibleRows.filter((row) => pickedIdSet.has(row.id))),\n [scope, eligibleRows, pickedIdSet],\n );\n\n // Vazio não aplica no modo \"mesmo valor\": o diálogo abre sem valor, e um Enter\n // distraído limparia o campo em todo o alcance. Na matriz por item, limpar uma\n // célula é uma alteração explícita e legítima.\n const isValueEmpty = isBulkValueEmpty(value);\n\n const sameValueValidationError = useMemo(() => {\n if (mode !== 'same' || !editable?.validate) return null;\n\n const firstInvalid = targetRows.map((row) => editable.validate?.(value, row) ?? null).find((message) => !!message);\n\n return firstInvalid ?? null;\n }, [mode, editable, targetRows, value]);\n\n // Only what actually changed becomes a write: a cell with no draft, or edited\n // back to the current value, stays out.\n const perItemChanges = useMemo(() => {\n if (mode !== 'perItem') return [];\n\n const changes: Array<IDataTableFillChange<T>> = [];\n targetRows.forEach((row) => {\n const rowDraft = drafts[row.id];\n if (!rowDraft) return;\n\n selectedPerItemColumns.forEach((column) => {\n if (!Object.prototype.hasOwnProperty.call(rowDraft, column.id)) return;\n if (column.editable?.disabled?.(row) ?? false) return;\n\n const draftValue = rowDraft[column.id];\n if (areBulkValuesEqual(draftValue, column.editable?.getValue(row) ?? null)) return;\n\n changes.push({ row, columnId: column.id, value: draftValue });\n });\n });\n\n return changes;\n }, [mode, targetRows, drafts, selectedPerItemColumns]);\n\n const perItemValidationError = useMemo(() => {\n const firstInvalid = perItemChanges\n .map((change) => {\n const column = selectedPerItemColumns.find((entry) => entry.id === change.columnId);\n return column?.editable?.validate?.(change.value, change.row) ?? null;\n })\n .find((message) => !!message);\n\n return firstInvalid ?? null;\n }, [perItemChanges, selectedPerItemColumns]);\n\n const setDraftValue = (rowId: string, targetColumnId: string, nextValue: unknown) =>\n setDrafts((current) => ({\n ...current,\n [rowId]: { ...current[rowId], [targetColumnId]: nextValue },\n }));\n\n const resolveCellValue = (row: T, column: IDataTableColumnConfig<T>): unknown => {\n const rowDraft = drafts[row.id];\n if (rowDraft && Object.prototype.hasOwnProperty.call(rowDraft, column.id)) return rowDraft[column.id];\n\n return column.editable?.getValue(row) ?? null;\n };\n\n const isCellDirty = (row: T, column: IDataTableColumnConfig<T>): boolean => {\n const rowDraft = drafts[row.id];\n if (!rowDraft || !Object.prototype.hasOwnProperty.call(rowDraft, column.id)) return false;\n\n return !areBulkValuesEqual(rowDraft[column.id], column.editable?.getValue(row) ?? null);\n };\n\n const modeOptions: Array<IControlledViewToggleOption<TBulkEditMode>> = [\n { value: 'same', label: t('dataTable.bulkEdit.modeSame'), icon: 'stacks' },\n { value: 'perItem', label: t('dataTable.bulkEdit.modePerItem'), icon: 'edit_note' },\n ];\n\n const scopeOptions: Array<IControlledViewToggleOption<TBulkEditScope>> = [\n { value: 'loaded', label: t('dataTable.bulkEdit.scopeLoaded'), icon: 'select_all', count: eligibleRows.length },\n { value: 'picked', label: t('dataTable.bulkEdit.scopePicked'), icon: 'checklist', count: pickedIdSet.size },\n ];\n\n const handleApply = () => {\n if (mode === 'perItem') {\n if (perItemChanges.length === 0) return;\n\n onApply(perItemChanges);\n onClose();\n return;\n }\n\n if (!selectedColumn || targetRows.length === 0) return;\n\n onApply(targetRows.map((row) => ({ row, columnId: selectedColumn.id, value })));\n onClose();\n };\n\n const confirmLabel =\n mode === 'perItem'\n ? perItemChanges.length === 0\n ? t('dataTable.bulkEdit.confirmNoChanges')\n : t('dataTable.bulkEdit.confirmChanges', { count: perItemChanges.length })\n : targetRows.length === 0\n ? t('dataTable.bulkEdit.confirmEmpty')\n : t('dataTable.bulkEdit.confirm', { count: targetRows.length });\n\n const disableConfirm =\n mode === 'perItem'\n ? perItemChanges.length === 0 || !!perItemValidationError\n : !selectedColumn || targetRows.length === 0 || isValueEmpty || !!sameValueValidationError;\n\n const renderPerItemMatrix = () => {\n if (selectedPerItemColumns.length === 0) {\n return <NoDataFound size=\"sm\" variant=\"waiting\" message={t('dataTable.bulkEdit.pickFieldsHint')} />;\n }\n\n if (targetRows.length === 0) {\n return <NoDataFound size=\"sm\" variant=\"waiting\" message={t('dataTable.bulkEdit.noTargets')} />;\n }\n\n return (\n <div className=\"flex min-h-0 flex-col gap-2 rounded-xl border border-border bg-surface-sunken p-3\">\n <span className=\"text-xs text-foreground-muted\">{t('dataTable.bulkEdit.perItemHint')}</span>\n <div className=\"overflow-x-auto\">\n <div className=\"flex min-w-max flex-col gap-2\">\n <div className=\"flex items-center gap-3\">\n <span className=\"w-52 shrink-0 text-xs font-medium uppercase tracking-wide text-foreground-subtle\">\n {t('dataTable.bulkEdit.itemColumn')}\n </span>\n {selectedPerItemColumns.map((column) => (\n <span\n key={column.id}\n className=\"w-64 shrink-0 truncate text-xs font-medium uppercase tracking-wide text-foreground-subtle\"\n >\n {column.header}\n </span>\n ))}\n </div>\n\n <div className=\"flex max-h-96 flex-col gap-2 overflow-y-auto pt-1\">\n {targetRows.map((row) => (\n <div key={row.id} className=\"flex items-center gap-3\">\n <ControlledTooltip content={resolveLabel(row)} className=\"z-[9999]\" placement=\"top-start\">\n <span className=\"w-52 shrink-0 truncate text-sm text-foreground\">{resolveLabel(row)}</span>\n </ControlledTooltip>\n {selectedPerItemColumns.map((column) => {\n const cellEditable = column.editable;\n if (!cellEditable) return <span key={column.id} className=\"w-64 shrink-0\" />;\n\n const isDisabled = cellEditable.disabled?.(row) ?? false;\n const cellValue = resolveCellValue(row, column);\n const dirty = isCellDirty(row, column);\n const cellError = dirty ? (cellEditable.validate?.(cellValue, row) ?? null) : null;\n\n return (\n <div key={column.id} className=\"relative w-64 shrink-0\">\n {dirty && (\n <span className=\"absolute -right-1 -top-1 z-10 size-2 rounded-full bg-brand\" aria-hidden />\n )}\n <DataTableBulkEditValueEditor\n editable={cellEditable}\n value={cellValue}\n onChange={(nextValue) => setDraftValue(row.id, column.id, nextValue)}\n disabled={isDisabled}\n errorMessage={cellError}\n />\n </div>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n </div>\n </div>\n );\n };\n\n return (\n <ControlledDialog\n open={open}\n size={mode === 'perItem' ? 'xl' : 'md'}\n title={t('dataTable.bulkEdit.title')}\n subtitle={mode === 'perItem' ? t('dataTable.bulkEdit.subtitlePerItem') : t('dataTable.bulkEdit.subtitle')}\n onCancel={onClose}\n onConfirm={handleApply}\n confirmLabel={confirmLabel}\n cancelLabel={t('actions.cancel')}\n disableConfirm={disableConfirm}\n >\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-foreground-subtle\">\n {t('dataTable.bulkEdit.modeLabel')}\n </span>\n <ControlledViewToggle<TBulkEditMode>\n options={modeOptions}\n value={mode}\n onChange={setMode}\n showLabels\n size=\"sm\"\n />\n </div>\n\n {mode === 'same' ? (\n <>\n <ControlledSelect\n className=\"w-full\"\n label={t('dataTable.bulkEdit.field')}\n options={editableColumns.map((column) => ({ value: column.id, label: column.header }))}\n selectedValue={columnId}\n onChangeHook={(selected) => {\n setColumnId(typeof selected === 'string' ? selected : '');\n setValue('');\n }}\n multiple={false}\n searchable\n />\n\n {editable && (\n <DataTableBulkEditValueEditor\n editable={editable}\n value={value}\n onChange={setValue}\n label={t('dataTable.bulkEdit.newValue')}\n errorMessage={sameValueValidationError}\n />\n )}\n </>\n ) : (\n <ControlledSelect\n className=\"w-full\"\n label={t('dataTable.bulkEdit.fields')}\n options={editableColumns.map((column) => ({ value: column.id, label: column.header }))}\n selectedValue={perItemColumnIds}\n onChangeHook={(selected) =>\n setPerItemColumnIds(Array.isArray(selected) ? selected : [selected].filter(Boolean))\n }\n multiple\n searchable\n clearable\n />\n )}\n\n <div className=\"flex flex-col gap-2\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-foreground-subtle\">\n {t('dataTable.bulkEdit.scopeLabel')}\n </span>\n <ControlledViewToggle<TBulkEditScope>\n options={scopeOptions}\n value={scope}\n onChange={setScope}\n showLabels\n size=\"sm\"\n />\n </div>\n\n {scope === 'picked' && (\n <DataTableBulkEditRowsPicker\n rows={eligibleRows}\n resolveLabel={resolveLabel}\n pickedIds={pickedIds}\n onChange={setPickedIds}\n />\n )}\n\n {mode === 'perItem' && renderPerItemMatrix()}\n </div>\n </ControlledDialog>\n );\n};\n\nexport default DataTableBulkEditDialog;\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { IDataTableRow } from './types';
2
+ interface IDataTableBulkEditRowsPickerProps<T extends IDataTableRow> {
3
+ rows: Array<T>;
4
+ resolveLabel: (row: T) => string;
5
+ pickedIds: Array<string>;
6
+ onChange: (nextPickedIds: Array<string>) => void;
7
+ }
8
+ /**
9
+ * The scope picking list: search, toggle the visible ones, toggle one by one.
10
+ * It is the same in both dialog modes, so it lives outside of it.
11
+ */
12
+ declare const DataTableBulkEditRowsPicker: <T extends IDataTableRow>({ rows, resolveLabel, pickedIds, onChange, }: IDataTableBulkEditRowsPickerProps<T>) => import("react").JSX.Element;
13
+ export default DataTableBulkEditRowsPicker;
@@ -0,0 +1,94 @@
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { useState, useMemo } from "react";
3
+ import { useTranslation } from "react-i18next";
4
+ import ControlledCheckbox from "../../Form/ControlledCheckbox.js";
5
+ import "classnames";
6
+ import "../../Form/ControlledButton.js";
7
+ import "tailwind-merge";
8
+ import "../ControlledMenu.js";
9
+ import "motion/react";
10
+ import "react-dom";
11
+ import "@floating-ui/dom";
12
+ import "../../../motion/motionPresets.js";
13
+ import "../../../motion/motionTokens.js";
14
+ import { normalizeText } from "../../../helpers/stringHelpers.js";
15
+ import "../MaterialSymbol/index.js";
16
+ import "date-fns";
17
+ import "date-fns/locale";
18
+ import "react-day-picker";
19
+ import "../ControlledPopover.js";
20
+ import "react-day-picker/style.css";
21
+ import "../../DateTime/DateRangePicker.js";
22
+ import "react-infinite-scroll-component";
23
+ import "react-window";
24
+ import NoDataFound from "../NoDataFound/index.js";
25
+ /* empty css */
26
+ import "../../Search/filters/FiltersButton.js";
27
+ import "../../../helpers/utilityHelpers.js";
28
+ import "../../Form/ControlledDialog.js";
29
+ import "../../Navigation/UiNavigationContext.js";
30
+ import UnifiedSearchBar from "../../Search/UnifiedSearchBar.js";
31
+ const DataTableBulkEditRowsPicker = ({
32
+ rows,
33
+ resolveLabel,
34
+ pickedIds,
35
+ onChange
36
+ }) => {
37
+ const { t } = useTranslation("ui");
38
+ const [searchTerm, setSearchTerm] = useState("");
39
+ const pickedIdSet = useMemo(() => new Set(pickedIds), [pickedIds]);
40
+ const visibleRows = useMemo(() => {
41
+ const term = normalizeText(searchTerm.trim());
42
+ if (term === "") return rows;
43
+ return rows.filter((row) => normalizeText(resolveLabel(row)).includes(term));
44
+ }, [rows, searchTerm, resolveLabel]);
45
+ const areAllVisiblePicked = visibleRows.length > 0 && visibleRows.every((row) => pickedIdSet.has(row.id));
46
+ const toggleAllVisible = () => {
47
+ const visibleIds = visibleRows.map((row) => row.id);
48
+ if (areAllVisiblePicked) {
49
+ onChange(pickedIds.filter((id) => !visibleIds.includes(id)));
50
+ return;
51
+ }
52
+ onChange(Array.from(/* @__PURE__ */ new Set([...pickedIds, ...visibleIds])));
53
+ };
54
+ const togglePicked = (rowId) => onChange(pickedIds.includes(rowId) ? pickedIds.filter((id) => id !== rowId) : [...pickedIds, rowId]);
55
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-col gap-2 rounded-xl border border-border bg-surface-sunken p-3", children: [
56
+ /* @__PURE__ */ jsx(
57
+ UnifiedSearchBar,
58
+ {
59
+ mode: "callback",
60
+ label: t("dataTable.bulkEdit.searchRows"),
61
+ debounceTime: 200,
62
+ minSearchLength: 1,
63
+ showClearButton: true,
64
+ filterCallback: setSearchTerm,
65
+ onClearFilter: () => setSearchTerm("")
66
+ }
67
+ ),
68
+ visibleRows.length === 0 ? /* @__PURE__ */ jsx(NoDataFound, { size: "sm", variant: "search", message: t("dataTable.bulkEdit.noRowsFound") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
69
+ /* @__PURE__ */ jsx(
70
+ ControlledCheckbox,
71
+ {
72
+ checked: areAllVisiblePicked,
73
+ onChange: toggleAllVisible,
74
+ label: t("dataTable.bulkEdit.selectAll", { count: visibleRows.length }),
75
+ size: "sm"
76
+ }
77
+ ),
78
+ /* @__PURE__ */ jsx("div", { className: "flex max-h-64 flex-col overflow-y-auto", children: visibleRows.map((row) => /* @__PURE__ */ jsx(
79
+ ControlledCheckbox,
80
+ {
81
+ checked: pickedIdSet.has(row.id),
82
+ onChange: () => togglePicked(row.id),
83
+ label: resolveLabel(row),
84
+ size: "sm"
85
+ },
86
+ row.id
87
+ )) })
88
+ ] })
89
+ ] });
90
+ };
91
+ export {
92
+ DataTableBulkEditRowsPicker as default
93
+ };
94
+ //# sourceMappingURL=DataTableBulkEditRowsPicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DataTableBulkEditRowsPicker.js","sources":["../../../../src/components/UI/DataTable/DataTableBulkEditRowsPicker.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledCheckbox from '@/components/Form/ControlledCheckbox';\nimport { UnifiedSearchBar } from '@/components/Search';\nimport NoDataFound from '@/components/UI/NoDataFound';\nimport { normalizeText } from '@/helpers/stringHelpers';\n\nimport type { IDataTableRow } from './types';\n\ninterface IDataTableBulkEditRowsPickerProps<T extends IDataTableRow> {\n rows: Array<T>;\n resolveLabel: (row: T) => string;\n pickedIds: Array<string>;\n onChange: (nextPickedIds: Array<string>) => void;\n}\n\n/**\n * The scope picking list: search, toggle the visible ones, toggle one by one.\n * It is the same in both dialog modes, so it lives outside of it.\n */\nconst DataTableBulkEditRowsPicker = <T extends IDataTableRow>({\n rows,\n resolveLabel,\n pickedIds,\n onChange,\n}: IDataTableBulkEditRowsPickerProps<T>) => {\n const { t } = useTranslation('ui');\n const [searchTerm, setSearchTerm] = useState('');\n\n const pickedIdSet = useMemo(() => new Set(pickedIds), [pickedIds]);\n\n const visibleRows = useMemo(() => {\n const term = normalizeText(searchTerm.trim());\n if (term === '') return rows;\n\n return rows.filter((row) => normalizeText(resolveLabel(row)).includes(term));\n }, [rows, searchTerm, resolveLabel]);\n\n const areAllVisiblePicked = visibleRows.length > 0 && visibleRows.every((row) => pickedIdSet.has(row.id));\n\n const toggleAllVisible = () => {\n const visibleIds = visibleRows.map((row) => row.id);\n if (areAllVisiblePicked) {\n onChange(pickedIds.filter((id) => !visibleIds.includes(id)));\n return;\n }\n\n onChange(Array.from(new Set([...pickedIds, ...visibleIds])));\n };\n\n const togglePicked = (rowId: string) =>\n onChange(pickedIds.includes(rowId) ? pickedIds.filter((id) => id !== rowId) : [...pickedIds, rowId]);\n\n return (\n <div className=\"flex min-h-0 flex-col gap-2 rounded-xl border border-border bg-surface-sunken p-3\">\n <UnifiedSearchBar\n mode=\"callback\"\n label={t('dataTable.bulkEdit.searchRows')}\n debounceTime={200}\n minSearchLength={1}\n showClearButton\n filterCallback={setSearchTerm}\n onClearFilter={() => setSearchTerm('')}\n />\n\n {visibleRows.length === 0 ? (\n <NoDataFound size=\"sm\" variant=\"search\" message={t('dataTable.bulkEdit.noRowsFound')} />\n ) : (\n <>\n <ControlledCheckbox\n checked={areAllVisiblePicked}\n onChange={toggleAllVisible}\n label={t('dataTable.bulkEdit.selectAll', { count: visibleRows.length })}\n size=\"sm\"\n />\n <div className=\"flex max-h-64 flex-col overflow-y-auto\">\n {visibleRows.map((row) => (\n <ControlledCheckbox\n key={row.id}\n checked={pickedIdSet.has(row.id)}\n onChange={() => togglePicked(row.id)}\n label={resolveLabel(row)}\n size=\"sm\"\n />\n ))}\n </div>\n </>\n )}\n </div>\n );\n};\n\nexport default DataTableBulkEditRowsPicker;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBA,MAAM,8BAA8B,CAA0B;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAA4C;AAC1C,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,EAAE;AAE/C,QAAM,cAAc,QAAQ,MAAM,IAAI,IAAI,SAAS,GAAG,CAAC,SAAS,CAAC;AAEjE,QAAM,cAAc,QAAQ,MAAM;AAChC,UAAM,OAAO,cAAc,WAAW,KAAA,CAAM;AAC5C,QAAI,SAAS,GAAI,QAAO;AAExB,WAAO,KAAK,OAAO,CAAC,QAAQ,cAAc,aAAa,GAAG,CAAC,EAAE,SAAS,IAAI,CAAC;AAAA,EAC7E,GAAG,CAAC,MAAM,YAAY,YAAY,CAAC;AAEnC,QAAM,sBAAsB,YAAY,SAAS,KAAK,YAAY,MAAM,CAAC,QAAQ,YAAY,IAAI,IAAI,EAAE,CAAC;AAExG,QAAM,mBAAmB,MAAM;AAC7B,UAAM,aAAa,YAAY,IAAI,CAAC,QAAQ,IAAI,EAAE;AAClD,QAAI,qBAAqB;AACvB,eAAS,UAAU,OAAO,CAAC,OAAO,CAAC,WAAW,SAAS,EAAE,CAAC,CAAC;AAC3D;AAAA,IACF;AAEA,aAAS,MAAM,KAAK,oBAAI,IAAI,CAAC,GAAG,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC;AAAA,EAC7D;AAEA,QAAM,eAAe,CAAC,UACpB,SAAS,UAAU,SAAS,KAAK,IAAI,UAAU,OAAO,CAAC,OAAO,OAAO,KAAK,IAAI,CAAC,GAAG,WAAW,KAAK,CAAC;AAErG,SACE,qBAAC,OAAA,EAAI,WAAU,qFACb,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAO,EAAE,+BAA+B;AAAA,QACxC,cAAc;AAAA,QACd,iBAAiB;AAAA,QACjB,iBAAe;AAAA,QACf,gBAAgB;AAAA,QAChB,eAAe,MAAM,cAAc,EAAE;AAAA,MAAA;AAAA,IAAA;AAAA,IAGtC,YAAY,WAAW,IACtB,oBAAC,eAAY,MAAK,MAAK,SAAQ,UAAS,SAAS,EAAE,gCAAgC,EAAA,CAAG,IAEtF,qBAAA,UAAA,EACE,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAAS;AAAA,UACT,UAAU;AAAA,UACV,OAAO,EAAE,gCAAgC,EAAE,OAAO,YAAY,QAAQ;AAAA,UACtE,MAAK;AAAA,QAAA;AAAA,MAAA;AAAA,0BAEN,OAAA,EAAI,WAAU,0CACZ,UAAA,YAAY,IAAI,CAAC,QAChB;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,SAAS,YAAY,IAAI,IAAI,EAAE;AAAA,UAC/B,UAAU,MAAM,aAAa,IAAI,EAAE;AAAA,UACnC,OAAO,aAAa,GAAG;AAAA,UACvB,MAAK;AAAA,QAAA;AAAA,QAJA,IAAI;AAAA,MAAA,CAMZ,EAAA,CACH;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GAEJ;AAEJ;"}