@pulog/component 0.0.0-stage → 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,3844 @@
1
+ import { SelectionProvider, useSelection } from './chunk-3JJG43AS.js';
2
+ export { SelectionProvider, SelectionSummary, useSelection } from './chunk-3JJG43AS.js';
3
+ import React14, { createContext, useState, useCallback, useContext, useMemo, useRef, useEffect } from 'react';
4
+ import { Button, Checkbox, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Input, Calendar, Card, Badge, Popover, PopoverTrigger, PopoverContent, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Skeleton, EmptyStateIllustration } from '@pulog/ui';
5
+ import { Search, CalendarSearch, Filter, Plus, Minus, ChevronsLeft, ChevronLeft, ChevronRight, ChevronsRight, Loader2, X, Check, ChevronDown, Columns3, Eye, EyeOff, Pin, ArrowLeftToLine, ArrowRightToLine, PinOff, AlertCircle, ArrowUpDown, ArrowUpNarrowWide, ArrowDownNarrowWide, Copy } from 'lucide-react';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+ import { cn } from '@pulog/utils';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { format } from 'date-fns';
10
+ import { enUS as enUS$1 } from 'date-fns/locale';
11
+ import { enUS } from 'react-day-picker/locale';
12
+
13
+ // src/data-table/lib/filter-utils.ts
14
+ function parseDateValue(value) {
15
+ if (value instanceof Date) {
16
+ return isNaN(value.getTime()) ? null : new Date(value.getTime());
17
+ }
18
+ if (typeof value === "string") {
19
+ const trimmed = value.trim();
20
+ if (!trimmed) return null;
21
+ const dateOnlyMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(trimmed);
22
+ if (dateOnlyMatch) {
23
+ const year = Number(dateOnlyMatch[1]);
24
+ const month = Number(dateOnlyMatch[2]) - 1;
25
+ const day = Number(dateOnlyMatch[3]);
26
+ const parsed2 = new Date(year, month, day);
27
+ return isNaN(parsed2.getTime()) ? null : parsed2;
28
+ }
29
+ const parsed = new Date(trimmed);
30
+ return isNaN(parsed.getTime()) ? null : parsed;
31
+ }
32
+ if (typeof value === "number") {
33
+ const parsed = new Date(value);
34
+ return isNaN(parsed.getTime()) ? null : parsed;
35
+ }
36
+ return null;
37
+ }
38
+ function formatDateKey(value) {
39
+ const parsed = parseDateValue(value);
40
+ if (!parsed) return null;
41
+ const year = parsed.getFullYear();
42
+ const month = String(parsed.getMonth() + 1).padStart(2, "0");
43
+ const day = String(parsed.getDate()).padStart(2, "0");
44
+ return `${year}-${month}-${day}`;
45
+ }
46
+ function isValidFilterValue(filter) {
47
+ if (!filter.field) {
48
+ return false;
49
+ }
50
+ switch (filter.type) {
51
+ case "dateRange":
52
+ return Array.isArray(filter.value) && filter.value.length === 2 && parseDateValue(filter.value[0]) !== null && parseDateValue(filter.value[1]) !== null;
53
+ case "range":
54
+ return Array.isArray(filter.value) && filter.value.length === 2 && typeof filter.value[0] === "number" && typeof filter.value[1] === "number";
55
+ case "multiSelect":
56
+ case "dateTree":
57
+ return Array.isArray(filter.value) && filter.value.length > 0;
58
+ case "checkbox":
59
+ return typeof filter.value === "boolean";
60
+ case "date":
61
+ case "select":
62
+ case "search":
63
+ if (Array.isArray(filter.value)) {
64
+ return filter.value.length > 0;
65
+ }
66
+ return filter.value !== null && filter.value !== void 0 && filter.value !== "";
67
+ default:
68
+ return filter.value !== null && filter.value !== void 0;
69
+ }
70
+ }
71
+ function applyFilters(data, filters) {
72
+ return data.filter((item) => {
73
+ return filters.every((filter) => matchesFilter(item, filter));
74
+ });
75
+ }
76
+ function matchesFilter(item, filter) {
77
+ const value = item[filter.field];
78
+ switch (filter.type) {
79
+ case "search": {
80
+ const cellStr = value == null ? "" : String(value);
81
+ const lower = cellStr.toLowerCase();
82
+ const searchValue = filter.value;
83
+ if (Array.isArray(searchValue)) {
84
+ return searchValue.some((item2) => lower.includes(String(item2).toLowerCase()));
85
+ }
86
+ return lower.includes(String(searchValue).toLowerCase());
87
+ }
88
+ case "date":
89
+ return formatDateKey(value) === formatDateKey(filter.value);
90
+ case "dateRange": {
91
+ const itemDateKey = formatDateKey(value);
92
+ const [fromValue, toValue] = Array.isArray(filter.value) ? filter.value : [filter.value?.from, filter.value?.to];
93
+ const fromDateKey = formatDateKey(fromValue);
94
+ const toDateKey = formatDateKey(toValue);
95
+ if (!itemDateKey || !fromDateKey || !toDateKey) return false;
96
+ return itemDateKey >= fromDateKey && itemDateKey <= toDateKey;
97
+ }
98
+ case "select":
99
+ return String(value ?? "") === String(filter.value);
100
+ case "multiSelect":
101
+ if (!Array.isArray(filter.value) || filter.value.length === 0) return false;
102
+ return filter.value.includes(String(value ?? ""));
103
+ case "dateTree": {
104
+ if (!Array.isArray(filter.value) || filter.value.length === 0) return false;
105
+ const itemKey = formatDateKey(value);
106
+ return itemKey !== null && filter.value.includes(itemKey);
107
+ }
108
+ case "checkbox":
109
+ return Boolean(value) === Boolean(filter.value);
110
+ case "range": {
111
+ const numValue = Number(value);
112
+ const [min, max] = filter.value;
113
+ return numValue >= min && numValue <= max;
114
+ }
115
+ default:
116
+ return true;
117
+ }
118
+ }
119
+ function groupFiltersByField(filters) {
120
+ return filters.reduce(
121
+ (acc, filter) => {
122
+ if (!acc[filter.field]) {
123
+ acc[filter.field] = [];
124
+ }
125
+ acc[filter.field].push(filter);
126
+ return acc;
127
+ },
128
+ {}
129
+ );
130
+ }
131
+ function removeFilter(filters, fieldToRemove, valueToRemove) {
132
+ return filters.filter((filter) => {
133
+ if (filter.field !== fieldToRemove) {
134
+ return true;
135
+ }
136
+ if (valueToRemove === void 0) {
137
+ return false;
138
+ }
139
+ return filter.value !== valueToRemove;
140
+ });
141
+ }
142
+ function clearFieldFilters(filters, fieldsToClear) {
143
+ return filters.filter((filter) => !fieldsToClear.includes(filter.field));
144
+ }
145
+ function hasActiveFilters(filters) {
146
+ return filters.length > 0 && filters.some(isValidFilterValue);
147
+ }
148
+ var FilterContext = createContext(void 0);
149
+ function FilterProvider({ children }) {
150
+ const [filters, setFilters] = useState([]);
151
+ const addFilter = useCallback((filter) => {
152
+ setFilters((prev) => [...prev, filter]);
153
+ }, []);
154
+ const updateFilter = useCallback(
155
+ (fieldId, newFilter) => {
156
+ setFilters(
157
+ (prev) => prev.map(
158
+ (f) => f.field === fieldId ? { ...f, ...newFilter } : f
159
+ )
160
+ );
161
+ },
162
+ []
163
+ );
164
+ const removeFilterFn = useCallback((fieldId, value2) => {
165
+ setFilters((prev) => removeFilter(prev, fieldId, value2));
166
+ }, []);
167
+ const clearFiltersFn = useCallback(() => {
168
+ setFilters([]);
169
+ }, []);
170
+ const clearFieldFiltersFn = useCallback((fields) => {
171
+ setFilters((prev) => clearFieldFilters(prev, fields));
172
+ }, []);
173
+ const getFieldFilters = useCallback(
174
+ (field) => filters.filter((f) => f.field === field),
175
+ [filters]
176
+ );
177
+ const value = {
178
+ filters,
179
+ addFilter,
180
+ updateFilter,
181
+ removeFilter: removeFilterFn,
182
+ clearFilters: clearFiltersFn,
183
+ clearFieldFilters: clearFieldFiltersFn,
184
+ hasActiveFilters: hasActiveFilters(filters),
185
+ getFieldFilters
186
+ };
187
+ return /* @__PURE__ */ jsx(FilterContext.Provider, { value, children });
188
+ }
189
+ function useFilters() {
190
+ const context = useContext(FilterContext);
191
+ if (!context) {
192
+ throw new Error("useFilters must be used within a FilterProvider");
193
+ }
194
+ return context;
195
+ }
196
+ var PaginationContext = createContext(
197
+ void 0
198
+ );
199
+ function PaginationProvider({
200
+ children,
201
+ defaultPageSize = 10
202
+ }) {
203
+ const [pageIndex, setPageIndex] = useState(1);
204
+ const [pageSize, setPageSizeState] = useState(defaultPageSize);
205
+ const [pageCount, setPageCount] = useState(1);
206
+ const [totalData, setTotalData] = useState(0);
207
+ const [paginationMode, setPaginationModeState] = useState(
208
+ "traditional"
209
+ );
210
+ const goToPage = useCallback((newPageIndex) => {
211
+ setPageIndex(Math.max(1, newPageIndex));
212
+ }, []);
213
+ const nextPage = useCallback(() => {
214
+ setPageIndex((prev) => Math.min(prev + 1, pageCount));
215
+ }, [pageCount]);
216
+ const previousPage = useCallback(() => {
217
+ setPageIndex((prev) => Math.max(prev - 1, 1));
218
+ }, []);
219
+ const setPageSizeFn = useCallback((size) => {
220
+ setPageSizeState(size);
221
+ setPageIndex(1);
222
+ }, []);
223
+ const updatePaginationMeta = useCallback((meta) => {
224
+ setPageIndex(meta.pageIndex);
225
+ setPageSizeState(meta.pageSize);
226
+ setPageCount(meta.pageCount);
227
+ setTotalData(meta.totalData);
228
+ }, []);
229
+ const updatePaginationCount = useCallback((meta) => {
230
+ setPageIndex(meta.pageIndex);
231
+ setPageCount(meta.pageCount);
232
+ setTotalData(meta.totalData);
233
+ }, []);
234
+ const setPaginationMode = useCallback(
235
+ (mode) => {
236
+ setPaginationModeState(mode);
237
+ },
238
+ []
239
+ );
240
+ const canGoNext = pageIndex < pageCount;
241
+ const canGoPrevious = pageIndex > 1;
242
+ const value = {
243
+ pageIndex,
244
+ pageSize,
245
+ pageCount,
246
+ totalData,
247
+ paginationMode,
248
+ goToPage,
249
+ nextPage,
250
+ previousPage,
251
+ setPageSize: setPageSizeFn,
252
+ updatePaginationMeta,
253
+ updatePaginationCount,
254
+ setPaginationMode,
255
+ canGoNext,
256
+ canGoPrevious
257
+ };
258
+ return /* @__PURE__ */ jsx(PaginationContext.Provider, { value, children });
259
+ }
260
+ function usePagination() {
261
+ const context = useContext(PaginationContext);
262
+ if (!context) {
263
+ throw new Error(
264
+ "usePagination must be used within a PaginationProvider"
265
+ );
266
+ }
267
+ return context;
268
+ }
269
+ var SortContext = createContext(void 0);
270
+ function SortProvider({ children }) {
271
+ const [sort, setSort] = useState({
272
+ column: null,
273
+ order: null
274
+ });
275
+ const setSortFn = useCallback((column, order = "asc") => {
276
+ setSort({
277
+ column,
278
+ order: column ? order : null
279
+ });
280
+ }, []);
281
+ const toggleSort = useCallback((column) => {
282
+ setSort((prev) => {
283
+ if (prev.column === column) {
284
+ if (prev.order === "asc") {
285
+ return { column, order: "desc" };
286
+ } else if (prev.order === "desc") {
287
+ return { column: null, order: null };
288
+ } else {
289
+ return { column, order: "asc" };
290
+ }
291
+ } else {
292
+ return { column, order: "asc" };
293
+ }
294
+ });
295
+ }, []);
296
+ const clearSort = useCallback(() => {
297
+ setSort({ column: null, order: null });
298
+ }, []);
299
+ const isSorted = useCallback(
300
+ (column) => sort.column === column && sort.order !== null,
301
+ [sort]
302
+ );
303
+ const getSortOrder = useCallback(
304
+ (column) => sort.column === column ? sort.order : null,
305
+ [sort]
306
+ );
307
+ const value = {
308
+ sort,
309
+ setSort: setSortFn,
310
+ toggleSort,
311
+ clearSort,
312
+ isSorted,
313
+ getSortOrder
314
+ };
315
+ return /* @__PURE__ */ jsx(SortContext.Provider, { value, children });
316
+ }
317
+ function useSort() {
318
+ const context = useContext(SortContext);
319
+ if (!context) {
320
+ throw new Error("useSort must be used within a SortProvider");
321
+ }
322
+ return context;
323
+ }
324
+ var ColumnVisibilityContext = createContext(
325
+ void 0
326
+ );
327
+ function isVisibilityEqual(a, b) {
328
+ const aKeys = Object.keys(a);
329
+ const bKeys = Object.keys(b);
330
+ if (aKeys.length !== bKeys.length) return false;
331
+ return aKeys.every((key) => a[key] === b[key]);
332
+ }
333
+ function ColumnVisibilityProvider({
334
+ children,
335
+ defaultVisibility = {}
336
+ }) {
337
+ const [visibility, setVisibilityState] = useState(
338
+ defaultVisibility
339
+ );
340
+ const toggleColumn = useCallback((columnId) => {
341
+ setVisibilityState((prev) => ({
342
+ ...prev,
343
+ [columnId]: !prev[columnId]
344
+ }));
345
+ }, []);
346
+ const showColumn = useCallback((columnId) => {
347
+ setVisibilityState((prev) => ({
348
+ ...prev,
349
+ [columnId]: true
350
+ }));
351
+ }, []);
352
+ const hideColumn = useCallback((columnId) => {
353
+ setVisibilityState((prev) => ({
354
+ ...prev,
355
+ [columnId]: false
356
+ }));
357
+ }, []);
358
+ const isColumnVisible = useCallback(
359
+ (columnId) => visibility[columnId] !== false,
360
+ [visibility]
361
+ );
362
+ const showAllColumns = useCallback(() => {
363
+ setVisibilityState((prev) => {
364
+ const newVisibility = {};
365
+ Object.keys(prev).forEach((key) => {
366
+ newVisibility[key] = true;
367
+ });
368
+ return newVisibility;
369
+ });
370
+ }, []);
371
+ const hideAllColumns = useCallback(() => {
372
+ setVisibilityState((prev) => {
373
+ const newVisibility = {};
374
+ Object.keys(prev).forEach((key) => {
375
+ newVisibility[key] = false;
376
+ });
377
+ return newVisibility;
378
+ });
379
+ }, []);
380
+ const setVisibility = useCallback((newVisibility) => {
381
+ setVisibilityState(
382
+ (prev) => isVisibilityEqual(prev, newVisibility) ? prev : newVisibility
383
+ );
384
+ }, []);
385
+ const value = {
386
+ visibility,
387
+ toggleColumn,
388
+ showColumn,
389
+ hideColumn,
390
+ isColumnVisible,
391
+ showAllColumns,
392
+ hideAllColumns,
393
+ setVisibility
394
+ };
395
+ return /* @__PURE__ */ jsx(ColumnVisibilityContext.Provider, { value, children });
396
+ }
397
+ function useColumnVisibility() {
398
+ const context = useContext(ColumnVisibilityContext);
399
+ if (!context) {
400
+ throw new Error(
401
+ "useColumnVisibility must be used within a ColumnVisibilityProvider"
402
+ );
403
+ }
404
+ return context;
405
+ }
406
+ var ColumnPinningContext = createContext(
407
+ void 0
408
+ );
409
+ function isPinningEqual(a, b) {
410
+ const aKeys = Object.keys(a);
411
+ const bKeys = Object.keys(b);
412
+ if (aKeys.length !== bKeys.length) return false;
413
+ return aKeys.every((key) => a[key] === b[key]);
414
+ }
415
+ function ColumnPinningProvider({
416
+ children,
417
+ defaultPinning = {}
418
+ }) {
419
+ const [pinning, setPinningState] = useState(defaultPinning);
420
+ const pinColumn = useCallback((columnId, side) => {
421
+ setPinningState((prev) => ({
422
+ ...prev,
423
+ [columnId]: side
424
+ }));
425
+ }, []);
426
+ const unpinColumn = useCallback((columnId) => {
427
+ setPinningState((prev) => ({
428
+ ...prev,
429
+ [columnId]: false
430
+ }));
431
+ }, []);
432
+ const getColumnPin = useCallback(
433
+ (columnId) => pinning[columnId],
434
+ [pinning]
435
+ );
436
+ const setPinning = useCallback((newPinning) => {
437
+ setPinningState(
438
+ (prev) => isPinningEqual(prev, newPinning) ? prev : newPinning
439
+ );
440
+ }, []);
441
+ const value = {
442
+ pinning,
443
+ pinColumn,
444
+ unpinColumn,
445
+ getColumnPin,
446
+ setPinning
447
+ };
448
+ return /* @__PURE__ */ jsx(ColumnPinningContext.Provider, { value, children });
449
+ }
450
+ function useColumnPinning() {
451
+ const context = useContext(ColumnPinningContext);
452
+ if (!context) {
453
+ throw new Error(
454
+ "useColumnPinning must be used within a ColumnPinningProvider"
455
+ );
456
+ }
457
+ return context;
458
+ }
459
+ function DataTableProvider({
460
+ children,
461
+ defaultColumnVisibility = {},
462
+ defaultColumnPinning = {},
463
+ selectionMode,
464
+ defaultPageSize
465
+ }) {
466
+ return /* @__PURE__ */ jsx(FilterProvider, { children: /* @__PURE__ */ jsx(PaginationProvider, { defaultPageSize, children: /* @__PURE__ */ jsx(SelectionProvider, { selectionMode, children: /* @__PURE__ */ jsx(SortProvider, { children: /* @__PURE__ */ jsx(ColumnVisibilityProvider, { defaultVisibility: defaultColumnVisibility, children: /* @__PURE__ */ jsx(ColumnPinningProvider, { defaultPinning: defaultColumnPinning, children }) }) }) }) }) });
467
+ }
468
+ function RowCheckbox({ rowId, currentPageRowIds }) {
469
+ const { isRowSelected, toggleRow } = useSelection();
470
+ return /* @__PURE__ */ jsx(
471
+ Checkbox,
472
+ {
473
+ checked: isRowSelected(rowId),
474
+ onCheckedChange: () => toggleRow(rowId, currentPageRowIds)
475
+ }
476
+ );
477
+ }
478
+ function SelectAllCheckbox({ rowIds, totalCount, onSelectAllAttempt }) {
479
+ const { rowSelection, isSelectAll, toggleAll } = useSelection();
480
+ const currentPageSelectedCount = useMemo(
481
+ () => isSelectAll ? rowIds.length : rowIds.filter((id) => rowSelection[id]).length,
482
+ [rowIds, rowSelection, isSelectAll]
483
+ );
484
+ const isAllSelected = isSelectAll || rowIds.length > 0 && currentPageSelectedCount === rowIds.length;
485
+ const isIndeterminate = !isSelectAll && currentPageSelectedCount > 0 && currentPageSelectedCount < rowIds.length;
486
+ const handleChange = () => {
487
+ if (isAllSelected) {
488
+ toggleAll(rowIds, false);
489
+ } else {
490
+ const confirm = () => toggleAll(rowIds, true, totalCount);
491
+ if (onSelectAllAttempt) {
492
+ onSelectAllAttempt(totalCount ?? rowIds.length, confirm);
493
+ } else {
494
+ confirm();
495
+ }
496
+ }
497
+ };
498
+ return /* @__PURE__ */ jsx(
499
+ Checkbox,
500
+ {
501
+ checked: isAllSelected,
502
+ ref: (element) => {
503
+ if (element) {
504
+ element.indeterminate = isIndeterminate;
505
+ }
506
+ },
507
+ onCheckedChange: handleChange
508
+ }
509
+ );
510
+ }
511
+ function ExpandableRow({ rowId }) {
512
+ const { toggleExpanded, isRowExpanded } = useSelection();
513
+ const isExpanded = isRowExpanded(rowId);
514
+ return /* @__PURE__ */ jsxs(
515
+ Button,
516
+ {
517
+ variant: "outline",
518
+ size: "sm",
519
+ onClick: () => toggleExpanded(rowId),
520
+ className: "relative h-5 w-5 overflow-hidden p-0",
521
+ children: [
522
+ /* @__PURE__ */ jsx("span", { className: `absolute inset-0 flex items-center justify-center transition-all duration-200 ease-in-out ${isExpanded ? "scale-50 opacity-0 rotate-90" : "scale-100 opacity-100 rotate-0"}`, children: /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }) }),
523
+ /* @__PURE__ */ jsx("span", { className: `absolute inset-0 flex items-center justify-center transition-all duration-200 ease-in-out ${isExpanded ? "scale-100 opacity-100 rotate-0" : "scale-50 opacity-0 -rotate-90"}`, children: /* @__PURE__ */ jsx(Minus, { className: "h-3 w-3" }) })
524
+ ]
525
+ }
526
+ );
527
+ }
528
+ function TraditionalPagination({
529
+ pageSizeOptions = [10, 25, 50, 100]
530
+ }) {
531
+ const { t } = useTranslation("datatable");
532
+ const {
533
+ pageIndex,
534
+ pageSize,
535
+ pageCount,
536
+ totalData,
537
+ goToPage,
538
+ nextPage,
539
+ previousPage,
540
+ setPageSize,
541
+ canGoNext,
542
+ canGoPrevious
543
+ } = usePagination();
544
+ const [pageInput, setPageInput] = React14.useState(String(pageIndex));
545
+ React14.useEffect(() => {
546
+ setPageInput(String(pageIndex));
547
+ }, [pageIndex]);
548
+ const submitPageInput = () => {
549
+ const parsedPage = Number.parseInt(pageInput, 10);
550
+ if (!Number.isFinite(parsedPage)) {
551
+ setPageInput(String(pageIndex));
552
+ return;
553
+ }
554
+ const targetPage = Math.min(Math.max(parsedPage, 1), Math.max(pageCount, 1));
555
+ setPageInput(String(targetPage));
556
+ if (targetPage !== pageIndex) goToPage(targetPage);
557
+ };
558
+ const navButtonClass = "rounded-none border-0 transition-colors hover:bg-primary hover:text-primary-foreground disabled:hover:bg-background disabled:hover:text-foreground";
559
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
560
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
561
+ /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-primary/10 px-2.5 py-1 text-xs font-semibold text-primary", children: totalData.toLocaleString("id-ID") }),
562
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: t("pagination.rows") })
563
+ ] }),
564
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
565
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
566
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: t("pagination.rowsPerPage") }),
567
+ /* @__PURE__ */ jsxs(Select, { value: String(pageSize), onValueChange: (v) => setPageSize(Number(v)), children: [
568
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-16 font-semibold", size: "sm", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
569
+ /* @__PURE__ */ jsx(SelectContent, { className: "z-130", position: "popper", children: pageSizeOptions.map((size) => /* @__PURE__ */ jsx(SelectItem, { value: String(size), children: size }, size)) })
570
+ ] })
571
+ ] }),
572
+ /* @__PURE__ */ jsxs("div", { className: "inline-flex items-stretch overflow-hidden rounded-md border border-border", children: [
573
+ /* @__PURE__ */ jsx("span", { className: "flex items-center border-r border-border bg-muted px-2.5 text-sm text-muted-foreground", children: t("pagination.page") }),
574
+ /* @__PURE__ */ jsx(
575
+ Input,
576
+ {
577
+ value: pageInput,
578
+ onChange: (event) => setPageInput(event.target.value.replace(/\D/g, "")),
579
+ onFocus: (event) => event.currentTarget.select(),
580
+ onBlur: submitPageInput,
581
+ onKeyDown: (event) => {
582
+ if (event.key === "Enter") {
583
+ event.preventDefault();
584
+ submitPageInput();
585
+ event.currentTarget.blur();
586
+ }
587
+ },
588
+ inputMode: "numeric",
589
+ "aria-label": "Nomor halaman",
590
+ className: "h-8 w-16 rounded-none border-0 text-center font-semibold shadow-none focus-visible:ring-1 focus-visible:ring-inset"
591
+ }
592
+ ),
593
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center border-l border-border bg-muted px-2.5 text-sm text-muted-foreground", children: [
594
+ t("pagination.of"),
595
+ " ",
596
+ pageCount.toLocaleString("id-ID")
597
+ ] })
598
+ ] }),
599
+ /* @__PURE__ */ jsxs("div", { className: "inline-flex items-stretch overflow-hidden rounded-md border border-border", children: [
600
+ /* @__PURE__ */ jsx(
601
+ Button,
602
+ {
603
+ variant: "outline",
604
+ size: "icon-sm",
605
+ onClick: () => goToPage(1),
606
+ disabled: !canGoPrevious,
607
+ title: "Halaman pertama",
608
+ className: cn(navButtonClass, "border-r"),
609
+ children: /* @__PURE__ */ jsx(ChevronsLeft, { className: "h-4 w-4" })
610
+ }
611
+ ),
612
+ /* @__PURE__ */ jsx(
613
+ Button,
614
+ {
615
+ variant: "outline",
616
+ size: "icon-sm",
617
+ onClick: previousPage,
618
+ disabled: !canGoPrevious,
619
+ title: "Halaman sebelumnya",
620
+ className: cn(navButtonClass, "border-r"),
621
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
622
+ }
623
+ ),
624
+ /* @__PURE__ */ jsx(
625
+ Button,
626
+ {
627
+ variant: "outline",
628
+ size: "icon-sm",
629
+ onClick: nextPage,
630
+ disabled: !canGoNext,
631
+ title: "Halaman berikutnya",
632
+ className: cn(navButtonClass, "border-r"),
633
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
634
+ }
635
+ ),
636
+ /* @__PURE__ */ jsx(
637
+ Button,
638
+ {
639
+ variant: "outline",
640
+ size: "icon-sm",
641
+ onClick: () => goToPage(pageCount),
642
+ disabled: !canGoNext,
643
+ title: "Halaman terakhir",
644
+ className: navButtonClass,
645
+ children: /* @__PURE__ */ jsx(ChevronsRight, { className: "h-4 w-4" })
646
+ }
647
+ )
648
+ ] })
649
+ ] })
650
+ ] });
651
+ }
652
+ function InfiniteScrollPagination({
653
+ isLoading = false,
654
+ onLoadMore
655
+ }) {
656
+ const { pageIndex, totalData, pageSize, pageCount, nextPage } = usePagination();
657
+ const currentItemsLoaded = pageIndex * pageSize;
658
+ const hasMore = currentItemsLoaded < totalData;
659
+ const handleLoadMore = () => {
660
+ nextPage();
661
+ onLoadMore?.();
662
+ };
663
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-4 p-4", children: [
664
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground", children: [
665
+ "Loaded ",
666
+ Math.min(currentItemsLoaded, totalData),
667
+ " of ",
668
+ totalData,
669
+ " items"
670
+ ] }),
671
+ hasMore ? /* @__PURE__ */ jsxs(
672
+ Button,
673
+ {
674
+ onClick: handleLoadMore,
675
+ disabled: isLoading,
676
+ className: "gap-2",
677
+ children: [
678
+ isLoading && /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
679
+ isLoading ? "Loading..." : "Load More"
680
+ ]
681
+ }
682
+ ) : /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "All items loaded" })
683
+ ] });
684
+ }
685
+ function parseSearchValue(raw) {
686
+ return raw.split(",").map((item) => item.trim()).filter(Boolean);
687
+ }
688
+ function formatSearchValue(value) {
689
+ if (Array.isArray(value)) return value.join(", ");
690
+ return String(value ?? "");
691
+ }
692
+ function SearchFilter({
693
+ field,
694
+ label,
695
+ placeholder = "Search...",
696
+ minSearchLength = 1,
697
+ autoApply = false,
698
+ allowCommaSeparated = false,
699
+ onClose
700
+ }) {
701
+ const { filters, addFilter, updateFilter, removeFilter: removeFilter2 } = useFilters();
702
+ const localRemovalRef = useRef(false);
703
+ const existingFilter = filters.find(
704
+ (f) => f.field === field && f.type === "search"
705
+ );
706
+ const [value, setValue] = useState(
707
+ existingFilter ? formatSearchValue(existingFilter.value) : ""
708
+ );
709
+ useEffect(() => {
710
+ if (!existingFilter) {
711
+ if (localRemovalRef.current) {
712
+ localRemovalRef.current = false;
713
+ return;
714
+ }
715
+ setValue("");
716
+ } else {
717
+ setValue(formatSearchValue(existingFilter.value));
718
+ }
719
+ }, [existingFilter?.value]);
720
+ const applyFilter = useCallback((raw) => {
721
+ const trimmed = raw.trim();
722
+ if (trimmed.length >= minSearchLength) {
723
+ const values = parseSearchValue(trimmed);
724
+ const nextValue = allowCommaSeparated && values.length > 1 ? values : trimmed;
725
+ const currentValue = formatSearchValue(existingFilter?.value);
726
+ if (existingFilter) {
727
+ if (currentValue !== trimmed) {
728
+ updateFilter(field, { operator: "contains", value: nextValue, type: "search" });
729
+ }
730
+ } else {
731
+ addFilter({
732
+ field,
733
+ operator: "contains",
734
+ value: nextValue,
735
+ type: "search"
736
+ });
737
+ }
738
+ return;
739
+ }
740
+ if (existingFilter) {
741
+ localRemovalRef.current = true;
742
+ removeFilter2(field);
743
+ }
744
+ }, [existingFilter, field, minSearchLength, allowCommaSeparated, addFilter, updateFilter, removeFilter2]);
745
+ useEffect(() => {
746
+ if (!autoApply) return;
747
+ const timer = window.setTimeout(() => {
748
+ applyFilter(value.trim());
749
+ }, 500);
750
+ return () => window.clearTimeout(timer);
751
+ }, [value, autoApply, applyFilter]);
752
+ const handleSubmit = useCallback(() => {
753
+ applyFilter(value.trim());
754
+ onClose?.();
755
+ }, [value, applyFilter, onClose]);
756
+ const handleKeyDown = useCallback(
757
+ (e) => {
758
+ if (e.key === "Enter") {
759
+ e.preventDefault();
760
+ handleSubmit();
761
+ }
762
+ },
763
+ [handleSubmit]
764
+ );
765
+ const handleClear = useCallback(() => {
766
+ setValue("");
767
+ removeFilter2(field);
768
+ onClose?.();
769
+ }, [field, removeFilter2, onClose]);
770
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
771
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
772
+ /* @__PURE__ */ jsx(
773
+ Input,
774
+ {
775
+ placeholder,
776
+ value,
777
+ onChange: (e) => setValue(e.target.value),
778
+ onKeyDown: handleKeyDown,
779
+ className: "pr-8"
780
+ }
781
+ ),
782
+ value && /* @__PURE__ */ jsx(
783
+ Button,
784
+ {
785
+ type: "button",
786
+ variant: "ghost",
787
+ size: "icon-xs",
788
+ onClick: handleClear,
789
+ "aria-label": `Hapus pencarian ${label}`,
790
+ className: "absolute inset-y-0 right-1 my-auto size-7 p-0 transition-colors active:translate-y-0",
791
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
792
+ }
793
+ )
794
+ ] }),
795
+ /* @__PURE__ */ jsx(
796
+ Button,
797
+ {
798
+ type: "button",
799
+ variant: "outline",
800
+ size: "sm",
801
+ onClick: handleSubmit,
802
+ disabled: value.trim().length < minSearchLength,
803
+ className: "h-9 w-9 shrink-0 rounded-md p-0 transition-colors active:translate-y-0",
804
+ children: /* @__PURE__ */ jsx(Search, { className: "h-4 w-4" })
805
+ }
806
+ )
807
+ ] }) });
808
+ }
809
+ function DateFilter({
810
+ field,
811
+ onClose,
812
+ minDate,
813
+ maxDate
814
+ }) {
815
+ const {
816
+ filters,
817
+ addFilter,
818
+ removeFilter: removeFilter2
819
+ } = useFilters();
820
+ const existingFilter = filters.find(
821
+ (item) => item.field === field && item.type === "date"
822
+ );
823
+ const [selectedDate, setSelectedDate] = useState();
824
+ const [tempSelectedDate, setTempSelectedDate] = useState();
825
+ const existingValueKey = existingFilter ? String(existingFilter.value) : null;
826
+ useEffect(() => {
827
+ const value = existingFilter?.value;
828
+ const date = value ? parseDateValue(value) ?? void 0 : void 0;
829
+ setSelectedDate(date);
830
+ setTempSelectedDate(date);
831
+ }, [existingValueKey]);
832
+ const handleApply = () => {
833
+ setSelectedDate(
834
+ tempSelectedDate
835
+ );
836
+ removeFilter2(field);
837
+ if (tempSelectedDate) {
838
+ const normalizedDate = formatDateKey(
839
+ tempSelectedDate
840
+ );
841
+ if (normalizedDate) {
842
+ addFilter({
843
+ field,
844
+ operator: "equals",
845
+ value: normalizedDate,
846
+ type: "date"
847
+ });
848
+ }
849
+ }
850
+ onClose?.();
851
+ };
852
+ const handleReset = () => {
853
+ setSelectedDate(void 0);
854
+ setTempSelectedDate(void 0);
855
+ removeFilter2(field);
856
+ onClose?.();
857
+ };
858
+ return /* @__PURE__ */ jsxs(
859
+ "div",
860
+ {
861
+ className: cn(
862
+ "w-full",
863
+ "space-y-3"
864
+ ),
865
+ children: [
866
+ tempSelectedDate && /* @__PURE__ */ jsx(
867
+ "div",
868
+ {
869
+ className: cn(
870
+ "rounded-md",
871
+ "border",
872
+ "px-3",
873
+ "py-2",
874
+ "text-center",
875
+ "text-xs",
876
+ "font-medium"
877
+ ),
878
+ children: format(
879
+ tempSelectedDate,
880
+ "dd MMM yyyy"
881
+ )
882
+ }
883
+ ),
884
+ /* @__PURE__ */ jsx(
885
+ Calendar,
886
+ {
887
+ mode: "single",
888
+ defaultMonth: parseDateValue(minDate) ?? void 0,
889
+ startMonth: parseDateValue(minDate) ?? void 0,
890
+ endMonth: parseDateValue(maxDate) ?? void 0,
891
+ disabled: [
892
+ ...parseDateValue(minDate) ? [{ before: parseDateValue(minDate) }] : [],
893
+ ...parseDateValue(maxDate) ? [{ after: parseDateValue(maxDate) }] : []
894
+ ],
895
+ selected: tempSelectedDate,
896
+ onSelect: setTempSelectedDate,
897
+ captionLayout: "dropdown"
898
+ }
899
+ ),
900
+ /* @__PURE__ */ jsxs(
901
+ "div",
902
+ {
903
+ className: cn(
904
+ "flex",
905
+ "items-center",
906
+ "justify-end",
907
+ "gap-2",
908
+ "border-t",
909
+ "bg-muted/40",
910
+ "px-2",
911
+ "pt-2"
912
+ ),
913
+ children: [
914
+ /* @__PURE__ */ jsx(
915
+ Button,
916
+ {
917
+ size: "sm",
918
+ onClick: handleReset,
919
+ className: cn(
920
+ "h-7",
921
+ "rounded-md",
922
+ "bg-destructive",
923
+ "px-3",
924
+ "text-xs",
925
+ "text-destructive-foreground",
926
+ "hover:bg-destructive/90"
927
+ ),
928
+ children: "Reset"
929
+ }
930
+ ),
931
+ /* @__PURE__ */ jsx(
932
+ Button,
933
+ {
934
+ size: "sm",
935
+ onClick: handleApply,
936
+ className: cn(
937
+ "h-7",
938
+ "rounded-md",
939
+ "px-3",
940
+ "text-xs"
941
+ ),
942
+ children: "Apply"
943
+ }
944
+ )
945
+ ]
946
+ }
947
+ )
948
+ ]
949
+ }
950
+ );
951
+ }
952
+ function getRangeLabel(range, defaultLabel) {
953
+ if (!range?.from) {
954
+ return defaultLabel;
955
+ }
956
+ if (!range.to) {
957
+ return format(
958
+ range.from,
959
+ "dd MMM yyyy",
960
+ { locale: enUS$1 }
961
+ );
962
+ }
963
+ return `
964
+ ${format(range.from, "dd MMM yyyy", { locale: enUS$1 })}
965
+ -
966
+ ${format(range.to, "dd MMM yyyy", { locale: enUS$1 })}
967
+ `;
968
+ }
969
+ function DateRangeFilter({
970
+ field,
971
+ onClose,
972
+ showReset = true,
973
+ minDate,
974
+ maxDate
975
+ }) {
976
+ const {
977
+ filters,
978
+ addFilter,
979
+ removeFilter: removeFilter2
980
+ } = useFilters();
981
+ const { t } = useTranslation("datatable");
982
+ const existingFilter = filters.find(
983
+ (item) => item.field === field && item.type === "dateRange"
984
+ );
985
+ const [selectedRange, setSelectedRange] = useState();
986
+ const [tempSelectedRange, setTempSelectedRange] = useState();
987
+ const existingValueKey = existingFilter ? JSON.stringify(existingFilter.value) : null;
988
+ useEffect(() => {
989
+ const value = existingFilter?.value;
990
+ if (!value?.[0] || !value?.[1]) {
991
+ setSelectedRange(void 0);
992
+ setTempSelectedRange(void 0);
993
+ return;
994
+ }
995
+ const range = {
996
+ from: parseDateValue(value[0]) ?? void 0,
997
+ to: parseDateValue(value[1]) ?? void 0
998
+ };
999
+ setSelectedRange(range);
1000
+ setTempSelectedRange(range);
1001
+ }, [existingValueKey]);
1002
+ const handleApply = () => {
1003
+ setSelectedRange(
1004
+ tempSelectedRange
1005
+ );
1006
+ removeFilter2(field);
1007
+ if (tempSelectedRange?.from && tempSelectedRange?.to) {
1008
+ const fromValue = formatDateKey(
1009
+ tempSelectedRange.from
1010
+ );
1011
+ const toValue = formatDateKey(
1012
+ tempSelectedRange.to
1013
+ );
1014
+ if (fromValue && toValue) {
1015
+ addFilter({
1016
+ field,
1017
+ operator: "dateRange",
1018
+ value: [
1019
+ fromValue,
1020
+ toValue
1021
+ ],
1022
+ type: "dateRange"
1023
+ });
1024
+ }
1025
+ }
1026
+ onClose?.();
1027
+ };
1028
+ const handleReset = () => {
1029
+ setSelectedRange(void 0);
1030
+ setTempSelectedRange(void 0);
1031
+ removeFilter2(field);
1032
+ onClose?.();
1033
+ };
1034
+ return /* @__PURE__ */ jsxs(
1035
+ "div",
1036
+ {
1037
+ className: cn(
1038
+ "w-full",
1039
+ "space-y-3"
1040
+ ),
1041
+ children: [
1042
+ /* @__PURE__ */ jsx(
1043
+ "div",
1044
+ {
1045
+ className: cn(
1046
+ "rounded-md",
1047
+ "border",
1048
+ "px-3",
1049
+ "py-2",
1050
+ "text-center",
1051
+ "text-xs",
1052
+ "font-medium"
1053
+ ),
1054
+ children: getRangeLabel(
1055
+ tempSelectedRange,
1056
+ t("selectDateRange")
1057
+ )
1058
+ }
1059
+ ),
1060
+ /* @__PURE__ */ jsx(
1061
+ Calendar,
1062
+ {
1063
+ mode: "range",
1064
+ defaultMonth: parseDateValue(minDate) ?? void 0,
1065
+ startMonth: parseDateValue(minDate) ?? void 0,
1066
+ endMonth: parseDateValue(maxDate) ?? void 0,
1067
+ disabled: [
1068
+ ...parseDateValue(minDate) ? [{ before: parseDateValue(minDate) }] : [],
1069
+ ...parseDateValue(maxDate) ? [{ after: parseDateValue(maxDate) }] : []
1070
+ ],
1071
+ selected: tempSelectedRange,
1072
+ onSelect: setTempSelectedRange,
1073
+ numberOfMonths: 2,
1074
+ captionLayout: "dropdown",
1075
+ locale: enUS
1076
+ }
1077
+ ),
1078
+ /* @__PURE__ */ jsxs(
1079
+ "div",
1080
+ {
1081
+ className: cn(
1082
+ "flex",
1083
+ "items-center",
1084
+ "justify-end",
1085
+ "gap-2",
1086
+ "border-t",
1087
+ "bg-muted/40",
1088
+ "px-2",
1089
+ "pt-2"
1090
+ ),
1091
+ children: [
1092
+ showReset && /* @__PURE__ */ jsx(
1093
+ Button,
1094
+ {
1095
+ size: "sm",
1096
+ onClick: handleReset,
1097
+ className: cn(
1098
+ "h-7",
1099
+ "rounded-md",
1100
+ "bg-destructive",
1101
+ "px-3",
1102
+ "text-xs",
1103
+ "text-destructive-foreground",
1104
+ "hover:bg-destructive/90"
1105
+ ),
1106
+ children: "Reset"
1107
+ }
1108
+ ),
1109
+ /* @__PURE__ */ jsx(
1110
+ Button,
1111
+ {
1112
+ size: "sm",
1113
+ onClick: handleApply,
1114
+ disabled: !tempSelectedRange?.from || !tempSelectedRange?.to,
1115
+ className: cn(
1116
+ "h-7",
1117
+ "rounded-md",
1118
+ "px-3",
1119
+ "text-xs"
1120
+ ),
1121
+ children: "Apply"
1122
+ }
1123
+ )
1124
+ ]
1125
+ }
1126
+ )
1127
+ ]
1128
+ }
1129
+ );
1130
+ }
1131
+ var LIMIT_RENDER = 50;
1132
+ function SelectFilter({
1133
+ field,
1134
+ options,
1135
+ placeholder = "Search...",
1136
+ onClose
1137
+ }) {
1138
+ const {
1139
+ addFilter,
1140
+ removeFilter: removeFilter2,
1141
+ getFieldFilters
1142
+ } = useFilters();
1143
+ const { t } = useTranslation("datatable");
1144
+ const [search, setSearch] = useState("");
1145
+ const existingFilter = getFieldFilters(field).find(
1146
+ (item) => item.type === "select"
1147
+ );
1148
+ const [selectedValue, setSelectedValue] = useState("");
1149
+ const [tempSelectedValue, setTempSelectedValue] = useState("");
1150
+ const existingValueKey = existingFilter ? JSON.stringify(existingFilter.value) : null;
1151
+ useEffect(() => {
1152
+ const value = existingFilter ? String(existingFilter.value) : "";
1153
+ setSelectedValue(value);
1154
+ setTempSelectedValue(value);
1155
+ }, [existingValueKey]);
1156
+ const filteredOptions = useMemo(() => {
1157
+ if (!search) {
1158
+ return options;
1159
+ }
1160
+ return options.filter(
1161
+ (item) => item.label.toLowerCase().includes(
1162
+ search.toLowerCase()
1163
+ )
1164
+ );
1165
+ }, [
1166
+ options,
1167
+ search
1168
+ ]);
1169
+ const visibleOptions = useMemo(() => {
1170
+ return filteredOptions.slice(
1171
+ 0,
1172
+ LIMIT_RENDER
1173
+ );
1174
+ }, [filteredOptions]);
1175
+ const handleSelect = (value) => {
1176
+ setTempSelectedValue(value);
1177
+ };
1178
+ const handleApply = () => {
1179
+ setSelectedValue(
1180
+ tempSelectedValue
1181
+ );
1182
+ removeFilter2(field);
1183
+ if (tempSelectedValue) {
1184
+ addFilter({
1185
+ field,
1186
+ operator: "equals",
1187
+ value: tempSelectedValue,
1188
+ type: "select"
1189
+ });
1190
+ }
1191
+ onClose?.();
1192
+ };
1193
+ const handleReset = () => {
1194
+ setSelectedValue("");
1195
+ setTempSelectedValue("");
1196
+ setSearch("");
1197
+ removeFilter2(field);
1198
+ onClose?.();
1199
+ };
1200
+ return /* @__PURE__ */ jsxs(
1201
+ "div",
1202
+ {
1203
+ className: cn(
1204
+ "w-full",
1205
+ "space-y-2"
1206
+ ),
1207
+ children: [
1208
+ /* @__PURE__ */ jsx(
1209
+ Input,
1210
+ {
1211
+ value: search,
1212
+ placeholder,
1213
+ onChange: (event) => setSearch(
1214
+ event.target.value
1215
+ ),
1216
+ className: cn(
1217
+ "h-9",
1218
+ "rounded-lg",
1219
+ "text-xs"
1220
+ )
1221
+ }
1222
+ ),
1223
+ /* @__PURE__ */ jsxs(
1224
+ "div",
1225
+ {
1226
+ className: cn(
1227
+ "flex",
1228
+ "items-center",
1229
+ "justify-between",
1230
+ "px-1",
1231
+ "text-[11px]",
1232
+ "text-muted-foreground"
1233
+ ),
1234
+ children: [
1235
+ /* @__PURE__ */ jsxs("span", { children: [
1236
+ "Showing ",
1237
+ visibleOptions.length,
1238
+ " ",
1239
+ "of",
1240
+ " ",
1241
+ filteredOptions.length,
1242
+ " ",
1243
+ "data"
1244
+ ] }),
1245
+ filteredOptions.length !== options.length && /* @__PURE__ */ jsxs("span", { children: [
1246
+ "Total ",
1247
+ options.length
1248
+ ] })
1249
+ ]
1250
+ }
1251
+ ),
1252
+ /* @__PURE__ */ jsxs(
1253
+ "div",
1254
+ {
1255
+ className: cn(
1256
+ "max-h-45",
1257
+ "overflow-y-auto",
1258
+ "space-y-1",
1259
+ "pr-1"
1260
+ ),
1261
+ children: [
1262
+ visibleOptions.length === 0 && /* @__PURE__ */ jsx(
1263
+ "div",
1264
+ {
1265
+ className: cn(
1266
+ "py-6",
1267
+ "text-center",
1268
+ "text-xs",
1269
+ "text-muted-foreground"
1270
+ ),
1271
+ children: t("filters.select.dataNotFound")
1272
+ }
1273
+ ),
1274
+ visibleOptions.map(
1275
+ (option) => {
1276
+ const value = String(
1277
+ option.value
1278
+ );
1279
+ const active = tempSelectedValue === value;
1280
+ return /* @__PURE__ */ jsxs(
1281
+ "button",
1282
+ {
1283
+ type: "button",
1284
+ onClick: () => handleSelect(
1285
+ value
1286
+ ),
1287
+ className: cn(
1288
+ "flex",
1289
+ "w-full",
1290
+ "items-center",
1291
+ "justify-between",
1292
+ "rounded-md",
1293
+ "px-2",
1294
+ "py-1.5",
1295
+ "text-left",
1296
+ "text-xs",
1297
+ "hover:bg-muted",
1298
+ active && "bg-muted"
1299
+ ),
1300
+ children: [
1301
+ /* @__PURE__ */ jsx(
1302
+ "span",
1303
+ {
1304
+ className: cn(
1305
+ "truncate"
1306
+ ),
1307
+ children: option.label
1308
+ }
1309
+ ),
1310
+ active && /* @__PURE__ */ jsx(
1311
+ Check,
1312
+ {
1313
+ className: cn(
1314
+ "size-4",
1315
+ "text-primary"
1316
+ )
1317
+ }
1318
+ )
1319
+ ]
1320
+ },
1321
+ value
1322
+ );
1323
+ }
1324
+ )
1325
+ ]
1326
+ }
1327
+ ),
1328
+ /* @__PURE__ */ jsxs(
1329
+ "div",
1330
+ {
1331
+ className: cn(
1332
+ "flex",
1333
+ "items-center",
1334
+ "justify-end",
1335
+ "gap-2",
1336
+ "border-t",
1337
+ "bg-muted/40",
1338
+ "px-2",
1339
+ "pt-2"
1340
+ ),
1341
+ children: [
1342
+ /* @__PURE__ */ jsx(
1343
+ Button,
1344
+ {
1345
+ size: "sm",
1346
+ onClick: handleReset,
1347
+ className: cn(
1348
+ "h-7",
1349
+ "rounded-md",
1350
+ "bg-destructive",
1351
+ "px-3",
1352
+ "text-xs",
1353
+ "text-destructive-foreground",
1354
+ "hover:bg-destructive/90"
1355
+ ),
1356
+ children: "Reset"
1357
+ }
1358
+ ),
1359
+ /* @__PURE__ */ jsx(
1360
+ Button,
1361
+ {
1362
+ size: "sm",
1363
+ onClick: handleApply,
1364
+ className: cn(
1365
+ "h-7",
1366
+ "rounded-md",
1367
+ "px-3",
1368
+ "text-xs"
1369
+ ),
1370
+ children: "Apply"
1371
+ }
1372
+ )
1373
+ ]
1374
+ }
1375
+ )
1376
+ ]
1377
+ }
1378
+ );
1379
+ }
1380
+ var LIMIT_RENDER2 = 50;
1381
+ var DEBOUNCE_MS = 500;
1382
+ function MultiSelectFilter({
1383
+ field,
1384
+ options,
1385
+ placeholder = "Search...",
1386
+ searchPlaceholder,
1387
+ minSearchLength = 0,
1388
+ onClose,
1389
+ onSearch,
1390
+ initialSearch = "",
1391
+ onSearchChange
1392
+ }) {
1393
+ const {
1394
+ filters,
1395
+ addFilter,
1396
+ removeFilter: removeFilter2
1397
+ } = useFilters();
1398
+ const { t } = useTranslation("datatable");
1399
+ const [search, setSearch] = useState(initialSearch);
1400
+ const [items, setItems] = useState(options);
1401
+ const [loading, setLoading] = useState(false);
1402
+ const isServer = !!onSearch;
1403
+ const debounceRef = useRef();
1404
+ const searchRequestRef = useRef(0);
1405
+ const existingFilter = filters.find(
1406
+ (item) => item.field === field && item.type === "multiSelect"
1407
+ );
1408
+ const [selectedValues, setSelectedValues] = useState([]);
1409
+ const [tempSelectedValues, setTempSelectedValues] = useState([]);
1410
+ const existingValueKey = existingFilter ? JSON.stringify(existingFilter.value) : null;
1411
+ useEffect(() => {
1412
+ const values = existingFilter ? existingFilter.value : [];
1413
+ setSelectedValues(values);
1414
+ setTempSelectedValues(values);
1415
+ }, [existingValueKey]);
1416
+ const runSearch = useCallback(async (query) => {
1417
+ if (!onSearch) return;
1418
+ const requestId = ++searchRequestRef.current;
1419
+ setLoading(true);
1420
+ try {
1421
+ const result = await onSearch(query);
1422
+ if (requestId === searchRequestRef.current) {
1423
+ setItems(result);
1424
+ }
1425
+ } catch {
1426
+ if (requestId === searchRequestRef.current) {
1427
+ setItems([]);
1428
+ }
1429
+ } finally {
1430
+ if (requestId === searchRequestRef.current) {
1431
+ setLoading(false);
1432
+ }
1433
+ }
1434
+ }, [onSearch]);
1435
+ useEffect(() => {
1436
+ if (!isServer) return;
1437
+ runSearch(initialSearch.trim());
1438
+ }, [isServer, runSearch]);
1439
+ useEffect(() => () => {
1440
+ if (debounceRef.current) clearTimeout(debounceRef.current);
1441
+ searchRequestRef.current += 1;
1442
+ }, []);
1443
+ const handleSearchChange = (value) => {
1444
+ setSearch(value);
1445
+ onSearchChange?.(value);
1446
+ if (!isServer) return;
1447
+ if (debounceRef.current) clearTimeout(debounceRef.current);
1448
+ const trimmedValue = value.trim();
1449
+ if (trimmedValue.length > 0 && trimmedValue.length < minSearchLength) {
1450
+ searchRequestRef.current += 1;
1451
+ setLoading(false);
1452
+ return;
1453
+ }
1454
+ debounceRef.current = setTimeout(() => {
1455
+ runSearch(trimmedValue);
1456
+ }, trimmedValue ? DEBOUNCE_MS : 0);
1457
+ };
1458
+ const filteredOptions = useMemo(() => {
1459
+ if (isServer) return items;
1460
+ if (!search) {
1461
+ return options;
1462
+ }
1463
+ const keyword = search.toLowerCase();
1464
+ return options.filter(
1465
+ (item) => item.label.toLowerCase().includes(keyword)
1466
+ );
1467
+ }, [
1468
+ isServer,
1469
+ items,
1470
+ options,
1471
+ search
1472
+ ]);
1473
+ const visibleOptions = useMemo(() => {
1474
+ return filteredOptions.slice(
1475
+ 0,
1476
+ LIMIT_RENDER2
1477
+ );
1478
+ }, [filteredOptions]);
1479
+ const allBaseValues = useMemo(
1480
+ () => Array.from(new Set((isServer ? items : options).map((item) => String(item.value)))),
1481
+ [isServer, items, options]
1482
+ );
1483
+ const isAllBaseValuesSelected = useMemo(() => {
1484
+ if (allBaseValues.length === 0) return false;
1485
+ if (tempSelectedValues.length !== allBaseValues.length) return false;
1486
+ const selectedSet = new Set(tempSelectedValues);
1487
+ return allBaseValues.every((value) => selectedSet.has(value));
1488
+ }, [allBaseValues, tempSelectedValues]);
1489
+ const toggleOption = (value) => {
1490
+ setTempSelectedValues(
1491
+ (prev) => prev.includes(value) ? prev.filter(
1492
+ (item) => item !== value
1493
+ ) : [
1494
+ ...prev,
1495
+ value
1496
+ ]
1497
+ );
1498
+ };
1499
+ const handleSelectAll = () => {
1500
+ if (isAllBaseValuesSelected) {
1501
+ setTempSelectedValues([]);
1502
+ return;
1503
+ }
1504
+ setTempSelectedValues(allBaseValues);
1505
+ };
1506
+ const handleApply = () => {
1507
+ setSelectedValues(tempSelectedValues);
1508
+ removeFilter2(field);
1509
+ if (tempSelectedValues.length > 0) {
1510
+ addFilter({
1511
+ field,
1512
+ operator: "in",
1513
+ value: tempSelectedValues,
1514
+ type: "multiSelect"
1515
+ });
1516
+ }
1517
+ onClose?.();
1518
+ };
1519
+ const handleReset = () => {
1520
+ setSelectedValues([]);
1521
+ setTempSelectedValues([]);
1522
+ setSearch("");
1523
+ onSearchChange?.("");
1524
+ removeFilter2(field);
1525
+ onClose?.();
1526
+ };
1527
+ useMemo(() => {
1528
+ const map = /* @__PURE__ */ new Map();
1529
+ items.forEach((o) => map.set(String(o.value), o));
1530
+ tempSelectedValues.forEach((v) => {
1531
+ if (!map.has(v)) map.set(v, { value: v, label: v });
1532
+ });
1533
+ return map;
1534
+ }, [items, tempSelectedValues]);
1535
+ isServer ? filteredOptions.length : options.length;
1536
+ return /* @__PURE__ */ jsxs(
1537
+ "div",
1538
+ {
1539
+ className: cn(
1540
+ "w-full",
1541
+ "space-y-2"
1542
+ ),
1543
+ children: [
1544
+ /* @__PURE__ */ jsxs(
1545
+ "div",
1546
+ {
1547
+ className: cn(
1548
+ "flex",
1549
+ "items-center",
1550
+ "justify-between",
1551
+ "border-b",
1552
+ "px-2",
1553
+ "pb-2",
1554
+ "text-xs"
1555
+ ),
1556
+ children: [
1557
+ /* @__PURE__ */ jsxs(
1558
+ "button",
1559
+ {
1560
+ type: "button",
1561
+ onClick: handleSelectAll,
1562
+ className: cn(
1563
+ "flex",
1564
+ "items-center",
1565
+ "gap-2"
1566
+ ),
1567
+ children: [
1568
+ /* @__PURE__ */ jsx(
1569
+ Checkbox,
1570
+ {
1571
+ checked: isAllBaseValuesSelected,
1572
+ className: cn(
1573
+ "size-4"
1574
+ )
1575
+ }
1576
+ ),
1577
+ /* @__PURE__ */ jsx("span", { children: t("filters.multiSelect.selectAll") })
1578
+ ]
1579
+ }
1580
+ ),
1581
+ /* @__PURE__ */ jsxs("span", { children: [
1582
+ tempSelectedValues.length,
1583
+ " ",
1584
+ t("filters.multiSelect.selected")
1585
+ ] })
1586
+ ]
1587
+ }
1588
+ ),
1589
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1590
+ /* @__PURE__ */ jsx(
1591
+ Input,
1592
+ {
1593
+ value: search,
1594
+ placeholder: searchPlaceholder ?? placeholder,
1595
+ onChange: (event) => handleSearchChange(
1596
+ event.target.value
1597
+ ),
1598
+ className: cn(
1599
+ "h-9",
1600
+ "rounded-lg",
1601
+ "text-xs",
1602
+ isServer && "pr-8"
1603
+ )
1604
+ }
1605
+ ),
1606
+ isServer && loading && /* @__PURE__ */ jsx(Loader2, { className: "absolute right-2.5 top-1/2 -translate-y-1/2 size-4 animate-spin text-muted-foreground" })
1607
+ ] }),
1608
+ /* @__PURE__ */ jsxs(
1609
+ "div",
1610
+ {
1611
+ className: cn(
1612
+ "flex",
1613
+ "items-center",
1614
+ "justify-between",
1615
+ "px-1",
1616
+ "text-[11px]",
1617
+ "text-muted-foreground"
1618
+ ),
1619
+ children: [
1620
+ /* @__PURE__ */ jsxs("span", { children: [
1621
+ isServer ? t("filters.multiSelect.result") : t("filters.multiSelect.showing"),
1622
+ " ",
1623
+ (visibleOptions.length || 0).toLocaleString("id-ID"),
1624
+ " ",
1625
+ "from",
1626
+ " ",
1627
+ (filteredOptions.length || 0).toLocaleString("id-ID"),
1628
+ " ",
1629
+ "data"
1630
+ ] }),
1631
+ !isServer && filteredOptions.length !== options.length && /* @__PURE__ */ jsxs("span", { children: [
1632
+ "Total ",
1633
+ options.length
1634
+ ] })
1635
+ ]
1636
+ }
1637
+ ),
1638
+ /* @__PURE__ */ jsxs(
1639
+ "div",
1640
+ {
1641
+ className: cn(
1642
+ "max-h-45",
1643
+ "overflow-y-auto",
1644
+ "space-y-1",
1645
+ "pr-1"
1646
+ ),
1647
+ children: [
1648
+ visibleOptions.length === 0 && !loading && /* @__PURE__ */ jsx(
1649
+ "div",
1650
+ {
1651
+ className: cn(
1652
+ "py-6",
1653
+ "text-center",
1654
+ "text-xs",
1655
+ "text-muted-foreground"
1656
+ ),
1657
+ children: t("filters.multiSelect.dataNotFound")
1658
+ }
1659
+ ),
1660
+ visibleOptions.map(
1661
+ (option) => {
1662
+ const value = String(
1663
+ option.value
1664
+ );
1665
+ const checked = tempSelectedValues.includes(
1666
+ value
1667
+ );
1668
+ return /* @__PURE__ */ jsxs(
1669
+ "button",
1670
+ {
1671
+ type: "button",
1672
+ onClick: () => toggleOption(
1673
+ value
1674
+ ),
1675
+ className: cn(
1676
+ "flex",
1677
+ "w-full",
1678
+ "items-start",
1679
+ "gap-2",
1680
+ "rounded-md",
1681
+ "px-2",
1682
+ "py-1.5",
1683
+ "text-left",
1684
+ "text-xs",
1685
+ "hover:bg-muted"
1686
+ ),
1687
+ children: [
1688
+ /* @__PURE__ */ jsx(
1689
+ Checkbox,
1690
+ {
1691
+ checked,
1692
+ className: cn(
1693
+ "mt-0.5",
1694
+ "size-4",
1695
+ "pointer-events-none"
1696
+ )
1697
+ }
1698
+ ),
1699
+ /* @__PURE__ */ jsx(
1700
+ "span",
1701
+ {
1702
+ className: cn(
1703
+ "leading-snug"
1704
+ ),
1705
+ children: option.label
1706
+ }
1707
+ )
1708
+ ]
1709
+ },
1710
+ value
1711
+ );
1712
+ }
1713
+ )
1714
+ ]
1715
+ }
1716
+ ),
1717
+ /* @__PURE__ */ jsxs(
1718
+ "div",
1719
+ {
1720
+ className: cn(
1721
+ "flex",
1722
+ "items-center",
1723
+ "justify-end",
1724
+ "gap-2",
1725
+ "border-t",
1726
+ "bg-muted/40",
1727
+ "px-2",
1728
+ "pt-2"
1729
+ ),
1730
+ children: [
1731
+ /* @__PURE__ */ jsx(
1732
+ Button,
1733
+ {
1734
+ size: "sm",
1735
+ onClick: handleReset,
1736
+ className: cn(
1737
+ "h-7",
1738
+ "rounded-md",
1739
+ "bg-destructive",
1740
+ "px-3",
1741
+ "text-xs",
1742
+ "text-destructive-foreground",
1743
+ "hover:bg-destructive/90"
1744
+ ),
1745
+ children: "Reset"
1746
+ }
1747
+ ),
1748
+ /* @__PURE__ */ jsx(
1749
+ Button,
1750
+ {
1751
+ size: "sm",
1752
+ onClick: handleApply,
1753
+ className: cn(
1754
+ "h-7",
1755
+ "rounded-md",
1756
+ "px-3",
1757
+ "text-xs"
1758
+ ),
1759
+ children: "Apply"
1760
+ }
1761
+ )
1762
+ ]
1763
+ }
1764
+ )
1765
+ ]
1766
+ }
1767
+ );
1768
+ }
1769
+ function CheckboxFilter({ field, label }) {
1770
+ const { filters, addFilter, removeFilter: removeFilter2 } = useFilters();
1771
+ const existingFilter = filters.find(
1772
+ (f) => f.field === field && f.type === "checkbox"
1773
+ );
1774
+ const isChecked = !!existingFilter;
1775
+ const handleChange = (checked) => {
1776
+ removeFilter2(field);
1777
+ if (checked) {
1778
+ addFilter({
1779
+ field,
1780
+ operator: "equals",
1781
+ value: true,
1782
+ type: "checkbox"
1783
+ });
1784
+ }
1785
+ };
1786
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
1787
+ /* @__PURE__ */ jsx(
1788
+ Checkbox,
1789
+ {
1790
+ id: field,
1791
+ checked: isChecked,
1792
+ onCheckedChange: (checked) => handleChange(checked)
1793
+ }
1794
+ ),
1795
+ /* @__PURE__ */ jsx(
1796
+ "label",
1797
+ {
1798
+ htmlFor: field,
1799
+ className: "text-sm font-medium cursor-pointer",
1800
+ children: label
1801
+ }
1802
+ )
1803
+ ] });
1804
+ }
1805
+ function RangeFilter({
1806
+ field,
1807
+ label,
1808
+ min = 0,
1809
+ max = 100,
1810
+ step = 1
1811
+ }) {
1812
+ const { filters, addFilter, removeFilter: removeFilter2 } = useFilters();
1813
+ const existingFilter = filters.find(
1814
+ (f) => f.field === field && f.type === "range"
1815
+ );
1816
+ const existingRange = existingFilter?.value;
1817
+ const [minValue, setMinValue] = useState(existingRange ? existingRange[0] : min);
1818
+ const [maxValue, setMaxValue] = useState(existingRange ? existingRange[1] : max);
1819
+ const existingValueKey = existingFilter ? JSON.stringify(existingFilter.value) : null;
1820
+ useEffect(() => {
1821
+ if (!existingFilter) {
1822
+ setMinValue(min);
1823
+ setMaxValue(max);
1824
+ } else if (existingFilter.value) {
1825
+ const [eMin, eMax] = existingFilter.value;
1826
+ setMinValue(eMin);
1827
+ setMaxValue(eMax);
1828
+ }
1829
+ }, [existingValueKey, min, max]);
1830
+ const handleApply = () => {
1831
+ removeFilter2(field);
1832
+ if (minValue < maxValue) {
1833
+ addFilter({
1834
+ field,
1835
+ operator: "between",
1836
+ value: [minValue, maxValue],
1837
+ type: "range"
1838
+ });
1839
+ }
1840
+ };
1841
+ const handleClear = () => {
1842
+ setMinValue(min);
1843
+ setMaxValue(max);
1844
+ removeFilter2(field);
1845
+ };
1846
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1847
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: label }),
1848
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1849
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1850
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
1851
+ /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground", children: "Min" }),
1852
+ /* @__PURE__ */ jsx(
1853
+ Input,
1854
+ {
1855
+ type: "number",
1856
+ min,
1857
+ max: maxValue - step,
1858
+ step,
1859
+ value: minValue,
1860
+ onChange: (e) => setMinValue(Number(e.target.value)),
1861
+ className: "h-8"
1862
+ }
1863
+ )
1864
+ ] }),
1865
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
1866
+ /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground", children: "Max" }),
1867
+ /* @__PURE__ */ jsx(
1868
+ Input,
1869
+ {
1870
+ type: "number",
1871
+ min: minValue + step,
1872
+ max,
1873
+ step,
1874
+ value: maxValue,
1875
+ onChange: (e) => setMaxValue(Number(e.target.value)),
1876
+ className: "h-8"
1877
+ }
1878
+ )
1879
+ ] })
1880
+ ] }),
1881
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
1882
+ "Range: ",
1883
+ minValue,
1884
+ " - ",
1885
+ maxValue
1886
+ ] }),
1887
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1888
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "default", onClick: handleApply, children: "Apply" }),
1889
+ existingFilter && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: handleClear, children: "Clear" })
1890
+ ] })
1891
+ ] })
1892
+ ] });
1893
+ }
1894
+ var BULAN = [
1895
+ "Januari",
1896
+ "Februari",
1897
+ "Maret",
1898
+ "April",
1899
+ "Mei",
1900
+ "Juni",
1901
+ "Juli",
1902
+ "Agustus",
1903
+ "September",
1904
+ "Oktober",
1905
+ "November",
1906
+ "Desember"
1907
+ ];
1908
+ function buildTree(options) {
1909
+ const map = /* @__PURE__ */ new Map();
1910
+ for (const opt of options) {
1911
+ const str = String(opt.value);
1912
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(str);
1913
+ if (!match) continue;
1914
+ const year = Number(match[1]);
1915
+ const month = Number(match[2]);
1916
+ const day = Number(match[3]);
1917
+ if (!map.has(year)) map.set(year, /* @__PURE__ */ new Map());
1918
+ const monthMap = map.get(year);
1919
+ if (!monthMap.has(month)) monthMap.set(month, []);
1920
+ monthMap.get(month).push({ date: str, label: String(day) });
1921
+ }
1922
+ const years = [];
1923
+ for (const [year, monthMap] of [...map.entries()].sort((a, b) => a[0] - b[0])) {
1924
+ const months = [];
1925
+ for (const [month, days] of [...monthMap.entries()].sort((a, b) => a[0] - b[0])) {
1926
+ months.push({
1927
+ year,
1928
+ month,
1929
+ label: BULAN[month - 1],
1930
+ days: [...days].sort((a, b) => Number(a.label) - Number(b.label))
1931
+ });
1932
+ }
1933
+ years.push({ year, label: String(year), months });
1934
+ }
1935
+ return years;
1936
+ }
1937
+ function filterTree(tree, q) {
1938
+ const lq = q.toLowerCase();
1939
+ const result = [];
1940
+ for (const year of tree) {
1941
+ if (year.label.includes(lq)) {
1942
+ result.push(year);
1943
+ continue;
1944
+ }
1945
+ const filteredMonths = [];
1946
+ for (const month of year.months) {
1947
+ if (month.label.toLowerCase().includes(lq)) {
1948
+ filteredMonths.push(month);
1949
+ continue;
1950
+ }
1951
+ const filteredDays = month.days.filter(
1952
+ (d) => d.label === lq || d.label.padStart(2, "0") === lq || d.date.includes(lq)
1953
+ );
1954
+ if (filteredDays.length > 0) {
1955
+ filteredMonths.push({ ...month, days: filteredDays });
1956
+ }
1957
+ }
1958
+ if (filteredMonths.length > 0) {
1959
+ result.push({ ...year, months: filteredMonths });
1960
+ }
1961
+ }
1962
+ return result;
1963
+ }
1964
+ function checkedState(selected, dates) {
1965
+ if (dates.length === 0) return false;
1966
+ const count = dates.filter((d) => selected.has(d)).length;
1967
+ if (count === 0) return false;
1968
+ if (count === dates.length) return true;
1969
+ return "indeterminate";
1970
+ }
1971
+ function DateTreeFilter({ field, options, onClose }) {
1972
+ const { filters, addFilter, removeFilter: removeFilter2 } = useFilters();
1973
+ const existingFilter = filters.find((f) => f.field === field && f.type === "dateTree");
1974
+ const existingValueKey = existingFilter ? JSON.stringify(existingFilter.value) : null;
1975
+ const [tempSelected, setTempSelected] = useState(/* @__PURE__ */ new Set());
1976
+ const [expandedYears, setExpandedYears] = useState(/* @__PURE__ */ new Set());
1977
+ const [expandedMonths, setExpandedMonths] = useState(/* @__PURE__ */ new Set());
1978
+ const [search, setSearch] = useState("");
1979
+ useEffect(() => {
1980
+ const values = existingFilter ? existingFilter.value : [];
1981
+ setTempSelected(new Set(values));
1982
+ }, [existingValueKey]);
1983
+ const tree = useMemo(() => buildTree(options), [options]);
1984
+ const allDates = useMemo(
1985
+ () => tree.flatMap((y) => y.months.flatMap((m) => m.days.map((d) => d.date))),
1986
+ [tree]
1987
+ );
1988
+ const isSearching = search.trim().length > 0;
1989
+ const visibleTree = useMemo(
1990
+ () => isSearching ? filterTree(tree, search.trim()) : tree,
1991
+ [tree, search, isSearching]
1992
+ );
1993
+ const selectAllState = checkedState(tempSelected, allDates);
1994
+ const handleSelectAll = () => {
1995
+ setTempSelected(selectAllState === true ? /* @__PURE__ */ new Set() : new Set(allDates));
1996
+ };
1997
+ const toggleYear = (year) => {
1998
+ const dates = year.months.flatMap((m) => m.days.map((d) => d.date));
1999
+ const state = checkedState(tempSelected, dates);
2000
+ setTempSelected((prev) => {
2001
+ const next = new Set(prev);
2002
+ if (state === true) dates.forEach((d) => next.delete(d));
2003
+ else dates.forEach((d) => next.add(d));
2004
+ return next;
2005
+ });
2006
+ };
2007
+ const toggleMonth = (month) => {
2008
+ const dates = month.days.map((d) => d.date);
2009
+ const state = checkedState(tempSelected, dates);
2010
+ setTempSelected((prev) => {
2011
+ const next = new Set(prev);
2012
+ if (state === true) dates.forEach((d) => next.delete(d));
2013
+ else dates.forEach((d) => next.add(d));
2014
+ return next;
2015
+ });
2016
+ };
2017
+ const toggleDay = (date) => {
2018
+ setTempSelected((prev) => {
2019
+ const next = new Set(prev);
2020
+ if (next.has(date)) next.delete(date);
2021
+ else next.add(date);
2022
+ return next;
2023
+ });
2024
+ };
2025
+ const toggleExpandYear = (year) => {
2026
+ setExpandedYears((prev) => {
2027
+ const next = new Set(prev);
2028
+ if (next.has(year)) next.delete(year);
2029
+ else next.add(year);
2030
+ return next;
2031
+ });
2032
+ };
2033
+ const toggleExpandMonth = (key) => {
2034
+ setExpandedMonths((prev) => {
2035
+ const next = new Set(prev);
2036
+ if (next.has(key)) next.delete(key);
2037
+ else next.add(key);
2038
+ return next;
2039
+ });
2040
+ };
2041
+ const handleApply = () => {
2042
+ removeFilter2(field);
2043
+ const selected = [...tempSelected];
2044
+ if (selected.length > 0) {
2045
+ addFilter({ field, operator: "in", value: selected, type: "dateTree" });
2046
+ }
2047
+ onClose?.();
2048
+ };
2049
+ const handleReset = () => {
2050
+ setTempSelected(/* @__PURE__ */ new Set());
2051
+ setSearch("");
2052
+ removeFilter2(field);
2053
+ onClose?.();
2054
+ };
2055
+ return /* @__PURE__ */ jsxs("div", { className: "w-full space-y-2", children: [
2056
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-2 pb-2 text-xs", children: [
2057
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: handleSelectAll, className: "flex items-center gap-2", children: [
2058
+ /* @__PURE__ */ jsx(Checkbox, { checked: selectAllState, className: "size-4" }),
2059
+ /* @__PURE__ */ jsx("span", { children: "Pilih Semua" })
2060
+ ] }),
2061
+ /* @__PURE__ */ jsxs("span", { children: [
2062
+ tempSelected.size,
2063
+ " Dipilih"
2064
+ ] })
2065
+ ] }),
2066
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2067
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground pointer-events-none" }),
2068
+ /* @__PURE__ */ jsx(
2069
+ Input,
2070
+ {
2071
+ value: search,
2072
+ onChange: (e) => setSearch(e.target.value),
2073
+ placeholder: "Cari tahun, bulan, tanggal\u2026",
2074
+ className: "h-8 pl-7 text-xs rounded-md"
2075
+ }
2076
+ )
2077
+ ] }),
2078
+ /* @__PURE__ */ jsxs("div", { className: "h-52 overflow-y-auto space-y-0.5 pr-1", children: [
2079
+ visibleTree.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center text-xs text-muted-foreground", children: isSearching ? `"${search.trim()}" tidak ditemukan` : "Data tidak ditemukan" }),
2080
+ visibleTree.map((year) => {
2081
+ const yearDates = year.months.flatMap((m) => m.days.map((d) => d.date));
2082
+ const yearState = checkedState(tempSelected, yearDates);
2083
+ const yearExpanded = isSearching || expandedYears.has(year.year);
2084
+ return /* @__PURE__ */ jsxs("div", { children: [
2085
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-md px-1 py-1 hover:bg-muted", children: [
2086
+ /* @__PURE__ */ jsx(
2087
+ "button",
2088
+ {
2089
+ type: "button",
2090
+ onClick: () => !isSearching && toggleExpandYear(year.year),
2091
+ className: cn(
2092
+ "flex shrink-0 items-center justify-center text-muted-foreground",
2093
+ isSearching && "opacity-30 cursor-default"
2094
+ ),
2095
+ children: yearExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" })
2096
+ }
2097
+ ),
2098
+ /* @__PURE__ */ jsxs(
2099
+ "button",
2100
+ {
2101
+ type: "button",
2102
+ onClick: () => toggleYear(year),
2103
+ className: "flex flex-1 items-center gap-2 text-xs font-semibold",
2104
+ children: [
2105
+ /* @__PURE__ */ jsx(Checkbox, { checked: yearState, className: "size-4" }),
2106
+ /* @__PURE__ */ jsx("span", { children: year.label })
2107
+ ]
2108
+ }
2109
+ )
2110
+ ] }),
2111
+ yearExpanded && year.months.map((month) => {
2112
+ const monthDates = month.days.map((d) => d.date);
2113
+ const monthState = checkedState(tempSelected, monthDates);
2114
+ const monthKey = `${year.year}-${String(month.month).padStart(2, "0")}`;
2115
+ const monthExpanded = isSearching || expandedMonths.has(monthKey);
2116
+ const selectedInMonth = monthDates.filter((d) => tempSelected.has(d)).length;
2117
+ return /* @__PURE__ */ jsxs("div", { className: "ml-4", children: [
2118
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-md px-1 py-1 hover:bg-muted", children: [
2119
+ /* @__PURE__ */ jsx(
2120
+ "button",
2121
+ {
2122
+ type: "button",
2123
+ onClick: () => !isSearching && toggleExpandMonth(monthKey),
2124
+ className: cn(
2125
+ "flex shrink-0 items-center justify-center text-muted-foreground",
2126
+ isSearching && "opacity-30 cursor-default"
2127
+ ),
2128
+ children: monthExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" })
2129
+ }
2130
+ ),
2131
+ /* @__PURE__ */ jsxs(
2132
+ "button",
2133
+ {
2134
+ type: "button",
2135
+ onClick: () => toggleMonth(month),
2136
+ className: "flex flex-1 items-center gap-2 text-xs font-medium",
2137
+ children: [
2138
+ /* @__PURE__ */ jsx(Checkbox, { checked: monthState, className: "size-4" }),
2139
+ /* @__PURE__ */ jsx("span", { children: month.label }),
2140
+ /* @__PURE__ */ jsxs("span", { className: cn(
2141
+ "ml-auto font-normal tabular-nums",
2142
+ selectedInMonth > 0 ? "text-primary" : "text-muted-foreground"
2143
+ ), children: [
2144
+ selectedInMonth,
2145
+ "/",
2146
+ monthDates.length
2147
+ ] })
2148
+ ]
2149
+ }
2150
+ )
2151
+ ] }),
2152
+ monthExpanded && /* @__PURE__ */ jsx("div", { className: "ml-4 grid grid-cols-7 gap-0.5 py-1", children: month.days.map((day) => /* @__PURE__ */ jsxs(
2153
+ "button",
2154
+ {
2155
+ type: "button",
2156
+ onClick: () => toggleDay(day.date),
2157
+ className: "flex flex-col items-center gap-0.5 rounded py-1 text-xs hover:bg-muted",
2158
+ children: [
2159
+ /* @__PURE__ */ jsx(
2160
+ Checkbox,
2161
+ {
2162
+ checked: tempSelected.has(day.date),
2163
+ className: "size-3.5 pointer-events-none"
2164
+ }
2165
+ ),
2166
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums text-[10px] leading-none", children: day.label })
2167
+ ]
2168
+ },
2169
+ day.date
2170
+ )) })
2171
+ ] }, monthKey);
2172
+ })
2173
+ ] }, year.year);
2174
+ })
2175
+ ] }),
2176
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 border-t bg-muted/40 px-2 pt-2", children: [
2177
+ /* @__PURE__ */ jsx(
2178
+ Button,
2179
+ {
2180
+ size: "sm",
2181
+ onClick: handleReset,
2182
+ className: "h-7 rounded-md bg-destructive px-3 text-xs text-destructive-foreground hover:bg-destructive/90",
2183
+ children: "Reset"
2184
+ }
2185
+ ),
2186
+ /* @__PURE__ */ jsx(
2187
+ Button,
2188
+ {
2189
+ size: "sm",
2190
+ onClick: handleApply,
2191
+ className: "h-7 rounded-md px-3 text-xs",
2192
+ children: "Apply"
2193
+ }
2194
+ )
2195
+ ] })
2196
+ ] });
2197
+ }
2198
+ function FilterPanel({ columns, onClose }) {
2199
+ const { filters, clearFilters, hasActiveFilters: hasActiveFilters2 } = useFilters();
2200
+ const renderFilter = (column) => {
2201
+ switch (column.type) {
2202
+ case "search":
2203
+ return /* @__PURE__ */ jsx(
2204
+ SearchFilter,
2205
+ {
2206
+ field: column.id,
2207
+ label: column.label,
2208
+ placeholder: column.placeholder,
2209
+ minSearchLength: column.minSearchLength,
2210
+ autoApply: column.autoApply,
2211
+ allowCommaSeparated: column.allowCommaSeparated
2212
+ },
2213
+ column.id
2214
+ );
2215
+ case "date":
2216
+ return /* @__PURE__ */ jsx(
2217
+ DateFilter,
2218
+ {
2219
+ field: column.id,
2220
+ label: column.label
2221
+ },
2222
+ column.id
2223
+ );
2224
+ case "dateRange":
2225
+ return /* @__PURE__ */ jsx(
2226
+ DateRangeFilter,
2227
+ {
2228
+ field: column.id,
2229
+ label: column.label
2230
+ },
2231
+ column.id
2232
+ );
2233
+ case "select":
2234
+ return /* @__PURE__ */ jsx(
2235
+ SelectFilter,
2236
+ {
2237
+ field: column.id,
2238
+ label: column.label,
2239
+ options: column.options || [],
2240
+ placeholder: column.placeholder
2241
+ },
2242
+ column.id
2243
+ );
2244
+ case "multiSelect":
2245
+ return /* @__PURE__ */ jsx(
2246
+ MultiSelectFilter,
2247
+ {
2248
+ field: column.id,
2249
+ label: column.label,
2250
+ options: column.options || [],
2251
+ onSearch: column.onSearch,
2252
+ minSearchLength: column.minSearchLength,
2253
+ placeholder: column.placeholder,
2254
+ searchPlaceholder: column.searchPlaceholder
2255
+ },
2256
+ column.id
2257
+ );
2258
+ case "checkbox":
2259
+ return /* @__PURE__ */ jsx(
2260
+ CheckboxFilter,
2261
+ {
2262
+ field: column.id,
2263
+ label: column.label
2264
+ },
2265
+ column.id
2266
+ );
2267
+ case "range":
2268
+ return /* @__PURE__ */ jsx(
2269
+ RangeFilter,
2270
+ {
2271
+ field: column.id,
2272
+ label: column.label,
2273
+ min: column.minValue,
2274
+ max: column.maxValue
2275
+ },
2276
+ column.id
2277
+ );
2278
+ case "dateTree":
2279
+ return /* @__PURE__ */ jsx(
2280
+ DateTreeFilter,
2281
+ {
2282
+ field: column.id,
2283
+ label: column.label,
2284
+ options: column.options || []
2285
+ },
2286
+ column.id
2287
+ );
2288
+ default:
2289
+ return null;
2290
+ }
2291
+ };
2292
+ return /* @__PURE__ */ jsxs(Card, { className: "p-4 space-y-4", children: [
2293
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2294
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold", children: "Filters" }),
2295
+ onClose && /* @__PURE__ */ jsx(
2296
+ Button,
2297
+ {
2298
+ variant: "ghost",
2299
+ size: "sm",
2300
+ onClick: onClose,
2301
+ className: "h-6 w-6 p-0",
2302
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
2303
+ }
2304
+ )
2305
+ ] }),
2306
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4", children: columns.map((column) => renderFilter(column)) }),
2307
+ hasActiveFilters2 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-2 border-t", children: [
2308
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1", children: [
2309
+ filters.slice(0, 3).map((filter, idx) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: filter.field }, idx)),
2310
+ filters.length > 3 && /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "text-xs", children: [
2311
+ "+",
2312
+ filters.length - 3,
2313
+ " more"
2314
+ ] })
2315
+ ] }),
2316
+ /* @__PURE__ */ jsx(
2317
+ Button,
2318
+ {
2319
+ variant: "ghost",
2320
+ size: "sm",
2321
+ onClick: clearFilters,
2322
+ className: "text-xs h-7",
2323
+ children: "Reset All"
2324
+ }
2325
+ )
2326
+ ] })
2327
+ ] });
2328
+ }
2329
+ function getFilterPopoverWidthClass(type) {
2330
+ switch (type) {
2331
+ case "dateTree":
2332
+ return "w-64 shadow-xl";
2333
+ case "checkbox":
2334
+ return "w-56";
2335
+ case "range":
2336
+ return "w-64";
2337
+ // 2-month calendar needs its own intrinsic width — not data-dependent, so it stays predictable.
2338
+ case "dateRange":
2339
+ return "w-auto";
2340
+ default:
2341
+ return "w-72";
2342
+ }
2343
+ }
2344
+ function isFilterActive(config, getFieldFilters) {
2345
+ return getFieldFilters(config.id).length > 0;
2346
+ }
2347
+ function renderSingleFilter(config, onClose, extra) {
2348
+ switch (config.type) {
2349
+ case "search":
2350
+ return /* @__PURE__ */ jsx(
2351
+ SearchFilter,
2352
+ {
2353
+ field: config.id,
2354
+ label: config.label,
2355
+ placeholder: config.placeholder,
2356
+ minSearchLength: config.minSearchLength,
2357
+ autoApply: config.autoApply,
2358
+ allowCommaSeparated: config.allowCommaSeparated,
2359
+ onClose
2360
+ }
2361
+ );
2362
+ case "date":
2363
+ return /* @__PURE__ */ jsx(
2364
+ DateFilter,
2365
+ {
2366
+ field: config.id,
2367
+ label: config.label,
2368
+ minDate: config.minDate,
2369
+ maxDate: config.maxDate,
2370
+ onClose
2371
+ }
2372
+ );
2373
+ case "dateRange":
2374
+ return /* @__PURE__ */ jsx(
2375
+ DateRangeFilter,
2376
+ {
2377
+ field: config.id,
2378
+ label: config.label,
2379
+ minDate: config.minDate,
2380
+ maxDate: config.maxDate,
2381
+ onClose
2382
+ }
2383
+ );
2384
+ case "select":
2385
+ return /* @__PURE__ */ jsx(
2386
+ SelectFilter,
2387
+ {
2388
+ field: config.id,
2389
+ label: config.label,
2390
+ options: config.options ?? [],
2391
+ placeholder: config.placeholder,
2392
+ onClose
2393
+ }
2394
+ );
2395
+ case "multiSelect":
2396
+ return /* @__PURE__ */ jsx(
2397
+ MultiSelectFilter,
2398
+ {
2399
+ field: config.id,
2400
+ label: config.label,
2401
+ options: config.options ?? [],
2402
+ onSearch: config.onSearch,
2403
+ minSearchLength: config.minSearchLength,
2404
+ placeholder: config.placeholder,
2405
+ searchPlaceholder: config.searchPlaceholder,
2406
+ onClose,
2407
+ initialSearch: extra.multiSelectSearch,
2408
+ onSearchChange: extra.onMultiSelectSearchChange
2409
+ }
2410
+ );
2411
+ case "checkbox":
2412
+ return /* @__PURE__ */ jsx(
2413
+ CheckboxFilter,
2414
+ {
2415
+ field: config.id,
2416
+ label: config.label
2417
+ }
2418
+ );
2419
+ case "range":
2420
+ return /* @__PURE__ */ jsx(
2421
+ RangeFilter,
2422
+ {
2423
+ field: config.id,
2424
+ label: config.label,
2425
+ min: config.minValue,
2426
+ max: config.maxValue
2427
+ }
2428
+ );
2429
+ case "dateTree":
2430
+ return /* @__PURE__ */ jsx(
2431
+ DateTreeFilter,
2432
+ {
2433
+ field: config.id,
2434
+ label: config.label,
2435
+ options: config.options ?? [],
2436
+ onClose
2437
+ }
2438
+ );
2439
+ default:
2440
+ return null;
2441
+ }
2442
+ }
2443
+ var FilterTrigger = React14.forwardRef(
2444
+ ({
2445
+ config,
2446
+ anyActive,
2447
+ onClick,
2448
+ ...rest
2449
+ }, ref) => /* @__PURE__ */ jsx(
2450
+ Button,
2451
+ {
2452
+ ref,
2453
+ variant: "ghost",
2454
+ size: "icon",
2455
+ ...rest,
2456
+ onClick: (event) => {
2457
+ event.stopPropagation();
2458
+ onClick?.(event);
2459
+ },
2460
+ className: cn(
2461
+ "flex",
2462
+ "h-5",
2463
+ "w-5",
2464
+ "items-center",
2465
+ "justify-center",
2466
+ "rounded",
2467
+ anyActive ? [
2468
+ "text-destructive"
2469
+ ] : [
2470
+ "text-muted-foreground",
2471
+ "hover:bg-muted"
2472
+ ]
2473
+ ),
2474
+ children: config.type === "search" ? /* @__PURE__ */ jsx(Search, { className: "size-3" }) : config.type === "dateRange" || config.type === "dateTree" ? /* @__PURE__ */ jsx(CalendarSearch, { className: "size-3" }) : /* @__PURE__ */ jsx(Filter, { className: "size-3" })
2475
+ }
2476
+ )
2477
+ );
2478
+ FilterTrigger.displayName = "FilterTrigger";
2479
+ function FilterPopover({
2480
+ config
2481
+ }) {
2482
+ const {
2483
+ getFieldFilters
2484
+ } = useFilters();
2485
+ const [open, setOpen] = React14.useState(false);
2486
+ const [multiSelectSearch, setMultiSelectSearch] = React14.useState("");
2487
+ return /* @__PURE__ */ jsxs(
2488
+ Popover,
2489
+ {
2490
+ open,
2491
+ onOpenChange: setOpen,
2492
+ children: [
2493
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
2494
+ FilterTrigger,
2495
+ {
2496
+ config,
2497
+ anyActive: isFilterActive(
2498
+ config,
2499
+ getFieldFilters
2500
+ )
2501
+ }
2502
+ ) }),
2503
+ /* @__PURE__ */ jsx(
2504
+ PopoverContent,
2505
+ {
2506
+ align: "end",
2507
+ onClick: (e) => e.stopPropagation(),
2508
+ onOpenAutoFocus: (event) => {
2509
+ const container = event.target;
2510
+ const input = container?.querySelector(
2511
+ 'input:not([type="hidden"]):not([disabled])'
2512
+ );
2513
+ if (input) {
2514
+ event.preventDefault();
2515
+ input.focus();
2516
+ }
2517
+ },
2518
+ className: cn(
2519
+ "z-200",
2520
+ "border",
2521
+ "border-border",
2522
+ "p-3",
2523
+ getFilterPopoverWidthClass(config.type)
2524
+ ),
2525
+ children: renderSingleFilter(
2526
+ config,
2527
+ () => setOpen(false),
2528
+ {
2529
+ multiSelectSearch,
2530
+ onMultiSelectSearchChange: setMultiSelectSearch
2531
+ }
2532
+ )
2533
+ }
2534
+ )
2535
+ ]
2536
+ }
2537
+ );
2538
+ }
2539
+ function ColumnHeaderFilter({
2540
+ filterConfig
2541
+ }) {
2542
+ const configs = Array.isArray(filterConfig) ? filterConfig : [filterConfig];
2543
+ return /* @__PURE__ */ jsx(
2544
+ "div",
2545
+ {
2546
+ className: cn(
2547
+ "flex",
2548
+ "items-center",
2549
+ "gap-0.5"
2550
+ ),
2551
+ children: configs.map((config) => /* @__PURE__ */ jsx(
2552
+ FilterPopover,
2553
+ {
2554
+ config
2555
+ },
2556
+ config.id
2557
+ ))
2558
+ }
2559
+ );
2560
+ }
2561
+ function columnLabel(col) {
2562
+ return typeof col.header === "string" ? col.header : col.id;
2563
+ }
2564
+ function ColumnVisibilityPanel({ columns }) {
2565
+ const { t } = useTranslation("datatable");
2566
+ const { isColumnVisible, toggleColumn, showAllColumns, hideAllColumns } = useColumnVisibility();
2567
+ const hiddenCount = columns.filter((col) => !isColumnVisible(col.id)).length;
2568
+ const allVisible = hiddenCount === 0;
2569
+ const allHidden = hiddenCount === columns.length;
2570
+ return /* @__PURE__ */ jsxs(Popover, { children: [
2571
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
2572
+ Button,
2573
+ {
2574
+ variant: hiddenCount > 0 ? "default" : "outline",
2575
+ size: "sm",
2576
+ className: "gap-2",
2577
+ children: [
2578
+ /* @__PURE__ */ jsx(Columns3, { className: "h-4 w-4" }),
2579
+ t("column"),
2580
+ hiddenCount > 0 && /* @__PURE__ */ jsxs("span", { className: "rounded-full bg-white/20 px-1.5 py-0.5 text-[10px] font-semibold leading-none tabular-nums", children: [
2581
+ "-",
2582
+ hiddenCount
2583
+ ] })
2584
+ ]
2585
+ }
2586
+ ) }),
2587
+ /* @__PURE__ */ jsxs(
2588
+ PopoverContent,
2589
+ {
2590
+ align: "end",
2591
+ className: "z-[200] w-52 p-0 shadow-xl border border-border",
2592
+ children: [
2593
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-3 py-2", children: [
2594
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold", children: t("showColumns") }),
2595
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2596
+ /* @__PURE__ */ jsx(
2597
+ "button",
2598
+ {
2599
+ type: "button",
2600
+ onClick: showAllColumns,
2601
+ disabled: allVisible,
2602
+ title: "Tampilkan semua",
2603
+ className: cn(
2604
+ "rounded p-1 transition-colors",
2605
+ allVisible ? "text-muted-foreground/40 cursor-default" : "text-muted-foreground hover:text-foreground hover:bg-muted"
2606
+ ),
2607
+ children: /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" })
2608
+ }
2609
+ ),
2610
+ /* @__PURE__ */ jsx(
2611
+ "button",
2612
+ {
2613
+ type: "button",
2614
+ onClick: hideAllColumns,
2615
+ disabled: allHidden,
2616
+ title: "Sembunyikan semua",
2617
+ className: cn(
2618
+ "rounded p-1 transition-colors",
2619
+ allHidden ? "text-muted-foreground/40 cursor-default" : "text-muted-foreground hover:text-foreground hover:bg-muted"
2620
+ ),
2621
+ children: /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5" })
2622
+ }
2623
+ )
2624
+ ] })
2625
+ ] }),
2626
+ /* @__PURE__ */ jsx("div", { className: "max-h-72 overflow-y-auto py-1", children: columns.map((col) => {
2627
+ const visible = isColumnVisible(col.id);
2628
+ return /* @__PURE__ */ jsxs(
2629
+ "button",
2630
+ {
2631
+ type: "button",
2632
+ onClick: () => toggleColumn(col.id),
2633
+ className: "flex w-full items-center gap-2.5 px-3 py-1.5 text-left text-xs hover:bg-muted transition-colors",
2634
+ children: [
2635
+ /* @__PURE__ */ jsx(
2636
+ Checkbox,
2637
+ {
2638
+ checked: visible,
2639
+ className: "size-4 pointer-events-none"
2640
+ }
2641
+ ),
2642
+ /* @__PURE__ */ jsx("span", { className: cn(!visible && "text-muted-foreground line-through"), children: columnLabel(col) })
2643
+ ]
2644
+ },
2645
+ col.id
2646
+ );
2647
+ }) }),
2648
+ hiddenCount > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t px-3 py-2 text-[11px] text-muted-foreground", children: [
2649
+ columns.length - hiddenCount,
2650
+ " ",
2651
+ t("of"),
2652
+ " ",
2653
+ columns.length,
2654
+ " ",
2655
+ t("column_plural")
2656
+ ] })
2657
+ ]
2658
+ }
2659
+ )
2660
+ ] });
2661
+ }
2662
+ function ColumnPinMenu({ pin, onPin, onUnpin }) {
2663
+ const { t } = useTranslation("datatable");
2664
+ const isPinned = pin === "left" || pin === "right";
2665
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
2666
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
2667
+ Button,
2668
+ {
2669
+ variant: "ghost",
2670
+ size: "icon",
2671
+ onClick: (event) => event.stopPropagation(),
2672
+ title: isPinned ? t("columnPin.pinned") : t("columnPin.pinning"),
2673
+ className: cn(
2674
+ "flex h-5 w-5 items-center justify-center rounded",
2675
+ isPinned ? "text-destructive" : "text-muted-foreground hover:bg-muted"
2676
+ ),
2677
+ children: /* @__PURE__ */ jsx(Pin, { className: "size-3" })
2678
+ }
2679
+ ) }),
2680
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "start", className: "z-200 w-44", onClick: (event) => event.stopPropagation(), children: [
2681
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { disabled: pin === "left", onClick: () => onPin("left"), children: [
2682
+ /* @__PURE__ */ jsx(ArrowLeftToLine, { className: "size-3.5" }),
2683
+ t("columnPin.pinLeft")
2684
+ ] }),
2685
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { disabled: pin === "right", onClick: () => onPin("right"), children: [
2686
+ /* @__PURE__ */ jsx(ArrowRightToLine, { className: "size-3.5" }),
2687
+ t("columnPin.pinRight")
2688
+ ] }),
2689
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { disabled: !isPinned, onClick: onUnpin, children: [
2690
+ /* @__PURE__ */ jsx(PinOff, { className: "size-3.5" }),
2691
+ t("columnPin.unpin")
2692
+ ] })
2693
+ ] })
2694
+ ] });
2695
+ }
2696
+ var SelectionSummary2 = React14.lazy(
2697
+ () => import('./SelectionSummary-N6MMSND2.js').then((module) => ({ default: module.SelectionSummary }))
2698
+ );
2699
+ function CopyCell({ text, children }) {
2700
+ const { t } = useTranslation("datatable");
2701
+ const [copied, setCopied] = React14.useState(false);
2702
+ const handleCopy = (e) => {
2703
+ e.stopPropagation();
2704
+ navigator.clipboard.writeText(text).then(() => {
2705
+ setCopied(true);
2706
+ setTimeout(() => setCopied(false), 1500);
2707
+ });
2708
+ };
2709
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-1.5", children: [
2710
+ /* @__PURE__ */ jsx("span", { className: "truncate flex-1", children }),
2711
+ /* @__PURE__ */ jsx(
2712
+ "button",
2713
+ {
2714
+ type: "button",
2715
+ onClick: handleCopy,
2716
+ title: copied ? t("copied") : t("copyTooltip"),
2717
+ className: "shrink-0 rounded p-0.5 text-muted-foreground/50 transition-colors hover:text-foreground",
2718
+ children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 text-emerald-500" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3" })
2719
+ }
2720
+ )
2721
+ ] });
2722
+ }
2723
+ function ExpandedRowLoader({
2724
+ row,
2725
+ onLoad,
2726
+ renderContent
2727
+ }) {
2728
+ const [state, setState] = React14.useState({
2729
+ data: null,
2730
+ isLoading: true,
2731
+ error: null
2732
+ });
2733
+ React14.useEffect(() => {
2734
+ let cancelled = false;
2735
+ setState({ data: null, isLoading: true, error: null });
2736
+ onLoad(row).then((data) => {
2737
+ if (!cancelled) setState({ data, isLoading: false, error: null });
2738
+ }).catch((err) => {
2739
+ if (!cancelled) setState({ data: null, isLoading: false, error: String(err) });
2740
+ });
2741
+ return () => {
2742
+ cancelled = true;
2743
+ };
2744
+ }, []);
2745
+ return /* @__PURE__ */ jsx(Fragment, { children: renderContent(row, state.data, { isLoading: state.isLoading, error: state.error }) });
2746
+ }
2747
+ var STICKY_SELECTION_KEY = "__datatable_selection__";
2748
+ var STICKY_EXPAND_KEY = "__datatable_expand__";
2749
+ var STICKY_ROW_NUMBER_KEY = "__datatable_rownumber__";
2750
+ var TOOL_COLUMN_WIDTH = 48;
2751
+ var DEFAULT_STICKY_DATA_COLUMN_WIDTH = 160;
2752
+ var MIN_COLUMN_WIDTH = 130;
2753
+ var SKELETON_CELL_WIDTHS = ["90%", "65%", "80%", "50%", "75%"];
2754
+ function areNumberRecordsEqual(a, b) {
2755
+ const aKeys = Object.keys(a);
2756
+ const bKeys = Object.keys(b);
2757
+ if (aKeys.length !== bKeys.length) return false;
2758
+ return aKeys.every((key) => a[key] === b[key]);
2759
+ }
2760
+ function deriveRowSurfaceClasses(rowClassName) {
2761
+ const tokens = (rowClassName ?? "").split(/\s+/).filter(Boolean);
2762
+ const base = tokens.filter((token) => /^(dark:)?bg-/.test(token));
2763
+ const hover = tokens.filter((token) => /(^|:)hover:bg-/.test(token)).map((token) => {
2764
+ if (token.startsWith("dark:hover:")) return token.replace("dark:hover:", "dark:group-hover:");
2765
+ if (token.startsWith("hover:")) return token.replace("hover:", "group-hover:");
2766
+ return token;
2767
+ });
2768
+ return {
2769
+ base,
2770
+ hover,
2771
+ hasCustomSurface: base.length > 0 || hover.length > 0
2772
+ };
2773
+ }
2774
+ function DataTableInner({
2775
+ columns,
2776
+ data,
2777
+ isLoading = false,
2778
+ error = null,
2779
+ rowKey,
2780
+ onRowClick,
2781
+ showSelection = true,
2782
+ showExpand = false,
2783
+ stickySelection = true,
2784
+ stickyExpand = true,
2785
+ showRowNumber = false,
2786
+ stickyRowNumber = true,
2787
+ expandContent,
2788
+ onExpandLoad,
2789
+ expandLoadContent,
2790
+ paginationMode = "traditional",
2791
+ showFilters = true,
2792
+ showSearch = false,
2793
+ placeholder = "Search data...",
2794
+ searchFields,
2795
+ searchPlaceholder,
2796
+ tableHeight,
2797
+ infiniteScrollHeight,
2798
+ pageSizeOptions = [10, 25, 50, 100],
2799
+ manualPagination = false,
2800
+ manualSorting = false,
2801
+ manualFiltering = false,
2802
+ totalCount,
2803
+ onStateChange,
2804
+ emptyStateMessage = "No Data Available",
2805
+ labelSelection = "Action",
2806
+ toolbarLeft,
2807
+ toolbarRight,
2808
+ renderSelectionCell,
2809
+ selectableTotalCount,
2810
+ rowClassName,
2811
+ showColumnVisibility = false,
2812
+ enableColumnResizing = true,
2813
+ enableColumnPinning = true,
2814
+ onSelectAllAttempt
2815
+ }) {
2816
+ const { isColumnVisible, setVisibility } = useColumnVisibility();
2817
+ const { pinning, pinColumn, unpinColumn } = useColumnPinning();
2818
+ const { t } = useTranslation("datatable");
2819
+ const { pageIndex, pageSize: contextPageSize, updatePaginationCount, goToPage } = usePagination();
2820
+ const skeletonRowCount = Math.min(Math.max(contextPageSize, 5), 10);
2821
+ const { sort, toggleSort } = useSort();
2822
+ const { isRowExpanded, clearSelectAll, selectionMode: effectiveSelectionMode } = useSelection();
2823
+ const { filters } = useFilters();
2824
+ const filterableColumns = React14.useMemo(
2825
+ () => columns.filter((col) => col.filterable && col.filterConfig).flatMap((col) => {
2826
+ const cfg = col.filterConfig;
2827
+ return Array.isArray(cfg) ? cfg : [cfg];
2828
+ }),
2829
+ [columns]
2830
+ );
2831
+ const sanitizedFilters = React14.useMemo(() => {
2832
+ return filters.filter((filter) => {
2833
+ if (filter.type !== "multiSelect" || !Array.isArray(filter.value)) return true;
2834
+ const config = filterableColumns.find((item) => item.id === filter.field && item.type === "multiSelect");
2835
+ const optionValues = Array.from(new Set((config?.options ?? []).map((option) => String(option.value))));
2836
+ if (optionValues.length === 0) return true;
2837
+ if (filter.value.length !== optionValues.length) return true;
2838
+ const selectedSet = new Set(filter.value.map(String));
2839
+ return !optionValues.every((value) => selectedSet.has(value));
2840
+ });
2841
+ }, [filters, filterableColumns]);
2842
+ const [showFilterPanel, setShowFilterPanel] = React14.useState(false);
2843
+ const [searchInput, setSearchInput] = React14.useState("");
2844
+ const [searchQuery, setSearchQuery] = React14.useState("");
2845
+ const stickyCellRefs = React14.useRef({});
2846
+ const headerCellRefs = React14.useRef({});
2847
+ const [stickyOffsets, setStickyOffsets] = React14.useState({});
2848
+ const [stickyWidths, setStickyWidths] = React14.useState({});
2849
+ const [columnWidths, setColumnWidths] = React14.useState({});
2850
+ const [resizingColumnId, setResizingColumnId] = React14.useState(null);
2851
+ const resizeStateRef = React14.useRef(null);
2852
+ const tableScrollContainerRef = React14.useRef(null);
2853
+ const rowElementsRef = React14.useRef({});
2854
+ const rowSurfaceColorsRef = React14.useRef({});
2855
+ const [hoveredRowId, setHoveredRowId] = React14.useState(null);
2856
+ const [, bumpRowSurfaceVersion] = React14.useReducer((value) => value + 1, 0);
2857
+ const rowSurface = React14.useMemo(() => deriveRowSurfaceClasses(rowClassName), [rowClassName]);
2858
+ const stickyFallbackSurface = React14.useMemo(
2859
+ () => !rowSurface.hasCustomSurface ? ["bg-card", "group-hover:bg-muted"] : [],
2860
+ [rowSurface.hasCustomSurface]
2861
+ );
2862
+ const getEffectivePin = React14.useCallback(
2863
+ (column) => {
2864
+ const runtimePin = pinning[column.id];
2865
+ if (runtimePin !== void 0) return runtimePin;
2866
+ if (column.sticky === "right") return "right";
2867
+ if (column.sticky) return "left";
2868
+ return false;
2869
+ },
2870
+ [pinning]
2871
+ );
2872
+ const canPinColumn = React14.useCallback(
2873
+ (column) => enableColumnPinning && column.pinnable !== false,
2874
+ [enableColumnPinning]
2875
+ );
2876
+ const isStickyLeftColumn = React14.useCallback(
2877
+ (column) => getEffectivePin(column) === "left",
2878
+ [getEffectivePin]
2879
+ );
2880
+ const isStickyRightColumn = React14.useCallback(
2881
+ (column) => getEffectivePin(column) === "right",
2882
+ [getEffectivePin]
2883
+ );
2884
+ const isAutoHeight = tableHeight === "auto";
2885
+ const resolvedTableHeight = React14.useMemo(() => {
2886
+ if (isAutoHeight) return void 0;
2887
+ const heightValue = tableHeight ?? infiniteScrollHeight;
2888
+ if (heightValue !== void 0 && heightValue !== null) {
2889
+ return typeof heightValue === "number" ? `${heightValue}px` : heightValue;
2890
+ }
2891
+ return paginationMode === "infinite" ? "600px" : void 0;
2892
+ }, [tableHeight, isAutoHeight, infiniteScrollHeight, paginationMode]);
2893
+ const isScrollable = isAutoHeight || Boolean(resolvedTableHeight);
2894
+ const tableBodyStyle = React14.useMemo(
2895
+ () => resolvedTableHeight ? { maxHeight: resolvedTableHeight } : void 0,
2896
+ [resolvedTableHeight]
2897
+ );
2898
+ const searchableColumns = React14.useMemo(() => {
2899
+ const targetFields = searchFields?.length ? searchFields.map(String) : columns.map((column) => column.id);
2900
+ return columns.filter((column) => targetFields.includes(column.id));
2901
+ }, [columns, searchFields]);
2902
+ const resolvedSearchPlaceholder = React14.useMemo(() => {
2903
+ if (searchPlaceholder) return searchPlaceholder;
2904
+ if (!showSearch) return "";
2905
+ if (!searchableColumns.length) return "Search...";
2906
+ const labels = searchableColumns.map(
2907
+ (column) => typeof column.header === "string" ? column.header : column.id
2908
+ );
2909
+ if (searchFields?.length) {
2910
+ if (placeholder) return placeholder;
2911
+ return labels.length ? `Search ${labels.join(", ")}...` : "Search...";
2912
+ }
2913
+ return "Search all columns...";
2914
+ }, [searchPlaceholder, showSearch, searchableColumns, searchFields]);
2915
+ const applySearch = React14.useCallback(
2916
+ (rows, query) => {
2917
+ const normalizedQuery = query.trim().toLowerCase();
2918
+ if (!normalizedQuery) return rows;
2919
+ if (!searchableColumns.length) return rows;
2920
+ return rows.filter(
2921
+ (row) => searchableColumns.some((column) => {
2922
+ let value = null;
2923
+ if (column.accessor) {
2924
+ if (typeof column.accessor === "function") {
2925
+ value = column.accessor(row);
2926
+ } else {
2927
+ value = row[column.accessor];
2928
+ }
2929
+ } else {
2930
+ value = row[column.id];
2931
+ }
2932
+ if (value === null || value === void 0) return false;
2933
+ return String(value).toLowerCase().includes(normalizedQuery);
2934
+ })
2935
+ );
2936
+ },
2937
+ [searchableColumns]
2938
+ );
2939
+ const handleSearchSubmit = () => {
2940
+ const nextQuery = searchInput.trim();
2941
+ setSearchInput(nextQuery);
2942
+ setSearchQuery(nextQuery);
2943
+ goToPage(1);
2944
+ };
2945
+ const handleSearchClear = () => {
2946
+ setSearchInput("");
2947
+ setSearchQuery("");
2948
+ goToPage(1);
2949
+ };
2950
+ const applySort = React14.useCallback(
2951
+ (rows, sortState) => {
2952
+ if (!sortState.column || !sortState.order) return rows;
2953
+ return [...rows].sort((a, b) => {
2954
+ const column = columns.find((c) => c.id === sortState.column);
2955
+ if (!column) return 0;
2956
+ let valA;
2957
+ let valB;
2958
+ if (column.accessor) {
2959
+ if (typeof column.accessor === "function") {
2960
+ valA = column.accessor(a);
2961
+ valB = column.accessor(b);
2962
+ } else {
2963
+ valA = a[column.accessor];
2964
+ valB = b[column.accessor];
2965
+ }
2966
+ }
2967
+ if (valA === valB) return 0;
2968
+ if (valA == null) return 1;
2969
+ if (valB == null) return -1;
2970
+ const multiplier = sortState.order === "desc" ? -1 : 1;
2971
+ if (typeof valA === "string" && typeof valB === "string") {
2972
+ return valA.localeCompare(valB) * multiplier;
2973
+ }
2974
+ return (valA < valB ? -1 : 1) * multiplier;
2975
+ });
2976
+ },
2977
+ [columns]
2978
+ );
2979
+ const processedData = React14.useMemo(() => {
2980
+ let result = data;
2981
+ if (!manualFiltering) {
2982
+ result = applySearch(result, searchQuery);
2983
+ }
2984
+ if (!manualFiltering) {
2985
+ result = applyFilters(result, sanitizedFilters);
2986
+ }
2987
+ if (!manualSorting) {
2988
+ result = applySort(result, sort);
2989
+ }
2990
+ return result;
2991
+ }, [data, sanitizedFilters, sort, searchQuery, applySearch, applySort, manualFiltering, manualSorting]);
2992
+ const paginatedData = React14.useMemo(() => {
2993
+ if (manualPagination) return processedData;
2994
+ if (paginationMode === "infinite") {
2995
+ return processedData.slice(0, pageIndex * contextPageSize);
2996
+ }
2997
+ const start = (pageIndex - 1) * contextPageSize;
2998
+ return processedData.slice(start, start + contextPageSize);
2999
+ }, [processedData, pageIndex, contextPageSize, paginationMode, manualPagination]);
3000
+ const visibleColumns = React14.useMemo(() => {
3001
+ const shown = columns.filter((col) => isColumnVisible(col.id));
3002
+ const left = shown.filter((col) => isStickyLeftColumn(col));
3003
+ const right = shown.filter((col) => isStickyRightColumn(col));
3004
+ const center = shown.filter((col) => !isStickyLeftColumn(col) && !isStickyRightColumn(col));
3005
+ return [...left, ...center, ...right];
3006
+ }, [columns, isColumnVisible, isStickyLeftColumn, isStickyRightColumn]);
3007
+ const [isScrolled, setIsScrolled] = React14.useState(false);
3008
+ const [isScrolledEnd, setIsScrolledEnd] = React14.useState(false);
3009
+ const [containerClientWidth, setContainerClientWidth] = React14.useState(0);
3010
+ React14.useEffect(() => {
3011
+ const outer = tableScrollContainerRef.current;
3012
+ if (!outer) return;
3013
+ const inner = outer.querySelector('[data-slot="table-container"]');
3014
+ const scroller = (isScrollable ? outer : inner) ?? outer;
3015
+ const handleScroll = () => {
3016
+ setIsScrolled(scroller.scrollLeft > 0);
3017
+ setIsScrolledEnd(
3018
+ scroller.scrollLeft + scroller.clientWidth < scroller.scrollWidth - 2
3019
+ );
3020
+ };
3021
+ scroller.addEventListener("scroll", handleScroll, { passive: true });
3022
+ handleScroll();
3023
+ return () => scroller.removeEventListener("scroll", handleScroll);
3024
+ }, [paginatedData, isScrollable]);
3025
+ const rowIds = processedData.map((row) => String(row[rowKey]));
3026
+ const stickyColumns = React14.useMemo(
3027
+ () => visibleColumns.filter((column) => isStickyLeftColumn(column)),
3028
+ [visibleColumns, isStickyLeftColumn]
3029
+ );
3030
+ const stickyRightColumns = React14.useMemo(
3031
+ () => visibleColumns.filter((column) => isStickyRightColumn(column)),
3032
+ [visibleColumns, isStickyRightColumn]
3033
+ );
3034
+ const stickyColumnKeys = React14.useMemo(() => {
3035
+ const keys = [];
3036
+ if (showSelection && stickySelection) keys.push(STICKY_SELECTION_KEY);
3037
+ if (showExpand && stickyExpand) keys.push(STICKY_EXPAND_KEY);
3038
+ if (showRowNumber && stickyRowNumber) keys.push(STICKY_ROW_NUMBER_KEY);
3039
+ stickyColumns.forEach((column) => {
3040
+ keys.push(column.id);
3041
+ });
3042
+ return keys;
3043
+ }, [showSelection, stickySelection, showExpand, stickyExpand, showRowNumber, stickyRowNumber, stickyColumns]);
3044
+ const stickyRightColumnKeys = React14.useMemo(
3045
+ () => [...stickyRightColumns].reverse().map((column) => column.id),
3046
+ [stickyRightColumns]
3047
+ );
3048
+ const lastStickyLeftKey = React14.useMemo(() => {
3049
+ return stickyColumnKeys[stickyColumnKeys.length - 1];
3050
+ }, [stickyColumnKeys]);
3051
+ const lastStickyRightKey = React14.useMemo(() => {
3052
+ return stickyRightColumnKeys[stickyRightColumnKeys.length - 1];
3053
+ }, [stickyRightColumnKeys]);
3054
+ const measureStickyOffsets = React14.useCallback(() => {
3055
+ const nextOffsets = {};
3056
+ const nextWidths = {};
3057
+ let left = 0;
3058
+ stickyColumnKeys.forEach((key) => {
3059
+ const isToolCol = key === STICKY_SELECTION_KEY || key === STICKY_EXPAND_KEY || key === STICKY_ROW_NUMBER_KEY;
3060
+ const measuredWidth = isToolCol ? TOOL_COLUMN_WIDTH : stickyCellRefs.current[key]?.getBoundingClientRect().width ?? 0;
3061
+ nextOffsets[key] = left;
3062
+ nextWidths[key] = measuredWidth;
3063
+ left += measuredWidth;
3064
+ });
3065
+ let right = 0;
3066
+ stickyRightColumnKeys.forEach((key) => {
3067
+ const measuredWidth = stickyCellRefs.current[key]?.getBoundingClientRect().width ?? 0;
3068
+ nextOffsets[key] = right;
3069
+ nextWidths[key] = measuredWidth;
3070
+ right += measuredWidth;
3071
+ });
3072
+ setStickyOffsets((prev) => areNumberRecordsEqual(prev, nextOffsets) ? prev : nextOffsets);
3073
+ setStickyWidths((prev) => areNumberRecordsEqual(prev, nextWidths) ? prev : nextWidths);
3074
+ }, [stickyColumnKeys, stickyRightColumnKeys]);
3075
+ const buildStickySizeStyle = React14.useCallback(
3076
+ (width, fallbackKey) => {
3077
+ const measuredWidth = fallbackKey ? stickyWidths[fallbackKey] : void 0;
3078
+ const resolvedWidth = typeof width === "number" ? `${width}px` : width ?? (measuredWidth !== void 0 && measuredWidth > 0 ? `${measuredWidth}px` : fallbackKey === STICKY_SELECTION_KEY || fallbackKey === STICKY_EXPAND_KEY || fallbackKey === STICKY_ROW_NUMBER_KEY ? `${TOOL_COLUMN_WIDTH}px` : void 0);
3079
+ return resolvedWidth ? {
3080
+ width: resolvedWidth,
3081
+ minWidth: resolvedWidth,
3082
+ maxWidth: resolvedWidth
3083
+ } : void 0;
3084
+ },
3085
+ [stickyWidths]
3086
+ );
3087
+ const readResolvedBackgroundColor = React14.useCallback((element) => {
3088
+ if (!element || typeof window === "undefined") return void 0;
3089
+ const backgroundColor = window.getComputedStyle(element).backgroundColor;
3090
+ if (!backgroundColor || backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)") {
3091
+ return void 0;
3092
+ }
3093
+ return backgroundColor;
3094
+ }, []);
3095
+ const syncRowBaseSurface = React14.useCallback((rowId, element) => {
3096
+ if (!rowSurface.hasCustomSurface || !element) return;
3097
+ const nextBase = readResolvedBackgroundColor(element);
3098
+ const current = rowSurfaceColorsRef.current[rowId];
3099
+ if (current?.base !== nextBase) {
3100
+ rowSurfaceColorsRef.current[rowId] = { ...current, base: nextBase };
3101
+ bumpRowSurfaceVersion();
3102
+ }
3103
+ }, [readResolvedBackgroundColor, rowSurface.hasCustomSurface]);
3104
+ const registerRowElement = React14.useCallback((rowId, element) => {
3105
+ rowElementsRef.current[rowId] = element;
3106
+ syncRowBaseSurface(rowId, element);
3107
+ }, [syncRowBaseSurface]);
3108
+ const cacheHoveredRowSurface = React14.useCallback((rowId) => {
3109
+ if (!rowSurface.hasCustomSurface) return;
3110
+ const element = rowElementsRef.current[rowId];
3111
+ if (!element) return;
3112
+ const nextHover = readResolvedBackgroundColor(element);
3113
+ const current = rowSurfaceColorsRef.current[rowId];
3114
+ if (current?.hover !== nextHover) {
3115
+ rowSurfaceColorsRef.current[rowId] = { ...current, hover: nextHover };
3116
+ bumpRowSurfaceVersion();
3117
+ }
3118
+ }, [readResolvedBackgroundColor, rowSurface.hasCustomSurface]);
3119
+ const getStickySurfaceStyle = React14.useCallback((rowId) => {
3120
+ if (!rowSurface.hasCustomSurface) return void 0;
3121
+ const colors = rowSurfaceColorsRef.current[rowId];
3122
+ const backgroundColor = hoveredRowId === rowId ? colors?.hover ?? colors?.base : colors?.base;
3123
+ return backgroundColor ? { backgroundColor } : void 0;
3124
+ }, [hoveredRowId, rowSurface.hasCustomSurface]);
3125
+ const getColumnStyle = React14.useCallback(
3126
+ (column) => {
3127
+ const resizedWidth = columnWidths[column.id];
3128
+ if (!isStickyLeftColumn(column) && !isStickyRightColumn(column)) {
3129
+ if (resizedWidth !== void 0) {
3130
+ const w = `${resizedWidth}px`;
3131
+ return { width: w, minWidth: w, maxWidth: w };
3132
+ }
3133
+ if (column.width !== void 0) {
3134
+ const w = typeof column.width === "number" ? `${column.width}px` : column.width;
3135
+ return { width: w, minWidth: w, maxWidth: w };
3136
+ }
3137
+ return void 0;
3138
+ }
3139
+ const fallbackWidth = resizedWidth !== void 0 ? `${resizedWidth}px` : typeof column.width === "number" ? `${column.width}px` : column.width ?? (stickyWidths[column.id] != null ? `${stickyWidths[column.id]}px` : `${DEFAULT_STICKY_DATA_COLUMN_WIDTH}px`);
3140
+ return {
3141
+ width: fallbackWidth,
3142
+ minWidth: fallbackWidth,
3143
+ maxWidth: fallbackWidth
3144
+ };
3145
+ },
3146
+ [isStickyLeftColumn, isStickyRightColumn, stickyWidths, columnWidths]
3147
+ );
3148
+ const canResizeColumn = React14.useCallback(
3149
+ (column) => enableColumnResizing && column.resizable !== false,
3150
+ [enableColumnResizing]
3151
+ );
3152
+ const handleResizePointerDown = React14.useCallback(
3153
+ (event, column) => {
3154
+ event.preventDefault();
3155
+ event.stopPropagation();
3156
+ const startWidth = columnWidths[column.id] ?? headerCellRefs.current[column.id]?.getBoundingClientRect().width ?? DEFAULT_STICKY_DATA_COLUMN_WIDTH;
3157
+ resizeStateRef.current = { columnId: column.id, startX: event.clientX, startWidth };
3158
+ setResizingColumnId(column.id);
3159
+ event.currentTarget.setPointerCapture(event.pointerId);
3160
+ },
3161
+ [columnWidths]
3162
+ );
3163
+ const handleResizePointerMove = React14.useCallback((event) => {
3164
+ const state = resizeStateRef.current;
3165
+ if (!state) return;
3166
+ const nextWidth = Math.max(MIN_COLUMN_WIDTH, Math.round(state.startWidth + (event.clientX - state.startX)));
3167
+ setColumnWidths((prev) => prev[state.columnId] === nextWidth ? prev : { ...prev, [state.columnId]: nextWidth });
3168
+ }, []);
3169
+ const handleResizePointerUp = React14.useCallback((event) => {
3170
+ resizeStateRef.current = null;
3171
+ setResizingColumnId(null);
3172
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
3173
+ event.currentTarget.releasePointerCapture(event.pointerId);
3174
+ }
3175
+ }, []);
3176
+ const handleResizeDoubleClick = React14.useCallback((event, column) => {
3177
+ event.stopPropagation();
3178
+ setColumnWidths((prev) => {
3179
+ if (!(column.id in prev)) return prev;
3180
+ const next = { ...prev };
3181
+ delete next[column.id];
3182
+ return next;
3183
+ });
3184
+ }, []);
3185
+ const lastDataLengthRef = React14.useRef(processedData.length);
3186
+ useEffect(() => {
3187
+ if (manualPagination) {
3188
+ if (totalCount !== void 0) {
3189
+ const pageCount = Math.max(1, Math.ceil(totalCount / contextPageSize));
3190
+ const safePageIndex = pageIndex > pageCount ? 1 : pageIndex;
3191
+ updatePaginationCount({
3192
+ pageIndex: safePageIndex,
3193
+ pageCount,
3194
+ totalData: totalCount
3195
+ });
3196
+ }
3197
+ return;
3198
+ }
3199
+ const dataChanged = lastDataLengthRef.current !== processedData.length;
3200
+ lastDataLengthRef.current = processedData.length;
3201
+ updatePaginationCount({
3202
+ pageIndex: dataChanged ? 1 : pageIndex,
3203
+ pageCount: Math.ceil(processedData.length / contextPageSize),
3204
+ totalData: processedData.length
3205
+ });
3206
+ }, [processedData.length, contextPageSize, updatePaginationCount, manualPagination, totalCount, pageIndex]);
3207
+ const filterResetRef = React14.useRef(false);
3208
+ useEffect(() => {
3209
+ if (manualPagination && (manualFiltering || manualSorting)) {
3210
+ filterResetRef.current = true;
3211
+ goToPage(1);
3212
+ }
3213
+ }, [filters, searchQuery, sort, manualPagination, manualFiltering, manualSorting]);
3214
+ useEffect(() => {
3215
+ onStateChange?.({
3216
+ pageIndex: filterResetRef.current ? 1 : pageIndex,
3217
+ pageSize: contextPageSize,
3218
+ sort,
3219
+ filters: sanitizedFilters,
3220
+ search: showSearch ? {
3221
+ query: searchQuery,
3222
+ fields: searchFields?.length ? searchFields.map(String) : columns.map((column) => column.id)
3223
+ } : void 0
3224
+ });
3225
+ filterResetRef.current = false;
3226
+ }, [pageIndex, contextPageSize, sort, sanitizedFilters, searchQuery, showSearch, onStateChange]);
3227
+ useEffect(() => {
3228
+ const initialVisibility = columns.reduce(
3229
+ (acc, col) => ({
3230
+ ...acc,
3231
+ [col.id]: isColumnVisible(col.id)
3232
+ }),
3233
+ {}
3234
+ );
3235
+ setVisibility(initialVisibility);
3236
+ }, [columns, setVisibility]);
3237
+ React14.useLayoutEffect(() => {
3238
+ measureStickyOffsets();
3239
+ setContainerClientWidth(tableScrollContainerRef.current?.clientWidth ?? 0);
3240
+ }, [measureStickyOffsets, paginatedData, visibleColumns, showSelection, showExpand, columnWidths]);
3241
+ useEffect(() => {
3242
+ if (typeof ResizeObserver === "undefined") return;
3243
+ const container = tableScrollContainerRef.current;
3244
+ if (!container) return;
3245
+ const observer = new ResizeObserver(() => {
3246
+ measureStickyOffsets();
3247
+ setContainerClientWidth(container.clientWidth);
3248
+ });
3249
+ observer.observe(container);
3250
+ return () => {
3251
+ observer.disconnect();
3252
+ };
3253
+ }, [measureStickyOffsets]);
3254
+ const getColumnValue = (row, column) => {
3255
+ if (column.cell) {
3256
+ return column.cell(row);
3257
+ }
3258
+ if (column.accessor) {
3259
+ if (typeof column.accessor === "function") {
3260
+ return column.accessor(row);
3261
+ }
3262
+ return row[column.accessor];
3263
+ }
3264
+ return null;
3265
+ };
3266
+ const getCopyText = (row, column) => {
3267
+ if (column.copyValue) return column.copyValue(row);
3268
+ if (column.accessor && typeof column.accessor !== "function") {
3269
+ return String(row[column.accessor] ?? "");
3270
+ }
3271
+ return String(row[column.id] ?? "");
3272
+ };
3273
+ const renderSortIcon = (columnId) => {
3274
+ if (sort.column !== columnId) {
3275
+ return /* @__PURE__ */ jsx(ArrowUpDown, { className: "h-4 w-4 text-muted-foreground" });
3276
+ }
3277
+ return sort.order === "asc" ? /* @__PURE__ */ jsx(ArrowUpNarrowWide, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ArrowDownNarrowWide, { className: "h-4 w-4" });
3278
+ };
3279
+ if (error) {
3280
+ return /* @__PURE__ */ jsxs(
3281
+ "div",
3282
+ {
3283
+ className: "flex h-full min-h-60 w-full flex-1 flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-destructive/30 bg-destructive/5 px-6 py-10 text-center animate-in fade-in zoom-in-95 duration-300 ease-out",
3284
+ style: resolvedTableHeight ? { minHeight: resolvedTableHeight } : void 0,
3285
+ children: [
3286
+ /* @__PURE__ */ jsx("div", { className: "flex size-12 shrink-0 items-center justify-center rounded-full bg-destructive/10", children: /* @__PURE__ */ jsx(AlertCircle, { className: "size-6 text-destructive" }) }),
3287
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
3288
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: t("loadFailed") }),
3289
+ /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm text-muted-foreground", children: error })
3290
+ ] })
3291
+ ]
3292
+ }
3293
+ );
3294
+ }
3295
+ return /* @__PURE__ */ jsxs("div", { className: cn(isAutoHeight ? "flex h-full min-h-0 flex-col gap-4" : "space-y-4"), children: [
3296
+ (showSearch || toolbarLeft || showFilters && filterableColumns.length > 0 || showColumnVisibility || toolbarRight) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:items-center md:justify-between", children: [
3297
+ showSearch ? /* @__PURE__ */ jsxs("div", { className: "w-full md:max-w-md", children: [
3298
+ /* @__PURE__ */ jsx("label", { className: "sr-only", htmlFor: "datatable-search", children: "Search" }),
3299
+ /* @__PURE__ */ jsxs("div", { className: "group relative w-fit", children: [
3300
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground transition-colors group-focus-within:text-foreground" }),
3301
+ /* @__PURE__ */ jsx(
3302
+ Input,
3303
+ {
3304
+ id: "datatable-search",
3305
+ value: searchInput,
3306
+ onChange: (event) => setSearchInput(event.target.value),
3307
+ onKeyDown: (event) => {
3308
+ if (event.key === "Enter") {
3309
+ event.preventDefault();
3310
+ handleSearchSubmit();
3311
+ }
3312
+ },
3313
+ placeholder: resolvedSearchPlaceholder,
3314
+ className: "h-9 w-80 rounded-lg border-border/70 bg-muted/40 pl-9 pr-9 shadow-sm transition-colors focus-visible:bg-background"
3315
+ }
3316
+ ),
3317
+ /* @__PURE__ */ jsx(
3318
+ "button",
3319
+ {
3320
+ type: "button",
3321
+ onMouseDown: (event) => {
3322
+ if (!searchInput) return;
3323
+ event.preventDefault();
3324
+ },
3325
+ onClick: (event) => {
3326
+ if (!searchInput) return;
3327
+ event.preventDefault();
3328
+ event.stopPropagation();
3329
+ handleSearchClear();
3330
+ },
3331
+ className: cn(
3332
+ "absolute right-1.5 top-1/2 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground focus-visible:outline-none",
3333
+ searchInput ? "pointer-events-auto opacity-100 transition-colors hover:bg-muted hover:text-foreground" : "pointer-events-none opacity-0"
3334
+ ),
3335
+ "aria-label": "Clear search",
3336
+ tabIndex: searchInput ? 0 : -1,
3337
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
3338
+ }
3339
+ )
3340
+ ] })
3341
+ ] }) : toolbarLeft && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: toolbarLeft }),
3342
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-1 justify-end", children: [
3343
+ showFilters && filterableColumns.length > 0 && /* @__PURE__ */ jsxs(
3344
+ Button,
3345
+ {
3346
+ variant: showFilterPanel ? "default" : "outline",
3347
+ size: "sm",
3348
+ onClick: () => setShowFilterPanel(!showFilterPanel),
3349
+ className: "gap-2",
3350
+ children: [
3351
+ /* @__PURE__ */ jsx(Filter, { className: "h-4 w-4" }),
3352
+ "Filters"
3353
+ ]
3354
+ }
3355
+ ),
3356
+ showColumnVisibility && /* @__PURE__ */ jsx(ColumnVisibilityPanel, { columns }),
3357
+ toolbarRight
3358
+ ] })
3359
+ ] }),
3360
+ showFilterPanel && filterableColumns.length > 0 && /* @__PURE__ */ jsx(
3361
+ FilterPanel,
3362
+ {
3363
+ columns: filterableColumns,
3364
+ onClose: () => setShowFilterPanel(false)
3365
+ }
3366
+ ),
3367
+ showSelection && /* @__PURE__ */ jsx(React14.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(SelectionSummary2, { actionLabel: labelSelection }) }),
3368
+ /* @__PURE__ */ jsx(
3369
+ "div",
3370
+ {
3371
+ ref: tableScrollContainerRef,
3372
+ className: cn(
3373
+ "relative rounded-xl border border-border bg-card shadow-sm tms-scroll",
3374
+ isAutoHeight ? "min-h-0 flex-1 overflow-auto" : resolvedTableHeight ? "overflow-auto" : "overflow-hidden"
3375
+ ),
3376
+ style: tableBodyStyle,
3377
+ children: /* @__PURE__ */ jsxs(Table, { containerClassName: isScrollable ? "overflow-visible" : void 0, children: [
3378
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
3379
+ showSelection && /* @__PURE__ */ jsx(
3380
+ TableHead,
3381
+ {
3382
+ ref: (node) => {
3383
+ if (stickySelection) {
3384
+ stickyCellRefs.current[STICKY_SELECTION_KEY] = node;
3385
+ }
3386
+ },
3387
+ className: cn(
3388
+ "h-11 text-center bg-muted sticky top-0",
3389
+ stickySelection ? "z-70" : "z-60",
3390
+ stickySelection && isScrolled && STICKY_SELECTION_KEY === lastStickyLeftKey && "shadow-[10px_0_15px_-5px_rgba(0,0,0,0.15),inset_-8px_0_8px_-8px_rgba(0,0,0,0.12)]"
3391
+ ),
3392
+ style: {
3393
+ left: stickySelection ? stickyOffsets[STICKY_SELECTION_KEY] ?? 0 : void 0,
3394
+ ...buildStickySizeStyle(void 0, STICKY_SELECTION_KEY)
3395
+ },
3396
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: effectiveSelectionMode === "radio" ? /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-muted-foreground", children: "Pilih" }) : /* @__PURE__ */ jsx(
3397
+ SelectAllCheckbox,
3398
+ {
3399
+ rowIds,
3400
+ totalCount: selectableTotalCount ?? totalCount,
3401
+ onSelectAllAttempt
3402
+ }
3403
+ ) })
3404
+ }
3405
+ ),
3406
+ showExpand && /* @__PURE__ */ jsx(
3407
+ TableHead,
3408
+ {
3409
+ ref: (node) => {
3410
+ if (stickyExpand) {
3411
+ stickyCellRefs.current[STICKY_EXPAND_KEY] = node;
3412
+ }
3413
+ },
3414
+ className: cn(
3415
+ "h-11 text-center bg-muted sticky top-0",
3416
+ stickyExpand ? "z-70" : "z-60",
3417
+ stickyExpand && isScrolled && STICKY_EXPAND_KEY === lastStickyLeftKey && "shadow-[10px_0_15px_-5px_rgba(0,0,0,0.15),inset_-8px_0_8px_-8px_rgba(0,0,0,0.12)]"
3418
+ ),
3419
+ style: {
3420
+ left: stickyExpand ? stickyOffsets[STICKY_EXPAND_KEY] ?? 0 : void 0,
3421
+ ...buildStickySizeStyle(void 0, STICKY_EXPAND_KEY)
3422
+ },
3423
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", { className: "block h-4 w-4" }) })
3424
+ }
3425
+ ),
3426
+ showRowNumber && /* @__PURE__ */ jsx(
3427
+ TableHead,
3428
+ {
3429
+ ref: (node) => {
3430
+ if (stickyRowNumber) {
3431
+ stickyCellRefs.current[STICKY_ROW_NUMBER_KEY] = node;
3432
+ }
3433
+ },
3434
+ className: cn(
3435
+ "h-11 text-center bg-muted sticky top-0",
3436
+ stickyRowNumber ? "z-70" : "z-60",
3437
+ stickyRowNumber && isScrolled && STICKY_ROW_NUMBER_KEY === lastStickyLeftKey && "shadow-[10px_0_15px_-5px_rgba(0,0,0,0.15),inset_-8px_0_8px_-8px_rgba(0,0,0,0.12)]"
3438
+ ),
3439
+ style: {
3440
+ left: stickyRowNumber ? stickyOffsets[STICKY_ROW_NUMBER_KEY] ?? 0 : void 0,
3441
+ ...buildStickySizeStyle(TOOL_COLUMN_WIDTH, STICKY_ROW_NUMBER_KEY)
3442
+ },
3443
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-xs font-semibold text-muted-foreground", children: "No" })
3444
+ }
3445
+ ),
3446
+ visibleColumns.map((column) => /* @__PURE__ */ jsxs(
3447
+ TableHead,
3448
+ {
3449
+ ref: (node) => {
3450
+ headerCellRefs.current[column.id] = node;
3451
+ if (isStickyLeftColumn(column) || isStickyRightColumn(column)) {
3452
+ stickyCellRefs.current[column.id] = node;
3453
+ }
3454
+ },
3455
+ className: cn(
3456
+ "h-11 relative group/th",
3457
+ isStickyLeftColumn(column) ? cn(
3458
+ "sticky top-0 text-left bg-muted transition-[background-color,box-shadow] duration-200 z-70",
3459
+ isScrolled && column.id === lastStickyLeftKey && "shadow-[8px_0_16px_rgba(0,0,0,0.14),inset_-8px_0_8px_-8px_rgba(0,0,0,0.12)]"
3460
+ ) : isStickyRightColumn(column) ? cn(
3461
+ "sticky top-0 text-left bg-muted transition-[background-color,box-shadow] duration-200 z-70",
3462
+ isScrolledEnd && column.id === lastStickyRightKey && "shadow-[-8px_0_16px_rgba(0,0,0,0.14),inset_8px_0_8px_-8px_rgba(0,0,0,0.12)]"
3463
+ ) : "sticky top-0 z-60 text-left bg-muted",
3464
+ "text-xs font-semibold text-muted-foreground",
3465
+ column.headerClassName
3466
+ ),
3467
+ style: {
3468
+ ...getColumnStyle(column),
3469
+ left: isStickyLeftColumn(column) ? stickyOffsets[column.id] ?? 0 : void 0,
3470
+ right: isStickyRightColumn(column) ? stickyOffsets[column.id] ?? 0 : void 0
3471
+ },
3472
+ children: [
3473
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center gap-1", children: [
3474
+ column.sortable ? /* @__PURE__ */ jsxs(
3475
+ "button",
3476
+ {
3477
+ onClick: () => toggleSort(column.id),
3478
+ className: "flex items-center gap-1.5 hover:text-foreground transition-colors",
3479
+ children: [
3480
+ column.header,
3481
+ renderSortIcon(column.id)
3482
+ ]
3483
+ }
3484
+ ) : /* @__PURE__ */ jsx("span", { children: column.header }),
3485
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
3486
+ column.filterable && column.filterConfig && /* @__PURE__ */ jsx(ColumnHeaderFilter, { filterConfig: column.filterConfig }),
3487
+ canPinColumn(column) && /* @__PURE__ */ jsx(
3488
+ ColumnPinMenu,
3489
+ {
3490
+ pin: getEffectivePin(column),
3491
+ onPin: (side) => pinColumn(column.id, side),
3492
+ onUnpin: () => unpinColumn(column.id)
3493
+ }
3494
+ )
3495
+ ] })
3496
+ ] }),
3497
+ canResizeColumn(column) && /* @__PURE__ */ jsx(
3498
+ "div",
3499
+ {
3500
+ onPointerDown: (event) => handleResizePointerDown(event, column),
3501
+ onPointerMove: handleResizePointerMove,
3502
+ onPointerUp: handleResizePointerUp,
3503
+ onClick: (event) => event.stopPropagation(),
3504
+ onDoubleClick: (event) => handleResizeDoubleClick(event, column),
3505
+ className: "absolute right-0 top-0 z-20 flex h-full w-2 cursor-col-resize touch-none select-none items-center justify-center",
3506
+ children: /* @__PURE__ */ jsx(
3507
+ "div",
3508
+ {
3509
+ className: cn(
3510
+ "h-4/6 w-px bg-border transition-colors group-hover/th:bg-muted-foreground/50",
3511
+ resizingColumnId === column.id && "w-0.5 bg-primary"
3512
+ )
3513
+ }
3514
+ )
3515
+ }
3516
+ )
3517
+ ]
3518
+ },
3519
+ column.id
3520
+ ))
3521
+ ] }) }),
3522
+ /* @__PURE__ */ jsx(TableBody, { children: isLoading ? (
3523
+ // Loading skeletons
3524
+ Array.from({ length: skeletonRowCount }).map((_, idx) => /* @__PURE__ */ jsxs(TableRow, { className: "border-b border-border/60", children: [
3525
+ showSelection && /* @__PURE__ */ jsx(TableCell, { className: "py-3", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded" }) }),
3526
+ showExpand && /* @__PURE__ */ jsx(TableCell, { className: "py-3", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded" }) }),
3527
+ showRowNumber && /* @__PURE__ */ jsx(TableCell, { className: "py-3", children: /* @__PURE__ */ jsx(Skeleton, { className: "mx-auto h-4 w-5 rounded" }) }),
3528
+ visibleColumns.map((column, colIdx) => /* @__PURE__ */ jsx(TableCell, { className: "py-3", children: /* @__PURE__ */ jsx(
3529
+ Skeleton,
3530
+ {
3531
+ className: "h-4 rounded",
3532
+ style: { width: SKELETON_CELL_WIDTHS[(idx + colIdx) % SKELETON_CELL_WIDTHS.length] }
3533
+ }
3534
+ ) }, column.id))
3535
+ ] }, idx))
3536
+ ) : processedData.length === 0 ? (
3537
+ // Empty state
3538
+ /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(
3539
+ TableCell,
3540
+ {
3541
+ colSpan: visibleColumns.length + (showSelection ? 1 : 0) + (showExpand ? 1 : 0) + (showRowNumber ? 1 : 0),
3542
+ className: "p-0",
3543
+ children: /* @__PURE__ */ jsxs(
3544
+ "div",
3545
+ {
3546
+ className: cn(
3547
+ "flex w-full flex-col items-center justify-center px-6 py-10 text-center animate-in fade-in zoom-in-95 duration-300 ease-out",
3548
+ isScrollable ? "min-h-[320px]" : "min-h-[240px]"
3549
+ ),
3550
+ style: resolvedTableHeight ? { minHeight: `calc(${resolvedTableHeight} - 45px)` } : void 0,
3551
+ children: [
3552
+ /* @__PURE__ */ jsx(EmptyStateIllustration, {}),
3553
+ /* @__PURE__ */ jsx("div", { className: "text-center text-muted-foreground", children: emptyStateMessage })
3554
+ ]
3555
+ }
3556
+ )
3557
+ }
3558
+ ) })
3559
+ ) : (
3560
+ // Data rows
3561
+ paginatedData.map((row, index) => {
3562
+ const rowId = String(row[rowKey]);
3563
+ const isExpanded = isRowExpanded(rowId);
3564
+ return /* @__PURE__ */ jsxs(React14.Fragment, { children: [
3565
+ /* @__PURE__ */ jsxs(
3566
+ TableRow,
3567
+ {
3568
+ ref: (node) => registerRowElement(rowId, node),
3569
+ onClick: () => onRowClick?.(row),
3570
+ onMouseEnter: () => {
3571
+ cacheHoveredRowSurface(rowId);
3572
+ setHoveredRowId(rowId);
3573
+ },
3574
+ onMouseLeave: () => {
3575
+ setHoveredRowId((current) => current === rowId ? null : current);
3576
+ },
3577
+ className: cn("group border-b border-border transition-colors duration-150 hover:bg-muted/60", rowClassName, {
3578
+ "cursor-pointer": onRowClick
3579
+ }),
3580
+ children: [
3581
+ showSelection && /* @__PURE__ */ jsx(
3582
+ TableCell,
3583
+ {
3584
+ onClick: (e) => e.stopPropagation(),
3585
+ className: cn(
3586
+ "py-3 text-center transition-[background-color,box-shadow] duration-200",
3587
+ !rowSurface.hasCustomSurface && "group-hover:bg-muted",
3588
+ stickySelection && "sticky z-45",
3589
+ stickySelection && stickyFallbackSurface,
3590
+ stickySelection && isScrolled && STICKY_SELECTION_KEY === lastStickyLeftKey && "shadow-[8px_0_16px_rgba(0,0,0,0.14),inset_-8px_0_8px_-8px_rgba(0,0,0,0.12)]"
3591
+ ),
3592
+ style: {
3593
+ ...getStickySurfaceStyle(rowId),
3594
+ left: stickySelection ? stickyOffsets[STICKY_SELECTION_KEY] ?? 0 : void 0,
3595
+ ...buildStickySizeStyle(void 0, STICKY_SELECTION_KEY)
3596
+ },
3597
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-foreground/90", children: renderSelectionCell ? renderSelectionCell(row, rowId, /* @__PURE__ */ jsx(RowCheckbox, { rowId, currentPageRowIds: rowIds })) : /* @__PURE__ */ jsx(RowCheckbox, { rowId, currentPageRowIds: rowIds }) })
3598
+ }
3599
+ ),
3600
+ showExpand && /* @__PURE__ */ jsx(
3601
+ TableCell,
3602
+ {
3603
+ onClick: (e) => e.stopPropagation(),
3604
+ className: cn(
3605
+ "py-3 text-center transition-[background-color,box-shadow] duration-200",
3606
+ !rowSurface.hasCustomSurface && "group-hover:bg-muted",
3607
+ stickyExpand && "sticky z-45",
3608
+ stickyExpand && stickyFallbackSurface,
3609
+ stickyExpand && isScrolled && STICKY_EXPAND_KEY === lastStickyLeftKey && "shadow-[8px_0_16px_rgba(0,0,0,0.14),inset_-8px_0_8px_-8px_rgba(0,0,0,0.12)]"
3610
+ ),
3611
+ style: {
3612
+ ...getStickySurfaceStyle(rowId),
3613
+ left: stickyExpand ? stickyOffsets[STICKY_EXPAND_KEY] ?? 0 : void 0,
3614
+ ...buildStickySizeStyle(void 0, STICKY_EXPAND_KEY)
3615
+ },
3616
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-foreground/90", children: /* @__PURE__ */ jsx(ExpandableRow, { rowId }) })
3617
+ }
3618
+ ),
3619
+ showRowNumber && /* @__PURE__ */ jsx(
3620
+ TableCell,
3621
+ {
3622
+ className: cn(
3623
+ "py-3 text-center font-medium text-muted-foreground text-xs transition-[background-color,box-shadow] duration-200",
3624
+ !rowSurface.hasCustomSurface && "group-hover:bg-muted",
3625
+ stickyRowNumber && "sticky z-45",
3626
+ stickyRowNumber && stickyFallbackSurface,
3627
+ stickyRowNumber && isScrolled && STICKY_ROW_NUMBER_KEY === lastStickyLeftKey && "shadow-[8px_0_16px_rgba(0,0,0,0.14),inset_-8px_0_8px_-8px_rgba(0,0,0,0.12)]"
3628
+ ),
3629
+ style: {
3630
+ ...getStickySurfaceStyle(rowId),
3631
+ left: stickyRowNumber ? stickyOffsets[STICKY_ROW_NUMBER_KEY] ?? 0 : void 0,
3632
+ ...buildStickySizeStyle(TOOL_COLUMN_WIDTH, STICKY_ROW_NUMBER_KEY)
3633
+ },
3634
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-foreground/90", children: manualPagination && paginationMode === "infinite" ? index + 1 : (pageIndex - 1) * contextPageSize + index + 1 })
3635
+ }
3636
+ ),
3637
+ visibleColumns.map((column) => /* @__PURE__ */ jsx(
3638
+ TableCell,
3639
+ {
3640
+ className: cn(
3641
+ "py-3",
3642
+ !column.copyable && "truncate",
3643
+ isStickyLeftColumn(column) && cn(
3644
+ "sticky z-45 transition-[background-color,box-shadow] duration-200",
3645
+ !rowSurface.hasCustomSurface && stickyFallbackSurface,
3646
+ isScrolled && column.id === lastStickyLeftKey && "shadow-[8px_0_16px_rgba(0,0,0,0.14),inset_-8px_0_8px_-8px_rgba(0,0,0,0.12)]"
3647
+ ),
3648
+ isStickyRightColumn(column) && cn(
3649
+ "sticky z-45 transition-[background-color,box-shadow] duration-200",
3650
+ !rowSurface.hasCustomSurface && stickyFallbackSurface,
3651
+ isScrolledEnd && column.id === lastStickyRightKey && "shadow-[-8px_0_16px_rgba(0,0,0,0.14),inset_8px_0_8px_-8px_rgba(0,0,0,0.12)]"
3652
+ ),
3653
+ "text-foreground/90",
3654
+ typeof column.className === "function" ? column.className(row) : column.className
3655
+ ),
3656
+ style: {
3657
+ ...isStickyLeftColumn(column) || isStickyRightColumn(column) ? getStickySurfaceStyle(rowId) : void 0,
3658
+ ...getColumnStyle(column),
3659
+ left: isStickyLeftColumn(column) ? stickyOffsets[column.id] ?? 0 : void 0,
3660
+ right: isStickyRightColumn(column) ? stickyOffsets[column.id] ?? 0 : void 0
3661
+ },
3662
+ children: column.copyable ? /* @__PURE__ */ jsx(CopyCell, { text: getCopyText(row, column), children: getColumnValue(row, column) }) : getColumnValue(row, column)
3663
+ },
3664
+ column.id
3665
+ ))
3666
+ ]
3667
+ }
3668
+ ),
3669
+ showExpand && isExpanded && (expandContent || onExpandLoad && expandLoadContent) && /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(
3670
+ TableCell,
3671
+ {
3672
+ colSpan: visibleColumns.length + (showSelection ? 1 : 0) + (showExpand ? 1 : 0) + (showRowNumber ? 1 : 0),
3673
+ className: "p-0 border-t border-border/40",
3674
+ children: /* @__PURE__ */ jsx(
3675
+ "div",
3676
+ {
3677
+ style: {
3678
+ position: "sticky",
3679
+ left: 0,
3680
+ width: containerClientWidth > 0 ? containerClientWidth : void 0,
3681
+ boxSizing: "border-box",
3682
+ zIndex: 1,
3683
+ ...getStickySurfaceStyle(rowId)
3684
+ },
3685
+ className: cn(
3686
+ "animate-in fade-in slide-in-from-top-1 duration-200 ease-out",
3687
+ !rowSurface.hasCustomSurface && "bg-card"
3688
+ ),
3689
+ children: /* @__PURE__ */ jsx("div", { className: "p-4", children: onExpandLoad && expandLoadContent ? /* @__PURE__ */ jsx(ExpandedRowLoader, { row, onLoad: onExpandLoad, renderContent: expandLoadContent }) : expandContent?.(row) })
3690
+ }
3691
+ )
3692
+ }
3693
+ ) })
3694
+ ] }, rowId);
3695
+ })
3696
+ ) })
3697
+ ] })
3698
+ }
3699
+ ),
3700
+ paginationMode === "traditional" && /* @__PURE__ */ jsx(TraditionalPagination, { pageSizeOptions }),
3701
+ paginationMode === "infinite" && /* @__PURE__ */ jsx(InfiniteScrollPagination, { isLoading })
3702
+ ] });
3703
+ }
3704
+ function DataTable(props) {
3705
+ const ambientPagination = useContext(PaginationContext);
3706
+ if (ambientPagination) {
3707
+ return /* @__PURE__ */ jsx(DataTableInner, { ...props });
3708
+ }
3709
+ return /* @__PURE__ */ jsx(
3710
+ DataTableProvider,
3711
+ {
3712
+ defaultPageSize: props.pageSize,
3713
+ selectionMode: props.selectionMode,
3714
+ defaultColumnVisibility: props.defaultColumnVisibility,
3715
+ defaultColumnPinning: props.defaultColumnPinning,
3716
+ children: /* @__PURE__ */ jsx(DataTableInner, { ...props })
3717
+ }
3718
+ );
3719
+ }
3720
+
3721
+ // src/data-table/lib/query-builder.ts
3722
+ function buildQueryParams(pageIndex, pageSize, filters, sort, additionalParams, search) {
3723
+ const params = {
3724
+ pageIndex,
3725
+ pageSize
3726
+ };
3727
+ if (sort.column) {
3728
+ params.sortBy = sort.column;
3729
+ params.sortOrder = sort.order || "asc";
3730
+ }
3731
+ filters.forEach((filter) => {
3732
+ const filterKey = `filters[${filter.field}]`;
3733
+ if (filter.type === "dateRange" && Array.isArray(filter.value)) {
3734
+ params[`${filterKey}[from]`] = filter.value[0];
3735
+ params[`${filterKey}[to]`] = filter.value[1];
3736
+ } else if (filter.type === "range" && Array.isArray(filter.value)) {
3737
+ params[`${filterKey}[min]`] = filter.value[0];
3738
+ params[`${filterKey}[max]`] = filter.value[1];
3739
+ } else if (filter.type === "multiSelect" && Array.isArray(filter.value)) {
3740
+ params[`${filterKey}[]`] = filter.value;
3741
+ } else {
3742
+ params[filterKey] = filter.value;
3743
+ }
3744
+ });
3745
+ if (search?.query?.trim()) {
3746
+ params.search = search.query.trim();
3747
+ if (search.fields?.length) {
3748
+ params.searchFields = search.fields;
3749
+ }
3750
+ }
3751
+ if (additionalParams) {
3752
+ Object.assign(params, additionalParams);
3753
+ }
3754
+ return params;
3755
+ }
3756
+ function paramsToURLSearchParams(params) {
3757
+ const searchParams = new URLSearchParams();
3758
+ Object.entries(params).forEach(([key, value]) => {
3759
+ if (value === null || value === void 0) {
3760
+ return;
3761
+ }
3762
+ if (Array.isArray(value)) {
3763
+ value.forEach((item) => {
3764
+ searchParams.append(key, String(item));
3765
+ });
3766
+ } else {
3767
+ searchParams.set(key, String(value));
3768
+ }
3769
+ });
3770
+ return searchParams;
3771
+ }
3772
+ function buildURL(baseURL, params) {
3773
+ const url = new URL(baseURL);
3774
+ const searchParams = paramsToURLSearchParams(params);
3775
+ url.search = searchParams.toString();
3776
+ return url.toString();
3777
+ }
3778
+
3779
+ // src/data-table/i18n/en.json
3780
+ var en_default = {
3781
+ copy: "Copy",
3782
+ copied: "Copied",
3783
+ copyFailed: "Copy failed",
3784
+ copyTooltip: "Copy to clipboard",
3785
+ search: "Search...",
3786
+ loadFailed: "Failed to load data",
3787
+ pagination: {
3788
+ previous: "Previous",
3789
+ next: "Next",
3790
+ page: "Page",
3791
+ of: "of",
3792
+ showing: "Showing",
3793
+ to: "to",
3794
+ from: "from",
3795
+ results: "results",
3796
+ rows: "rows",
3797
+ rowsPerPage: "Rows per page"
3798
+ },
3799
+ filters: {
3800
+ dateTree: {
3801
+ inputPlaceholder: "Search year, month, day...",
3802
+ dataNotFound: "No data found",
3803
+ notFound: "Not found"
3804
+ },
3805
+ multiSelect: {
3806
+ selectAll: "Select all",
3807
+ unselectAll: "Unselect all",
3808
+ searchPlaceholder: "Search...",
3809
+ result: "result",
3810
+ showing: "Showing",
3811
+ dataNotFound: "No data found",
3812
+ selected: "selected"
3813
+ },
3814
+ select: {
3815
+ selectAll: "Select all",
3816
+ unselectAll: "Unselect all",
3817
+ searchPlaceholder: "Search...",
3818
+ dataNotFound: "No data found"
3819
+ }
3820
+ },
3821
+ columnPin: {
3822
+ pinLeft: "Pin left",
3823
+ pinRight: "Pin right",
3824
+ unpin: "Unpin",
3825
+ pinned: "Pinned",
3826
+ pinning: "Pin column",
3827
+ unpinning: "Unpinning"
3828
+ },
3829
+ column: "Column",
3830
+ showColumns: "Show columns",
3831
+ of: "of",
3832
+ column_plural: "columns shown",
3833
+ totalSelected: "Total selected",
3834
+ clearSelections: "Clear selections",
3835
+ selectDateRange: "Select date range"
3836
+ };
3837
+
3838
+ // src/data-table/i18n/index.ts
3839
+ var DATATABLE_NS = "datatable";
3840
+ var datatableEn = en_default;
3841
+
3842
+ export { CheckboxFilter, ColumnHeaderFilter, ColumnPinMenu, ColumnPinningProvider, ColumnVisibilityPanel, ColumnVisibilityProvider, DATATABLE_NS, DataTable, DataTableProvider, DateFilter, DateRangeFilter, DateTreeFilter, ExpandableRow, FilterPanel, FilterProvider, InfiniteScrollPagination, MultiSelectFilter, PaginationProvider, RangeFilter, RowCheckbox, SearchFilter, SelectAllCheckbox, SelectFilter, SortProvider, TraditionalPagination, applyFilters, buildQueryParams, buildURL, clearFieldFilters, datatableEn, formatDateKey, groupFiltersByField, hasActiveFilters, isValidFilterValue, paramsToURLSearchParams, parseDateValue, removeFilter, useColumnPinning, useColumnVisibility, useFilters, usePagination, useSort };
3843
+ //# sourceMappingURL=index.js.map
3844
+ //# sourceMappingURL=index.js.map