@juv/codego-react-ui 3.4.11 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2237,13 +2237,13 @@ function BulletinBoard({
2237
2237
  const categories = React8.useMemo(() => {
2238
2238
  if (categoriesProp) return categoriesProp;
2239
2239
  const set = /* @__PURE__ */ new Set();
2240
- items.forEach((i) => {
2240
+ (items ?? []).forEach((i) => {
2241
2241
  if (i.category) set.add(i.category);
2242
2242
  });
2243
2243
  return Array.from(set);
2244
2244
  }, [items, categoriesProp]);
2245
2245
  const filtered = React8.useMemo(() => {
2246
- let list = items;
2246
+ let list = items ?? [];
2247
2247
  if (search.trim()) {
2248
2248
  const q = search.toLowerCase();
2249
2249
  list = list.filter(
@@ -2252,7 +2252,7 @@ function BulletinBoard({
2252
2252
  }
2253
2253
  if (category) list = list.filter((i) => i.category === category);
2254
2254
  return [...list].sort((a, b) => (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0));
2255
- }, [items, search, category]);
2255
+ }, [items ?? [], search, category]);
2256
2256
  const showToolbar = searchable || filterable && categories.length > 0;
2257
2257
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: cn("flex flex-col gap-4", className), children: [
2258
2258
  showHeader && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
@@ -5906,11 +5906,77 @@ function FileUpload({
5906
5906
  // src/components/ui/repeater.tsx
5907
5907
  var import_lucide_react15 = require("lucide-react");
5908
5908
  var import_jsx_runtime29 = require("react/jsx-runtime");
5909
+ function RepeaterFieldRenderer({
5910
+ field,
5911
+ value,
5912
+ onChange
5913
+ }) {
5914
+ if (field.type === "image") {
5915
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
5916
+ field.label && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-xs font-medium text-muted-foreground", children: field.label }),
5917
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-center gap-3", children: [
5918
+ value && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("img", { src: value, alt: field.key, className: "h-10 w-10 rounded-lg object-cover ring-1 ring-border shrink-0" }),
5919
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
5920
+ Input,
5921
+ {
5922
+ inputMode: "text",
5923
+ value: value ?? "",
5924
+ onChange: (e) => onChange(e.target.value),
5925
+ placeholder: field.placeholder ?? "Image URL"
5926
+ }
5927
+ )
5928
+ ] })
5929
+ ] });
5930
+ }
5931
+ if (field.type === "attachment") {
5932
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
5933
+ field.label && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-xs font-medium text-muted-foreground", children: field.label }),
5934
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-center gap-2", children: [
5935
+ value && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
5936
+ "a",
5937
+ {
5938
+ href: value,
5939
+ target: "_blank",
5940
+ rel: "noopener noreferrer",
5941
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/50 px-2.5 py-1 text-xs font-medium text-foreground hover:bg-muted transition-colors shrink-0",
5942
+ children: [
5943
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.Paperclip, { className: "h-3 w-3" }),
5944
+ String(value).split("/").pop()
5945
+ ]
5946
+ }
5947
+ ),
5948
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
5949
+ Input,
5950
+ {
5951
+ inputMode: "text",
5952
+ value: value ?? "",
5953
+ onChange: (e) => onChange(e.target.value),
5954
+ placeholder: field.placeholder ?? "Attachment URL"
5955
+ }
5956
+ )
5957
+ ] })
5958
+ ] });
5959
+ }
5960
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
5961
+ field.label && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-xs font-medium text-muted-foreground", children: field.label }),
5962
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
5963
+ Input,
5964
+ {
5965
+ inputMode: "text",
5966
+ value: value ?? "",
5967
+ onChange: (e) => onChange(e.target.value),
5968
+ placeholder: field.placeholder ?? field.key
5969
+ }
5970
+ )
5971
+ ] });
5972
+ }
5909
5973
  function Repeater({
5910
5974
  items,
5911
5975
  onAdd,
5912
5976
  onRemove,
5913
5977
  renderItem,
5978
+ fields,
5979
+ onFieldChange,
5914
5980
  addButtonText = "Add Item",
5915
5981
  className
5916
5982
  }) {
@@ -5923,7 +5989,15 @@ function Repeater({
5923
5989
  children: [
5924
5990
  /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "mt-1 cursor-grab text-muted-foreground/30 group-hover:text-muted-foreground/60 transition-colors shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react15.GripVertical, { className: "h-4 w-4" }) }),
5925
5991
  /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary", children: index + 1 }),
5926
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex-1 min-w-0", children: renderItem(item, index) }),
5992
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex-1 min-w-0", children: fields ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "grid gap-3", style: { gridTemplateColumns: fields.length > 1 ? `repeat(${Math.min(fields.length, 3)}, minmax(0, 1fr))` : "1fr" }, children: fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
5993
+ RepeaterFieldRenderer,
5994
+ {
5995
+ field: f,
5996
+ value: item[f.key],
5997
+ onChange: (v) => onFieldChange?.(index, f.key, v)
5998
+ },
5999
+ f.key
6000
+ )) }) : renderItem ? renderItem(item, index) : null }),
5927
6001
  /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
5928
6002
  Button,
5929
6003
  {
@@ -6406,17 +6480,7 @@ var import_react_dom2 = require("react-dom");
6406
6480
  var import_axios3 = __toESM(require("axios"), 1);
6407
6481
  var import_lucide_react17 = require("lucide-react");
6408
6482
  var import_jsx_runtime32 = require("react/jsx-runtime");
6409
- var csrfAxios = import_axios3.default.create();
6410
- csrfAxios.interceptors.request.use((config) => {
6411
- const method = (config.method ?? "").toUpperCase();
6412
- if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
6413
- const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute("content");
6414
- if (!token) throw new Error('[Table] CSRF token not found. Add <meta name="csrf-token" content="..."> to your HTML <head>.');
6415
- config.headers.set("X-CSRF-Token", token);
6416
- }
6417
- return config;
6418
- });
6419
- function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOverrides, debounce = 300, transform, manual = false, refresh: refreshEnabled = false, refreshInterval = 0, hardReload, onSuccess, onError }) {
6483
+ function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOverrides, debounce = 300, transform, manual = false, refresh: refreshEnabled = false, refreshInterval = 0, hardReload, onSuccess, onError, filter: filterFields, sort: sortKeys }) {
6420
6484
  const [data, setData] = React28.useState([]);
6421
6485
  const [columns, setColumns] = React28.useState([]);
6422
6486
  const [currentPage, setCurrentPage] = React28.useState(1);
@@ -6426,6 +6490,15 @@ function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOv
6426
6490
  const [tick, setTick] = React28.useState(0);
6427
6491
  const [searchValue, setSearchValue] = React28.useState("");
6428
6492
  const debounceTimer = React28.useRef(void 0);
6493
+ const [filterValues, setFilterValues] = React28.useState(() => {
6494
+ const init = {};
6495
+ filterFields?.forEach((f) => {
6496
+ init[f.key] = f.type === "checkbox" || f.type === "toggle" ? false : "";
6497
+ });
6498
+ return init;
6499
+ });
6500
+ const [sortKey, setSortKey] = React28.useState("");
6501
+ const [sortDir, setSortDir] = React28.useState("asc");
6429
6502
  React28.useEffect(() => {
6430
6503
  if (hardReload) hardReload.current = () => setTick((t) => t + 1);
6431
6504
  }, [hardReload]);
@@ -6434,13 +6507,32 @@ function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOv
6434
6507
  const id = setInterval(() => setTick((t) => t + 1), refreshInterval);
6435
6508
  return () => clearInterval(id);
6436
6509
  }, [refreshInterval]);
6510
+ const activeFilterParams = React28.useMemo(() => {
6511
+ const out = {};
6512
+ filterFields?.forEach((f) => {
6513
+ const v = filterValues[f.key];
6514
+ if (f.type === "checkbox" || f.type === "toggle") {
6515
+ if (v) out[f.key] = 1;
6516
+ } else if (f.type === "date-range") {
6517
+ if (v?.from) out[`${f.key}_from`] = v.from;
6518
+ if (v?.to) out[`${f.key}_to`] = v.to;
6519
+ } else if (v !== "" && v !== null && v !== void 0) {
6520
+ out[f.key] = v;
6521
+ }
6522
+ });
6523
+ if (sortKey) {
6524
+ out.sort = sortKey;
6525
+ out.direction = sortDir;
6526
+ }
6527
+ return out;
6528
+ }, [filterValues, sortKey, sortDir, filterFields]);
6437
6529
  React28.useEffect(() => {
6438
6530
  if (manual && tick === 0) return;
6439
6531
  let cancelled = false;
6440
6532
  setLoading(true);
6441
6533
  setError(null);
6442
6534
  import_axios3.default.get(url, {
6443
- params: { ...params, page: currentPage, search: searchValue }
6535
+ params: { ...params, ...activeFilterParams, page: currentPage, search: searchValue }
6444
6536
  }).then(({ data: res }) => {
6445
6537
  if (cancelled) return;
6446
6538
  const payload = encrypt ? decryptLaravelPayload(res, key) : res;
@@ -6486,15 +6578,184 @@ function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOv
6486
6578
  return () => {
6487
6579
  cancelled = true;
6488
6580
  };
6489
- }, [url, currentPage, tick, JSON.stringify(params), encrypt, decryptPayloadLog, JSON.stringify(columnOverrides), searchValue]);
6581
+ }, [url, currentPage, tick, JSON.stringify(params), JSON.stringify(activeFilterParams), encrypt, decryptPayloadLog, JSON.stringify(columnOverrides), searchValue]);
6490
6582
  const handleSearchChange = (value) => {
6491
6583
  setSearchValue(value);
6492
6584
  setCurrentPage(1);
6493
6585
  if (debounceTimer.current) clearTimeout(debounceTimer.current);
6494
- debounceTimer.current = setTimeout(() => {
6495
- setTick((t) => t + 1);
6496
- }, debounce);
6586
+ debounceTimer.current = setTimeout(() => setTick((t) => t + 1), debounce);
6587
+ };
6588
+ const handleFilterChange = (key2, value) => {
6589
+ setFilterValues((prev) => ({ ...prev, [key2]: value }));
6590
+ setCurrentPage(1);
6591
+ setTick((t) => t + 1);
6592
+ };
6593
+ const handleClearFilters = () => {
6594
+ const reset = {};
6595
+ filterFields?.forEach((f) => {
6596
+ reset[f.key] = f.type === "checkbox" || f.type === "toggle" ? false : "";
6597
+ });
6598
+ setFilterValues(reset);
6599
+ setSortKey("");
6600
+ setSortDir("asc");
6601
+ setCurrentPage(1);
6602
+ setTick((t) => t + 1);
6497
6603
  };
6604
+ const hasActiveFilters = filterFields?.some((f) => {
6605
+ const v = filterValues[f.key];
6606
+ return f.type === "checkbox" || f.type === "toggle" ? !!v : v !== "" && v !== null && v !== void 0;
6607
+ }) || !!sortKey;
6608
+ const filterBar = filterFields?.length || sortKeys?.length ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-wrap items-end gap-3 rounded-xl border border-border bg-muted/30 px-4 py-3", children: [
6609
+ filterFields?.map((f) => {
6610
+ const label = f.label ?? f.key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
6611
+ const value = filterValues[f.key];
6612
+ const opts = (f.options ?? []).map(
6613
+ (o) => typeof o === "string" ? { label: o, value: o } : o
6614
+ );
6615
+ if (f.type === "checkbox") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer select-none", children: [
6616
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6617
+ "input",
6618
+ {
6619
+ type: "checkbox",
6620
+ checked: !!value,
6621
+ onChange: (e) => handleFilterChange(f.key, e.target.checked),
6622
+ className: "h-4 w-4 rounded accent-primary"
6623
+ }
6624
+ ),
6625
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xs font-medium text-foreground", children: label })
6626
+ ] }, f.key);
6627
+ if (f.type === "toggle") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer select-none", children: [
6628
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6629
+ "button",
6630
+ {
6631
+ type: "button",
6632
+ role: "switch",
6633
+ "aria-checked": !!value,
6634
+ onClick: () => handleFilterChange(f.key, !value),
6635
+ className: cn(
6636
+ "relative inline-flex h-5 w-9 shrink-0 rounded-full border-2 border-transparent transition-colors",
6637
+ value ? "bg-primary" : "bg-muted"
6638
+ ),
6639
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn(
6640
+ "pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
6641
+ value ? "translate-x-4" : "translate-x-0"
6642
+ ) })
6643
+ }
6644
+ ),
6645
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xs font-medium text-foreground", children: label })
6646
+ ] }, f.key);
6647
+ if (f.type === "select") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6648
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: label }),
6649
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
6650
+ "select",
6651
+ {
6652
+ value: value ?? "",
6653
+ onChange: (e) => handleFilterChange(f.key, e.target.value),
6654
+ className: "h-8 min-w-[120px] rounded-lg border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors",
6655
+ children: [
6656
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: "", children: f.placeholder ?? `All ${label}` }),
6657
+ opts.map((o) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: o.value, children: o.label }, o.value))
6658
+ ]
6659
+ }
6660
+ )
6661
+ ] }, f.key);
6662
+ if (f.type === "date" || f.type === "date-time") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6663
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: label }),
6664
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6665
+ "input",
6666
+ {
6667
+ type: f.type === "date-time" ? "datetime-local" : "date",
6668
+ value: value ?? "",
6669
+ onChange: (e) => handleFilterChange(f.key, e.target.value),
6670
+ className: "h-8 rounded-lg border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors"
6671
+ }
6672
+ )
6673
+ ] }, f.key);
6674
+ if (f.type === "date-range") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6675
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: label }),
6676
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-1.5", children: [
6677
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6678
+ "input",
6679
+ {
6680
+ type: "date",
6681
+ value: value?.from ?? "",
6682
+ onChange: (e) => handleFilterChange(f.key, { ...value, from: e.target.value }),
6683
+ className: "h-8 rounded-lg border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors"
6684
+ }
6685
+ ),
6686
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xs text-muted-foreground", children: "\u2013" }),
6687
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6688
+ "input",
6689
+ {
6690
+ type: "date",
6691
+ value: value?.to ?? "",
6692
+ onChange: (e) => handleFilterChange(f.key, { ...value, to: e.target.value }),
6693
+ className: "h-8 rounded-lg border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors"
6694
+ }
6695
+ )
6696
+ ] })
6697
+ ] }, f.key);
6698
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6699
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: label }),
6700
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6701
+ "input",
6702
+ {
6703
+ type: "text",
6704
+ value: value ?? "",
6705
+ placeholder: f.placeholder ?? `Filter ${label}\u2026`,
6706
+ onChange: (e) => handleFilterChange(f.key, e.target.value),
6707
+ className: "h-8 min-w-[140px] rounded-lg border border-border bg-background px-3 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors"
6708
+ }
6709
+ )
6710
+ ] }, f.key);
6711
+ }),
6712
+ sortKeys?.length ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
6713
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: "Sort by" }),
6714
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-1.5", children: [
6715
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
6716
+ "select",
6717
+ {
6718
+ value: sortKey,
6719
+ onChange: (e) => {
6720
+ setSortKey(e.target.value);
6721
+ setCurrentPage(1);
6722
+ setTick((t) => t + 1);
6723
+ },
6724
+ className: "h-8 min-w-[120px] rounded-lg border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors",
6725
+ children: [
6726
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: "", children: "Default" }),
6727
+ sortKeys.map((k) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: k, children: k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) }, k))
6728
+ ]
6729
+ }
6730
+ ),
6731
+ sortKey && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6732
+ "button",
6733
+ {
6734
+ type: "button",
6735
+ onClick: () => {
6736
+ setSortDir((d) => d === "asc" ? "desc" : "asc");
6737
+ setTick((t) => t + 1);
6738
+ },
6739
+ className: "flex h-8 w-8 items-center justify-center rounded-lg border border-border bg-background text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
6740
+ title: sortDir === "asc" ? "Ascending" : "Descending",
6741
+ children: sortDir === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.ChevronUp, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.ChevronDown, { className: "h-3.5 w-3.5" })
6742
+ }
6743
+ )
6744
+ ] })
6745
+ ] }) : null,
6746
+ hasActiveFilters && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
6747
+ "button",
6748
+ {
6749
+ type: "button",
6750
+ onClick: handleClearFilters,
6751
+ className: "flex h-8 items-center gap-1.5 self-end rounded-lg border border-border bg-background px-3 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground transition-colors",
6752
+ children: [
6753
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.X, { className: "h-3 w-3" }),
6754
+ " Clear"
6755
+ ]
6756
+ }
6757
+ )
6758
+ ] }) : null;
6498
6759
  return {
6499
6760
  data,
6500
6761
  columns,
@@ -6503,11 +6764,18 @@ function useServerTable({ url, params, encrypt, key, decryptPayloadLog, columnOv
6503
6764
  serverPagination: pagination ? { pagination, currentPage, goToPage: (page) => setCurrentPage(page) } : null,
6504
6765
  loading,
6505
6766
  error,
6767
+ filterBar,
6506
6768
  goToPage: (page) => setCurrentPage(page),
6507
6769
  reload: () => setTick((t) => t + 1),
6508
6770
  refresh: () => setTick((t) => t + 1),
6771
+ // Passthrough props
6509
6772
  searchValue,
6510
- onSearchChange: handleSearchChange
6773
+ onSearchChange: handleSearchChange,
6774
+ page: currentPage,
6775
+ onPageChange: (page) => setCurrentPage(page),
6776
+ sort: [],
6777
+ onSortChange: () => {
6778
+ }
6511
6779
  };
6512
6780
  }
6513
6781
  var MODAL_WIDTH = {
@@ -6565,6 +6833,7 @@ function validateField(field, value) {
6565
6833
  return null;
6566
6834
  }
6567
6835
  function FieldRenderer({ field, value, onChange }) {
6836
+ if (field.component) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_jsx_runtime32.Fragment, { children: field.component });
6568
6837
  if (field.render) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_jsx_runtime32.Fragment, { children: field.render(value, onChange) });
6569
6838
  const toLabelValue = (o) => {
6570
6839
  if (typeof o === "string") return { label: o, value: o };
@@ -6657,9 +6926,33 @@ function FieldRenderer({ field, value, onChange }) {
6657
6926
  case "rich-text":
6658
6927
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RichTextEditor, { value: value ?? "", onChange: (v) => onChange(v) });
6659
6928
  case "file-upload":
6660
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(FileUpload, { onChange: (files) => onChange(files) });
6929
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(FileUpload, { onFileSelect: (file) => onChange(file), onFilesChange: (files) => {
6930
+ if (files.length > 1) onChange(files);
6931
+ } });
6661
6932
  case "repeater": {
6662
6933
  const items = Array.isArray(value) ? value : [];
6934
+ if (field.repeaterFields) {
6935
+ const rows = Array.isArray(value) ? value : [];
6936
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6937
+ Repeater,
6938
+ {
6939
+ items: rows,
6940
+ fields: field.repeaterFields,
6941
+ onAdd: () => {
6942
+ const blank = {};
6943
+ field.repeaterFields.forEach((f) => {
6944
+ blank[f.key] = "";
6945
+ });
6946
+ onChange([...rows, blank]);
6947
+ },
6948
+ onRemove: (i) => onChange(rows.filter((_, idx) => idx !== i)),
6949
+ onFieldChange: (i, key, val) => {
6950
+ const next = rows.map((r, idx) => idx === i ? { ...r, [key]: val } : r);
6951
+ onChange(next);
6952
+ }
6953
+ }
6954
+ );
6955
+ }
6663
6956
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6664
6957
  Repeater,
6665
6958
  {
@@ -6700,8 +6993,172 @@ function ViewModal({
6700
6993
  item,
6701
6994
  fields,
6702
6995
  onClose,
6703
- width
6996
+ width,
6997
+ grid
6704
6998
  }) {
6999
+ const renderViewValue = (f, value) => {
7000
+ const sizeStyle = {
7001
+ ...f.width ? { width: typeof f.width === "number" ? `${f.width}px` : f.width } : {},
7002
+ ...f.height ? { height: typeof f.height === "number" ? `${f.height}px` : f.height } : {}
7003
+ };
7004
+ const vt = f.viewType ?? f.type;
7005
+ const empty = value === null || value === void 0 || value === "";
7006
+ const dash = /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-muted-foreground italic text-sm", children: "\u2014" });
7007
+ if (f.component) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_jsx_runtime32.Fragment, { children: f.component });
7008
+ if (f.render) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_jsx_runtime32.Fragment, { children: f.render(value, () => {
7009
+ }) });
7010
+ switch (vt) {
7011
+ case "image":
7012
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7013
+ "img",
7014
+ {
7015
+ src: value,
7016
+ alt: f.label,
7017
+ className: "rounded-xl object-cover ring-1 ring-border",
7018
+ style: { width: sizeStyle.width ?? 128, height: sizeStyle.height ?? 128 }
7019
+ }
7020
+ );
7021
+ case "image-url":
7022
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7023
+ "a",
7024
+ {
7025
+ href: value,
7026
+ onClick: (e) => e.preventDefault(),
7027
+ className: "inline-block rounded-xl overflow-hidden ring-1 ring-border hover:ring-primary transition-all",
7028
+ style: { width: sizeStyle.width ?? 128, height: sizeStyle.height ?? 128 },
7029
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("img", { src: value, alt: f.label, className: "w-full h-full object-cover" })
7030
+ }
7031
+ );
7032
+ case "image-url-open-other-tabs":
7033
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7034
+ "a",
7035
+ {
7036
+ href: value,
7037
+ target: "_blank",
7038
+ rel: "noopener noreferrer",
7039
+ className: "inline-block rounded-xl overflow-hidden ring-1 ring-border hover:ring-primary transition-all",
7040
+ style: { width: sizeStyle.width ?? 128, height: sizeStyle.height ?? 128 },
7041
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("img", { src: value, alt: f.label, className: "w-full h-full object-cover" })
7042
+ }
7043
+ );
7044
+ case "text-url":
7045
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7046
+ "a",
7047
+ {
7048
+ href: value,
7049
+ onClick: (e) => e.preventDefault(),
7050
+ className: "text-sm text-primary underline underline-offset-2 hover:text-primary/80 break-all",
7051
+ style: sizeStyle,
7052
+ children: value
7053
+ }
7054
+ );
7055
+ case "text-url-open-other-tabs":
7056
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7057
+ "a",
7058
+ {
7059
+ href: value,
7060
+ target: "_blank",
7061
+ rel: "noopener noreferrer",
7062
+ className: "text-sm text-primary underline underline-offset-2 hover:text-primary/80 break-all",
7063
+ style: sizeStyle,
7064
+ children: value
7065
+ }
7066
+ );
7067
+ case "attachment":
7068
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7069
+ "a",
7070
+ {
7071
+ href: value,
7072
+ target: "_blank",
7073
+ rel: "noopener noreferrer",
7074
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/50 px-3 py-1.5 text-xs font-medium text-foreground hover:bg-muted transition-colors",
7075
+ style: sizeStyle,
7076
+ children: [
7077
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("svg", { className: "h-3.5 w-3.5 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" }) }),
7078
+ String(value).split("/").pop() ?? "Download"
7079
+ ]
7080
+ }
7081
+ );
7082
+ case "repeater": {
7083
+ const rows = Array.isArray(value) ? value : [];
7084
+ if (!rows.length) return dash;
7085
+ const rFields = f.repeaterFields;
7086
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "space-y-2", children: rows.map((row, ri) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-wrap gap-3 rounded-xl border border-border bg-muted/30 px-3 py-2", children: [
7087
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary", children: ri + 1 }),
7088
+ rFields ? rFields.map((rf) => {
7089
+ const v = row[rf.key];
7090
+ if (rf.type === "image") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
7091
+ rf.label && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] text-muted-foreground", children: rf.label }),
7092
+ v ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("img", { src: v, alt: rf.key, className: "h-10 w-10 rounded-lg object-cover ring-1 ring-border" }) : dash
7093
+ ] }, rf.key);
7094
+ if (rf.type === "attachment") return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
7095
+ rf.label && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] text-muted-foreground", children: rf.label }),
7096
+ v ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7097
+ "a",
7098
+ {
7099
+ href: v,
7100
+ target: "_blank",
7101
+ rel: "noopener noreferrer",
7102
+ className: "inline-flex items-center gap-1 rounded-lg border border-border bg-muted/50 px-2 py-1 text-xs font-medium hover:bg-muted transition-colors",
7103
+ children: [
7104
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("svg", { className: "h-3 w-3 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" }) }),
7105
+ String(v).split("/").pop()
7106
+ ]
7107
+ }
7108
+ ) : dash
7109
+ ] }, rf.key);
7110
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
7111
+ rf.label && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] text-muted-foreground", children: rf.label }),
7112
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-sm", children: v ?? "\u2014" })
7113
+ ] }, rf.key);
7114
+ }) : (
7115
+ // payload mode: row has { type, key, value }
7116
+ Object.entries(row).map(([k, v]) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-1", children: [
7117
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[10px] text-muted-foreground", children: k }),
7118
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-sm", children: String(v) })
7119
+ ] }, k))
7120
+ )
7121
+ ] }, ri)) });
7122
+ }
7123
+ case "checkbox":
7124
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7125
+ "input",
7126
+ {
7127
+ type: "checkbox",
7128
+ checked: !!value,
7129
+ readOnly: true,
7130
+ className: "h-4 w-4 rounded border-border accent-primary cursor-default",
7131
+ style: sizeStyle
7132
+ }
7133
+ );
7134
+ case "toggle":
7135
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7136
+ "div",
7137
+ {
7138
+ className: cn(
7139
+ "relative inline-flex shrink-0 rounded-full border-2 border-transparent transition-colors",
7140
+ value ? "bg-primary" : "bg-muted"
7141
+ ),
7142
+ style: { width: sizeStyle.width ?? 36, height: sizeStyle.height ?? 20 },
7143
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7144
+ "span",
7145
+ {
7146
+ className: cn(
7147
+ "pointer-events-none inline-block rounded-full bg-white shadow-sm transition-transform"
7148
+ ),
7149
+ style: {
7150
+ width: typeof (sizeStyle.height ?? 20) === "number" ? sizeStyle.height - 4 : 16,
7151
+ height: typeof (sizeStyle.height ?? 20) === "number" ? sizeStyle.height - 4 : 16,
7152
+ transform: value ? `translateX(${typeof (sizeStyle.width ?? 36) === "number" ? sizeStyle.width - (sizeStyle.height ?? 20) : 16}px)` : "translateX(0)"
7153
+ }
7154
+ }
7155
+ )
7156
+ }
7157
+ );
7158
+ default:
7159
+ return empty ? dash : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-sm text-foreground break-words", style: sizeStyle, children: String(value) });
7160
+ }
7161
+ };
6705
7162
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6706
7163
  ModalShell,
6707
7164
  {
@@ -6709,11 +7166,27 @@ function ViewModal({
6709
7166
  onClose,
6710
7167
  width,
6711
7168
  footer: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Button, { variant: "outline", size: "sm", onClick: onClose, children: "Close" }),
6712
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "space-y-3", children: fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { children: [
6713
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-xs font-semibold text-muted-foreground mb-1", children: f.label }),
6714
- f.render ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_jsx_runtime32.Fragment, { children: f.render(item[f.key], () => {
6715
- }) }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-sm text-foreground break-words", children: item[f.key] === null || item[f.key] === void 0 || item[f.key] === "" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-muted-foreground italic", children: "\u2014" }) : String(item[f.key]) })
6716
- ] }, f.key)) })
7169
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7170
+ "div",
7171
+ {
7172
+ className: grid ? "grid gap-4" : "space-y-3",
7173
+ style: grid ? { gridTemplateColumns: `repeat(${grid}, minmax(0, 1fr))` } : void 0,
7174
+ children: fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7175
+ "div",
7176
+ {
7177
+ style: {
7178
+ ...f.colSpan ? { gridColumn: `span ${f.colSpan}` } : {},
7179
+ ...f.rowSpan ? { gridRow: `span ${f.rowSpan}` } : {}
7180
+ },
7181
+ children: [
7182
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-xs font-semibold text-muted-foreground mb-1", children: f.label }),
7183
+ renderViewValue(f, item[f.key])
7184
+ ]
7185
+ },
7186
+ f.key
7187
+ ))
7188
+ }
7189
+ )
6717
7190
  }
6718
7191
  );
6719
7192
  }
@@ -6731,7 +7204,7 @@ function EditModal({
6731
7204
  const [form, setForm] = React28.useState(() => {
6732
7205
  const init = {};
6733
7206
  fields.forEach((f) => {
6734
- init[f.key] = item[f.key] ?? "";
7207
+ init[f.key] = f.type === "file-upload" ? null : item[f.key] ?? "";
6735
7208
  });
6736
7209
  return init;
6737
7210
  });
@@ -6743,6 +7216,7 @@ function EditModal({
6743
7216
  e.preventDefault();
6744
7217
  const errs = {};
6745
7218
  fields.forEach((f) => {
7219
+ if (f.type === "file-upload" && !form[f.key] && item[f.key]) return;
6746
7220
  const msg = validateField(f, form[f.key]);
6747
7221
  if (msg) errs[f.key] = msg;
6748
7222
  });
@@ -6754,7 +7228,30 @@ function EditModal({
6754
7228
  setLoading(true);
6755
7229
  setError(null);
6756
7230
  try {
6757
- await csrfAxios.put(`${baseUrl}/${itemId}/update`, form);
7231
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute("content");
7232
+ if (!csrfToken) throw new Error("[Table] CSRF token not found.");
7233
+ const isFile = (v) => v instanceof File;
7234
+ const isFileArray = (v) => Array.isArray(v) && v.length > 0 && v[0] instanceof File;
7235
+ const hasFiles = Object.values(form).some((v) => isFile(v) || isFileArray(v));
7236
+ let body;
7237
+ if (hasFiles) {
7238
+ const fd = new FormData();
7239
+ fd.append("_method", "PUT");
7240
+ Object.entries(form).forEach(([k, v]) => {
7241
+ if (isFileArray(v)) {
7242
+ v.forEach((f) => fd.append(k, f));
7243
+ } else if (isFile(v)) {
7244
+ fd.append(k, v);
7245
+ } else if (v === null || v === void 0) {
7246
+ } else if (!Array.isArray(v)) {
7247
+ fd.append(k, String(v));
7248
+ }
7249
+ });
7250
+ body = fd;
7251
+ } else {
7252
+ body = form;
7253
+ }
7254
+ await import_axios3.default.put(`${baseUrl}/${itemId}/update`, body, { headers: { "X-CSRF-Token": csrfToken } });
6758
7255
  const updated = { ...item, ...form };
6759
7256
  if (notif && (notif.type ?? "toast") === "notification") {
6760
7257
  setBanner(true);
@@ -6795,14 +7292,14 @@ function EditModal({
6795
7292
  ),
6796
7293
  notif.action && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { children: notif.action })
6797
7294
  ] }),
6798
- fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7295
+ fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6799
7296
  "div",
6800
7297
  {
6801
7298
  style: {
6802
7299
  ...f.colSpan ? { gridColumn: `span ${f.colSpan}` } : {},
6803
7300
  ...f.rowSpan ? { gridRow: `span ${f.rowSpan}` } : {}
6804
7301
  },
6805
- children: [
7302
+ children: f.component ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_jsx_runtime32.Fragment, { children: f.component }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
6806
7303
  f.type !== "checkbox" && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("label", { className: "block text-xs font-semibold text-muted-foreground mb-1", children: [
6807
7304
  f.label,
6808
7305
  f.required && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-danger ml-0.5", children: "*" })
@@ -6822,7 +7319,7 @@ function EditModal({
6822
7319
  }
6823
7320
  ),
6824
7321
  fieldErrors[f.key] && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-xs text-danger mt-1", children: fieldErrors[f.key] })
6825
- ]
7322
+ ] })
6826
7323
  },
6827
7324
  f.key
6828
7325
  )),
@@ -6845,7 +7342,9 @@ function DeleteModal({
6845
7342
  setLoading(true);
6846
7343
  setError(null);
6847
7344
  try {
6848
- await csrfAxios.delete(`${baseUrl}/${itemId}/delete`);
7345
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute("content");
7346
+ if (!csrfToken) throw new Error("[Table] CSRF token not found.");
7347
+ await import_axios3.default.delete(`${baseUrl}/${itemId}/delete?csrfToken=${encodeURIComponent(csrfToken)}`, { headers: { "X-CSRF-Token": csrfToken } });
6849
7348
  onSuccess?.(item);
6850
7349
  onClose();
6851
7350
  } catch (err) {
@@ -6920,26 +7419,89 @@ var BADGE_COLORS = {
6920
7419
  function badgeClass(value) {
6921
7420
  return BADGE_COLORS[value.toLowerCase()] ?? "bg-primary/10 text-primary border-primary/20";
6922
7421
  }
7422
+ function deriveField(key, sample) {
7423
+ const label = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
7424
+ const base = { key, label };
7425
+ if (typeof sample === "boolean") return { ...base, type: "toggle", viewType: "toggle" };
7426
+ if (typeof sample === "number") return { ...base, inputType: "number" };
7427
+ if (Array.isArray(sample)) {
7428
+ if (sample.length === 0 || typeof sample[0] === "string")
7429
+ return { ...base, type: "tag-input" };
7430
+ return base;
7431
+ }
7432
+ if (typeof sample === "string") {
7433
+ if (/\.(png|jpe?g|gif|webp|svg|avif)(\?.*)?$/i.test(sample))
7434
+ return { ...base, type: "input", viewType: "image" };
7435
+ if (/\.(pdf|docx?|xlsx?|csv|zip|pptx?)(\?.*)?$/i.test(sample))
7436
+ return { ...base, type: "input", viewType: "attachment" };
7437
+ if (/^https?:\/\//.test(sample))
7438
+ return { ...base, type: "input", viewType: "text-url-open-other-tabs" };
7439
+ if (sample.length > 120 || /\n/.test(sample))
7440
+ return { ...base, type: "textarea" };
7441
+ if (/password|secret|token/i.test(key))
7442
+ return { ...base, type: "password" };
7443
+ if (/email/i.test(key))
7444
+ return { ...base, inputType: "email" };
7445
+ if (/color|colour/i.test(key) && /^#[0-9a-f]{3,8}$/i.test(sample))
7446
+ return { ...base, type: "color-picker", viewType: "text" };
7447
+ }
7448
+ return base;
7449
+ }
6923
7450
  function Table({
6924
7451
  data,
6925
7452
  columns,
7453
+ loading,
7454
+ emptyState,
7455
+ error: errorProp,
6926
7456
  searchable = false,
6927
7457
  searchPlaceholder = "Search...",
6928
- pagination = false,
7458
+ searchValue: controlledSearch,
7459
+ onSearchChange,
7460
+ clientPagination = false,
6929
7461
  itemsPerPage = 10,
6930
7462
  selectable = false,
6931
7463
  onBulkDelete,
6932
7464
  idKey = "id",
7465
+ bulkDeleteBaseUrl,
6933
7466
  defaultActions,
6934
7467
  serverPagination,
6935
- className
7468
+ variant = "default",
7469
+ className,
7470
+ onRowClick,
7471
+ onRowDoubleClick,
7472
+ rowClassName,
7473
+ expandable = false,
7474
+ renderExpanded,
7475
+ columnVisibility,
7476
+ onColumnVisibilityChange,
7477
+ columnVisibilityIcon,
7478
+ filterBar,
7479
+ filterableIcon,
7480
+ exportable = false,
7481
+ onExport,
7482
+ virtualized = false,
7483
+ draggable = false,
7484
+ onRowReorder,
7485
+ keyboardNavigation = false
6936
7486
  }) {
6937
7487
  const { toast } = useToast();
6938
- const [search, setSearch] = React28.useState("");
7488
+ const isControlledSearch = controlledSearch !== void 0;
7489
+ const [internalSearch, setInternalSearch] = React28.useState("");
7490
+ const search = isControlledSearch ? controlledSearch : internalSearch;
7491
+ const setSearch = (v) => {
7492
+ if (!isControlledSearch) setInternalSearch(v);
7493
+ onSearchChange?.(v);
7494
+ };
6939
7495
  const [currentPage, setCurrentPage] = React28.useState(1);
6940
7496
  const [selectedIds, setSelectedIds] = React28.useState([]);
6941
7497
  const [sortKey, setSortKey] = React28.useState(null);
6942
7498
  const [sortDir, setSortDir] = React28.useState(null);
7499
+ const [bulkLoading, setBulkLoading] = React28.useState(false);
7500
+ const [bulkConfirm, setBulkConfirm] = React28.useState(null);
7501
+ const [filterBarOpen, setFilterBarOpen] = React28.useState(false);
7502
+ const [expandedIds, setExpandedIds] = React28.useState(/* @__PURE__ */ new Set());
7503
+ const [dragOverId, setDragOverId] = React28.useState(null);
7504
+ const [focusedRowIdx, setFocusedRowIdx] = React28.useState(-1);
6943
7505
  const [viewItem, setViewItem] = React28.useState(null);
6944
7506
  const [editItem, setEditItem] = React28.useState(null);
6945
7507
  const [deleteItem, setDeleteItem] = React28.useState(null);
@@ -6951,15 +7513,17 @@ function Table({
6951
7513
  const safeBaseUrl = defaultActions?.baseUrl.replace(/\/+$/, "") ?? "";
6952
7514
  const autoFields = React28.useMemo(() => {
6953
7515
  if (!tableData.length) return [];
6954
- return Object.keys(tableData[0]).map((k) => ({
6955
- key: k,
6956
- label: k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
6957
- }));
7516
+ const row = tableData[0];
7517
+ return Object.keys(row).map((k) => deriveField(k, row[k]));
6958
7518
  }, [tableData]);
6959
7519
  const editFields = defaultActions?.editForm ?? autoFields;
6960
7520
  const viewFields = defaultActions?.viewForm ?? autoFields;
7521
+ const visibleColumns = React28.useMemo(() => {
7522
+ if (!columnVisibility) return columns;
7523
+ return columns.filter((col) => columnVisibility[String(col.key)] !== false);
7524
+ }, [columns, columnVisibility]);
6961
7525
  const allColumns = React28.useMemo(() => {
6962
- if (!defaultActions) return columns;
7526
+ if (!defaultActions) return visibleColumns;
6963
7527
  const actionsCol = {
6964
7528
  key: "__actions__",
6965
7529
  title: "Actions",
@@ -7008,8 +7572,8 @@ function Table({
7008
7572
  ))
7009
7573
  ] })
7010
7574
  };
7011
- return defaultActions.position === "first" ? [actionsCol, ...columns] : [...columns, actionsCol];
7012
- }, [columns, defaultActions]);
7575
+ return defaultActions.position === "first" ? [actionsCol, ...visibleColumns] : [...visibleColumns, actionsCol];
7576
+ }, [visibleColumns, defaultActions]);
7013
7577
  const handleSort = (key) => {
7014
7578
  if (sortKey !== key) {
7015
7579
  setSortKey(key);
@@ -7042,16 +7606,78 @@ function Table({
7042
7606
  const totalPages = Math.max(1, Math.ceil(filteredData.length / itemsPerPage));
7043
7607
  const safePage = Math.min(currentPage, totalPages);
7044
7608
  const paginatedData = React28.useMemo(() => {
7045
- if (!pagination) return filteredData;
7609
+ if (!clientPagination) return filteredData;
7046
7610
  const start = (safePage - 1) * itemsPerPage;
7047
7611
  return filteredData.slice(start, start + itemsPerPage);
7048
- }, [filteredData, pagination, safePage, itemsPerPage]);
7612
+ }, [filteredData, clientPagination, safePage, itemsPerPage]);
7049
7613
  React28.useEffect(() => {
7050
7614
  setCurrentPage(1);
7051
7615
  }, [search]);
7616
+ React28.useEffect(() => {
7617
+ if (!keyboardNavigation) return;
7618
+ const handler = (e) => {
7619
+ if (e.key === "ArrowDown") {
7620
+ e.preventDefault();
7621
+ setFocusedRowIdx((i) => Math.min(i + 1, paginatedData.length - 1));
7622
+ }
7623
+ if (e.key === "ArrowUp") {
7624
+ e.preventDefault();
7625
+ setFocusedRowIdx((i) => Math.max(i - 1, 0));
7626
+ }
7627
+ };
7628
+ window.addEventListener("keydown", handler);
7629
+ return () => window.removeEventListener("keydown", handler);
7630
+ }, [keyboardNavigation, paginatedData.length]);
7052
7631
  const handleSelectAll = (checked) => setSelectedIds(checked ? paginatedData.map((item) => String(item[idKey])) : []);
7053
7632
  const handleSelect = (id, checked) => setSelectedIds((prev) => checked ? [...prev, id] : prev.filter((i) => i !== id));
7054
7633
  const allSelected = paginatedData.length > 0 && selectedIds.length === paginatedData.length;
7634
+ const totalRows = serverPagination ? serverPagination.pagination.total : filteredData.length;
7635
+ const unselectedCount = totalRows - selectedIds.length;
7636
+ const handleSelectAllRecords = () => setSelectedIds(filteredData.map((item) => String(item[idKey])));
7637
+ const handleUnselectAll = () => setSelectedIds([]);
7638
+ const execBulkDeleteSelected = async () => {
7639
+ if (!bulkDeleteBaseUrl || selectedIds.length === 0) {
7640
+ onBulkDelete?.(selectedIds);
7641
+ setSelectedIds([]);
7642
+ return;
7643
+ }
7644
+ setBulkLoading(true);
7645
+ try {
7646
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute("content");
7647
+ if (!csrfToken) throw new Error("[Table] CSRF token not found.");
7648
+ const safeUrl = bulkDeleteBaseUrl.replace(/\/+$/, "");
7649
+ await import_axios3.default.delete(`${safeUrl}/delete/${selectedIds.join(",")}/selected`, { headers: { "X-CSRF-Token": csrfToken } });
7650
+ setTableData((prev) => prev.filter((r) => !selectedIds.includes(String(r[idKey]))));
7651
+ onBulkDelete?.(selectedIds);
7652
+ setSelectedIds([]);
7653
+ defaultActions?.onReload?.();
7654
+ toast({ variant: "success", title: "Deleted", description: `${selectedIds.length} record${selectedIds.length !== 1 ? "s" : ""} deleted successfully.` });
7655
+ } catch (err) {
7656
+ const msg = err?.response?.data?.message ?? err.message ?? "Bulk delete failed";
7657
+ toast({ variant: "error", title: "Delete failed", description: msg });
7658
+ } finally {
7659
+ setBulkLoading(false);
7660
+ }
7661
+ };
7662
+ const execDeleteAll = async () => {
7663
+ if (!bulkDeleteBaseUrl) return;
7664
+ setBulkLoading(true);
7665
+ try {
7666
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute("content");
7667
+ if (!csrfToken) throw new Error("[Table] CSRF token not found.");
7668
+ const safeUrl = bulkDeleteBaseUrl.replace(/\/+$/, "");
7669
+ await import_axios3.default.delete(`${safeUrl}/delete/all`, { headers: { "X-CSRF-Token": csrfToken } });
7670
+ setTableData([]);
7671
+ setSelectedIds([]);
7672
+ defaultActions?.onReload?.();
7673
+ toast({ variant: "success", title: "Deleted", description: "All records deleted successfully." });
7674
+ } catch (err) {
7675
+ const msg = err?.response?.data?.message ?? err.message ?? "Delete all failed";
7676
+ toast({ variant: "error", title: "Delete failed", description: msg });
7677
+ } finally {
7678
+ setBulkLoading(false);
7679
+ }
7680
+ };
7055
7681
  const pagePills = React28.useMemo(() => {
7056
7682
  if (totalPages <= 5) return Array.from({ length: totalPages }, (_, i) => i + 1);
7057
7683
  if (safePage <= 3) return [1, 2, 3, 4, 5];
@@ -7065,6 +7691,7 @@ function Table({
7065
7691
  };
7066
7692
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
7067
7693
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: cn("w-full space-y-3", className), children: [
7694
+ errorProp && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "rounded-xl border border-danger/30 bg-danger/5 px-4 py-3 text-sm text-danger", children: errorProp }),
7068
7695
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-between gap-3 flex-wrap", children: [
7069
7696
  searchable && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "relative w-72", children: [
7070
7697
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Search, { className: "absolute text-primary left-3 top-1/2 -translate-y-1/2 h-4 w-4 z-10" }),
@@ -7086,33 +7713,127 @@ function Table({
7086
7713
  }
7087
7714
  )
7088
7715
  ] }),
7089
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-2 ml-auto", children: [
7090
- selectable && onBulkDelete && selectedIds.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7716
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-2 ml-auto flex-wrap", children: [
7717
+ selectable && selectedIds.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
7718
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7719
+ "button",
7720
+ {
7721
+ onClick: handleUnselectAll,
7722
+ disabled: bulkLoading,
7723
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/50 px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-muted transition-colors disabled:opacity-40",
7724
+ children: [
7725
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.X, { className: "h-3.5 w-3.5" }),
7726
+ "Unselect all ",
7727
+ selectedIds.length
7728
+ ]
7729
+ }
7730
+ ),
7731
+ unselectedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7732
+ "button",
7733
+ {
7734
+ onClick: handleSelectAllRecords,
7735
+ disabled: bulkLoading,
7736
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/50 px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-muted transition-colors disabled:opacity-40",
7737
+ children: [
7738
+ "Select all ",
7739
+ unselectedCount
7740
+ ]
7741
+ }
7742
+ ),
7743
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7744
+ "button",
7745
+ {
7746
+ onClick: () => setBulkConfirm("selected"),
7747
+ disabled: bulkLoading,
7748
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-danger/10 border border-danger/20 px-3 py-1.5 text-xs font-medium text-danger hover:bg-danger/20 transition-colors disabled:opacity-40",
7749
+ children: [
7750
+ bulkLoading ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Trash2, { className: "h-3.5 w-3.5" }),
7751
+ "Delete ",
7752
+ selectedIds.length,
7753
+ " selected"
7754
+ ]
7755
+ }
7756
+ ),
7757
+ bulkDeleteBaseUrl && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7758
+ "button",
7759
+ {
7760
+ onClick: () => setBulkConfirm("all"),
7761
+ disabled: bulkLoading,
7762
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-danger/10 border border-danger/20 px-3 py-1.5 text-xs font-medium text-danger hover:bg-danger/20 transition-colors disabled:opacity-40",
7763
+ children: [
7764
+ bulkLoading ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Trash2, { className: "h-3.5 w-3.5" }),
7765
+ "Delete all"
7766
+ ]
7767
+ }
7768
+ )
7769
+ ] }),
7770
+ filterBar && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7091
7771
  "button",
7092
7772
  {
7093
- onClick: () => {
7094
- onBulkDelete(selectedIds);
7095
- setSelectedIds([]);
7096
- },
7097
- className: "inline-flex items-center gap-1.5 rounded-lg bg-danger/10 border border-danger/20 px-3 py-1.5 text-xs font-medium text-danger hover:bg-danger/20 transition-colors",
7098
- children: [
7099
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Trash2, { className: "h-3.5 w-3.5" }),
7100
- "Delete ",
7101
- selectedIds.length,
7102
- " selected"
7103
- ]
7773
+ onClick: () => setFilterBarOpen((o) => !o),
7774
+ className: cn(
7775
+ "inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors",
7776
+ filterBarOpen ? "border-primary bg-primary/10 text-primary hover:bg-primary/20" : "border-border bg-muted/50 text-muted-foreground hover:bg-muted"
7777
+ ),
7778
+ children: filterableIcon ?? "Filter"
7104
7779
  }
7105
7780
  ),
7781
+ exportable && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "relative group", children: [
7782
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { className: "inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/50 px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-muted transition-colors", children: "Export" }),
7783
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "absolute right-0 top-full mt-1 z-20 hidden group-hover:flex flex-col min-w-[110px] rounded-xl border border-border bg-card shadow-lg overflow-hidden", children: ["csv", "excel", "pdf"].map((type) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7784
+ "button",
7785
+ {
7786
+ onClick: () => onExport?.(type),
7787
+ className: "px-4 py-2 text-xs text-left hover:bg-muted transition-colors capitalize",
7788
+ children: type.toUpperCase()
7789
+ },
7790
+ type
7791
+ )) })
7792
+ ] }),
7793
+ columnVisibility && onColumnVisibilityChange && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "relative group", children: [
7794
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { className: "inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/50 px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-muted transition-colors", children: columnVisibilityIcon ?? "Columns" }),
7795
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "absolute right-0 top-full mt-1 z-20 hidden group-hover:flex flex-col min-w-[150px] rounded-xl border border-border bg-card shadow-lg overflow-hidden p-2 gap-1", children: columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("label", { className: "flex items-center gap-2 px-2 py-1 rounded-lg hover:bg-muted cursor-pointer text-xs", children: [
7796
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7797
+ "input",
7798
+ {
7799
+ type: "checkbox",
7800
+ checked: columnVisibility[String(col.key)] !== false,
7801
+ onChange: (e) => onColumnVisibilityChange({ ...columnVisibility, [String(col.key)]: e.target.checked }),
7802
+ className: "accent-primary"
7803
+ }
7804
+ ),
7805
+ col.title
7806
+ ] }, String(col.key))) })
7807
+ ] }),
7106
7808
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
7107
- serverPagination ? serverPagination.pagination.total : filteredData.length,
7809
+ totalRows,
7108
7810
  " ",
7109
- (serverPagination ? serverPagination.pagination.total : filteredData.length) === 1 ? "row" : "rows",
7811
+ totalRows === 1 ? "row" : "rows",
7110
7812
  search && ` \xB7 filtered from ${tableData.length}`
7111
7813
  ] })
7112
7814
  ] })
7113
7815
  ] }),
7114
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "rounded-xl border border-border overflow-hidden bg-card/50 backdrop-blur-sm shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "w-full overflow-auto", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("table", { className: "w-full caption-bottom text-sm", children: [
7115
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("tr", { className: "border-b border-border bg-muted/40", children: [
7816
+ filterBar && filterBarOpen && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { children: filterBar }),
7817
+ loading && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-center py-12 text-muted-foreground gap-2", children: [
7818
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Loader2, { className: "h-5 w-5 animate-spin" }),
7819
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-sm", children: "Loading\u2026" })
7820
+ ] }),
7821
+ !loading && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: cn(
7822
+ variant === "default" && "rounded-xl border border-border overflow-hidden bg-card/50 backdrop-blur-sm shadow-sm",
7823
+ variant === "zebra" && "rounded-xl border border-border overflow-hidden bg-card/50 backdrop-blur-sm shadow-sm",
7824
+ variant === "card" && "space-y-2",
7825
+ variant === "glass" && "rounded-2xl overflow-hidden border border-white/10 bg-background/30 backdrop-blur-xl shadow-xl",
7826
+ variant === "soft" && "rounded-2xl overflow-hidden bg-card",
7827
+ variant === "soft" && "[box-shadow:6px_6px_12px_hsl(var(--foreground)/0.07),-6px_-6px_12px_hsl(var(--background)/0.8)]",
7828
+ virtualized && "max-h-[520px] overflow-y-auto"
7829
+ ), children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: cn("w-full overflow-auto", variant === "card" && "space-y-2"), children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("table", { className: cn("w-full caption-bottom text-sm", variant === "card" && "border-separate border-spacing-y-2"), children: [
7830
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("tr", { className: cn(
7831
+ variant === "default" && "border-b border-border bg-muted/40",
7832
+ variant === "zebra" && "border-b border-border bg-muted/40",
7833
+ variant === "card" && "[&>th]:bg-transparent",
7834
+ variant === "glass" && "border-b border-white/10 bg-white/5",
7835
+ variant === "soft" && "border-b-0 bg-muted/30"
7836
+ ), children: [
7116
7837
  selectable && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("th", { className: "h-11 w-[46px] px-4 text-left align-middle", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7117
7838
  Checkbox,
7118
7839
  {
@@ -7120,13 +7841,16 @@ function Table({
7120
7841
  onChange: (e) => handleSelectAll(e.target.checked)
7121
7842
  }
7122
7843
  ) }),
7844
+ expandable && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("th", { className: "h-11 w-8" }),
7123
7845
  allColumns.map((col, ci) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7124
7846
  "th",
7125
7847
  {
7126
7848
  onClick: () => col.sortable && handleSort(String(col.key)),
7127
7849
  className: cn(
7128
7850
  "h-11 px-4 text-left align-middle text-xs font-semibold uppercase tracking-wider text-muted-foreground select-none whitespace-nowrap",
7129
- col.sortable && "cursor-pointer hover:text-foreground transition-colors"
7851
+ col.sortable && "cursor-pointer hover:text-foreground transition-colors",
7852
+ variant === "glass" && "text-foreground/70",
7853
+ variant === "soft" && "text-muted-foreground/80"
7130
7854
  ),
7131
7855
  children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "inline-flex items-center", children: [
7132
7856
  col.title,
@@ -7139,9 +7863,9 @@ function Table({
7139
7863
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("tbody", { children: paginatedData.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7140
7864
  "td",
7141
7865
  {
7142
- colSpan: allColumns.length + (selectable ? 1 : 0),
7866
+ colSpan: allColumns.length + (selectable ? 1 : 0) + (expandable ? 1 : 0),
7143
7867
  className: "h-32 text-center align-middle",
7144
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col items-center gap-1 text-muted-foreground", children: [
7868
+ children: emptyState ?? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col items-center gap-1 text-muted-foreground", children: [
7145
7869
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Search, { className: "h-8 w-8 opacity-20" }),
7146
7870
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-sm", children: "No results found" }),
7147
7871
  search && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { onClick: () => setSearch(""), className: "text-xs text-primary hover:underline", children: "Clear search" })
@@ -7150,85 +7874,168 @@ function Table({
7150
7874
  ) }) : paginatedData.map((item, i) => {
7151
7875
  const id = String(item[idKey] || i);
7152
7876
  const isSelected = selectedIds.includes(id);
7153
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7154
- "tr",
7155
- {
7156
- className: cn(
7157
- "border-b border-border/60 transition-colors last:border-0",
7158
- isSelected ? "bg-primary/5 hover:bg-primary/8" : "hover:bg-muted/30"
7159
- ),
7160
- children: [
7161
- selectable && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "px-4 py-3 align-middle", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7162
- Checkbox,
7163
- {
7164
- checked: isSelected,
7165
- onChange: (e) => handleSelect(id, e.target.checked)
7166
- }
7167
- ) }),
7168
- allColumns.map((col, ci) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "px-4 py-3 align-middle", children: col.render ? col.render(item) : col.type === "image" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7169
- "img",
7170
- {
7171
- src: item[col.key],
7172
- alt: item[col.key],
7173
- className: "h-9 w-9 rounded-lg object-cover ring-1 ring-border"
7174
- }
7175
- ) : col.type === "badge" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: cn(
7176
- "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium",
7177
- badgeClass(String(item[col.key]))
7178
- ), children: [
7179
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn(
7180
- "mr-1.5 h-1.5 w-1.5 rounded-full",
7181
- badgeClass(String(item[col.key])).includes("success") ? "bg-success" : badgeClass(String(item[col.key])).includes("warning") ? "bg-warning" : badgeClass(String(item[col.key])).includes("danger") ? "bg-danger" : badgeClass(String(item[col.key])).includes("info") ? "bg-info" : "bg-primary"
7877
+ const isExpanded = expandedIds.has(id);
7878
+ const isFocused = keyboardNavigation && focusedRowIdx === i;
7879
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(React28.Fragment, { children: [
7880
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7881
+ "tr",
7882
+ {
7883
+ draggable,
7884
+ tabIndex: keyboardNavigation ? 0 : void 0,
7885
+ onDragStart: draggable ? (e) => {
7886
+ e.dataTransfer.setData("text/plain", id);
7887
+ } : void 0,
7888
+ onDragOver: draggable ? (e) => {
7889
+ e.preventDefault();
7890
+ setDragOverId(id);
7891
+ } : void 0,
7892
+ onDragLeave: draggable ? () => setDragOverId(null) : void 0,
7893
+ onDrop: draggable ? (e) => {
7894
+ e.preventDefault();
7895
+ setDragOverId(null);
7896
+ const fromId = e.dataTransfer.getData("text/plain");
7897
+ if (fromId === id) return;
7898
+ setTableData((prev) => {
7899
+ const fromIdx = prev.findIndex((r) => String(r[idKey] || "") === fromId);
7900
+ const toIdx = prev.findIndex((r) => String(r[idKey] || "") === id);
7901
+ if (fromIdx < 0 || toIdx < 0) return prev;
7902
+ const next = [...prev];
7903
+ const [moved] = next.splice(fromIdx, 1);
7904
+ next.splice(toIdx, 0, moved);
7905
+ onRowReorder?.(next);
7906
+ return next;
7907
+ });
7908
+ } : void 0,
7909
+ onClick: () => {
7910
+ if (expandable) setExpandedIds((prev) => {
7911
+ const s = new Set(prev);
7912
+ s.has(id) ? s.delete(id) : s.add(id);
7913
+ return s;
7914
+ });
7915
+ onRowClick?.(item);
7916
+ if (keyboardNavigation) setFocusedRowIdx(i);
7917
+ },
7918
+ onDoubleClick: () => onRowDoubleClick?.(item),
7919
+ className: cn(
7920
+ // default
7921
+ variant === "default" && "border-b border-border/60 transition-colors last:border-0",
7922
+ variant === "default" && (isSelected ? "bg-primary/5 hover:bg-primary/8" : "hover:bg-muted/30"),
7923
+ // zebra
7924
+ variant === "zebra" && "border-b border-border/40 transition-colors last:border-0",
7925
+ variant === "zebra" && (isSelected ? "bg-primary/8" : i % 2 === 0 ? "bg-card" : "bg-muted/40"),
7926
+ variant === "zebra" && !isSelected && "hover:bg-primary/5",
7927
+ // card
7928
+ variant === "card" && "rounded-xl border border-border bg-card shadow-sm transition-all hover:shadow-md hover:-translate-y-px",
7929
+ variant === "card" && (isSelected ? "border-primary/50 bg-primary/5" : ""),
7930
+ variant === "card" && "[&>td:first-child]:rounded-l-xl [&>td:last-child]:rounded-r-xl",
7931
+ // glass
7932
+ variant === "glass" && "border-b border-white/8 transition-colors last:border-0",
7933
+ variant === "glass" && (isSelected ? "bg-primary/15 hover:bg-primary/20" : "hover:bg-white/5"),
7934
+ // soft
7935
+ variant === "soft" && "transition-all",
7936
+ variant === "soft" && (isSelected ? "bg-primary/8 [box-shadow:inset_2px_2px_5px_hsl(var(--foreground)/0.06),inset_-2px_-2px_5px_hsl(var(--background)/0.7)]" : "hover:bg-muted/20"),
7937
+ variant === "soft" && "border-b border-border/30 last:border-0",
7938
+ (onRowClick || onRowDoubleClick || expandable) && "cursor-pointer",
7939
+ draggable && dragOverId === id && "ring-2 ring-inset ring-primary/40",
7940
+ isFocused && "ring-2 ring-inset ring-ring",
7941
+ rowClassName?.(item)
7942
+ ),
7943
+ children: [
7944
+ selectable && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "px-4 py-3 align-middle", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7945
+ Checkbox,
7946
+ {
7947
+ checked: isSelected,
7948
+ onChange: (e) => handleSelect(id, e.target.checked)
7949
+ }
7182
7950
  ) }),
7183
- item[col.key]
7184
- ] }) : col.type === "stack" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(AvatarStack, { images: Array.isArray(item[col.key]) ? item[col.key] : [], ...col.stackProps ?? {} }) : col.type === "icon" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "flex items-center", children: item[col.key] }) : col.type === "select" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7185
- "select",
7186
- {
7187
- value: item[col.key],
7188
- onChange: (e) => col.onChange?.(item, e.target.value),
7189
- className: "h-8 rounded-lg border border-border bg-background/50 px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors",
7190
- children: (col.selectOptions ?? []).map((opt) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: opt, children: opt }, opt))
7191
- }
7192
- ) : col.type === "toggle" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7193
- "button",
7194
- {
7195
- role: "switch",
7196
- "aria-checked": !!item[col.key],
7197
- onClick: () => col.onChange?.(item, !item[col.key]),
7198
- className: cn(
7199
- "relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
7200
- item[col.key] ? "bg-primary" : "bg-muted"
7201
- ),
7202
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn(
7203
- "pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
7204
- item[col.key] ? "translate-x-4" : "translate-x-0"
7205
- ) })
7206
- }
7207
- ) : col.type === "color" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-2", children: [
7208
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7209
- "input",
7951
+ expandable && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "w-8 px-2 py-3 align-middle", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.ChevronRight, { className: cn("h-3.5 w-3.5 text-muted-foreground transition-transform", isExpanded && "rotate-90") }) }),
7952
+ allColumns.map((col, ci) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { className: "px-4 py-3 align-middle", children: col.render ? col.render(item) : col.type === "image" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7953
+ "img",
7954
+ {
7955
+ src: item[col.key],
7956
+ alt: item[col.key],
7957
+ className: "h-9 w-9 rounded-lg object-cover ring-1 ring-border"
7958
+ }
7959
+ ) : col.type === "badge" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: cn(
7960
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium",
7961
+ badgeClass(String(item[col.key]))
7962
+ ), children: [
7963
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn(
7964
+ "mr-1.5 h-1.5 w-1.5 rounded-full",
7965
+ badgeClass(String(item[col.key])).includes("success") ? "bg-success" : badgeClass(String(item[col.key])).includes("warning") ? "bg-warning" : badgeClass(String(item[col.key])).includes("danger") ? "bg-danger" : badgeClass(String(item[col.key])).includes("info") ? "bg-info" : "bg-primary"
7966
+ ) }),
7967
+ item[col.key]
7968
+ ] }) : col.type === "stack" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(AvatarStack, { images: Array.isArray(item[col.key]) ? item[col.key] : [], ...col.stackProps ?? {} }) : col.type === "icon" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "flex items-center", children: item[col.key] }) : col.type === "select" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7969
+ "select",
7210
7970
  {
7211
- type: "color",
7212
- value: item[col.key] || "#000000",
7971
+ value: item[col.key],
7213
7972
  onChange: (e) => col.onChange?.(item, e.target.value),
7214
- className: "h-7 w-7 cursor-pointer rounded border border-border bg-transparent p-0.5"
7973
+ className: "h-8 rounded-lg border border-border bg-background/50 px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors",
7974
+ children: (col.selectOptions ?? []).map((opt) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("option", { value: opt, children: opt }, opt))
7215
7975
  }
7216
- ),
7217
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xs text-muted-foreground font-mono", children: item[col.key] })
7218
- ] }) : col.type === "checkbox" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7219
- Checkbox,
7220
- {
7221
- checked: !!item[col.key],
7222
- onChange: (e) => col.onChange?.(item, e.target.checked)
7223
- }
7224
- ) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-foreground/90", children: item[col.key] }) }, `${String(col.key)}-${ci}`))
7225
- ]
7226
- },
7227
- id
7228
- );
7976
+ ) : col.type === "toggle" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7977
+ "button",
7978
+ {
7979
+ role: "switch",
7980
+ "aria-checked": !!item[col.key],
7981
+ onClick: () => col.onChange?.(item, !item[col.key]),
7982
+ className: cn(
7983
+ "relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
7984
+ item[col.key] ? "bg-primary" : "bg-muted"
7985
+ ),
7986
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: cn(
7987
+ "pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
7988
+ item[col.key] ? "translate-x-4" : "translate-x-0"
7989
+ ) })
7990
+ }
7991
+ ) : col.type === "color" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-2", children: [
7992
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7993
+ "input",
7994
+ {
7995
+ type: "color",
7996
+ value: item[col.key] || "#000000",
7997
+ onChange: (e) => col.onChange?.(item, e.target.value),
7998
+ className: "h-7 w-7 cursor-pointer rounded border border-border bg-transparent p-0.5"
7999
+ }
8000
+ ),
8001
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xs text-muted-foreground font-mono", children: item[col.key] })
8002
+ ] }) : col.type === "checkbox" ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
8003
+ Checkbox,
8004
+ {
8005
+ checked: !!item[col.key],
8006
+ onChange: (e) => col.onChange?.(item, e.target.checked)
8007
+ }
8008
+ ) : col.type === "text-url" ? (() => {
8009
+ const href = col.redirect ? typeof col.redirect === "function" ? col.redirect(item) : col.redirect : String(item[col.key] ?? "");
8010
+ const colorMap = {
8011
+ primary: "var(--primary)",
8012
+ info: "var(--info)",
8013
+ success: "var(--success)",
8014
+ warning: "var(--warning)",
8015
+ danger: "var(--danger)"
8016
+ };
8017
+ const underline = col.underlineColor ? colorMap[col.underlineColor] ?? col.underlineColor : "var(--primary)";
8018
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
8019
+ "a",
8020
+ {
8021
+ href,
8022
+ target: col.openNewTab ? "_blank" : void 0,
8023
+ rel: col.openNewTab ? "noopener noreferrer" : void 0,
8024
+ style: { textDecorationColor: underline },
8025
+ className: "text-sm underline underline-offset-2 hover:opacity-75 transition-opacity break-all",
8026
+ onClick: col.openNewTab ? void 0 : (e) => e.preventDefault(),
8027
+ children: item[col.key]
8028
+ }
8029
+ );
8030
+ })() : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-foreground/90", children: item[col.key] }) }, `${String(col.key)}-${ci}`))
8031
+ ]
8032
+ }
8033
+ ),
8034
+ expandable && isExpanded && renderExpanded && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("tr", { className: "bg-muted/20 border-b border-border/60", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("td", { colSpan: allColumns.length + (selectable ? 1 : 0) + 1, className: "px-6 py-3", children: renderExpanded(item) }) })
8035
+ ] }, id);
7229
8036
  }) })
7230
8037
  ] }) }) }),
7231
- pagination && !serverPagination && totalPages > 1 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
8038
+ clientPagination && !serverPagination && totalPages > 1 && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
7232
8039
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
7233
8040
  "Showing ",
7234
8041
  (safePage - 1) * itemsPerPage + 1,
@@ -7279,8 +8086,8 @@ function Table({
7279
8086
  ] })
7280
8087
  ] }),
7281
8088
  serverPagination && (() => {
7282
- const { pagination: pagination2, currentPage: cp, goToPage } = serverPagination;
7283
- const totalServerPages = pagination2.last_page ?? Math.ceil(pagination2.total / pagination2.per_page);
8089
+ const { pagination, currentPage: cp, goToPage } = serverPagination;
8090
+ const totalServerPages = pagination.last_page ?? Math.ceil(pagination.total / pagination.per_page);
7284
8091
  const pills = [];
7285
8092
  if (totalServerPages <= 7) {
7286
8093
  for (let i = 1; i <= totalServerPages; i++) pills.push(i);
@@ -7293,7 +8100,7 @@ function Table({
7293
8100
  }
7294
8101
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
7295
8102
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
7296
- pagination2.total,
8103
+ pagination.total,
7297
8104
  " total rows \xB7 page ",
7298
8105
  cp,
7299
8106
  " of ",
@@ -7304,7 +8111,7 @@ function Table({
7304
8111
  "button",
7305
8112
  {
7306
8113
  onClick: () => goToPage(cp - 1),
7307
- disabled: !pagination2.prev_page_url,
8114
+ disabled: !pagination.prev_page_url,
7308
8115
  className: "flex h-8 w-8 items-center justify-center rounded-lg border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40 disabled:pointer-events-none",
7309
8116
  children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.ChevronLeft, { className: "h-4 w-4" })
7310
8117
  }
@@ -7327,7 +8134,7 @@ function Table({
7327
8134
  "button",
7328
8135
  {
7329
8136
  onClick: () => goToPage(cp + 1),
7330
- disabled: !pagination2.next_page_url,
8137
+ disabled: !pagination.next_page_url,
7331
8138
  className: "flex h-8 w-8 items-center justify-center rounded-lg border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40 disabled:pointer-events-none",
7332
8139
  children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.ChevronRight, { className: "h-4 w-4" })
7333
8140
  }
@@ -7342,6 +8149,7 @@ function Table({
7342
8149
  item: viewItem,
7343
8150
  fields: viewFields,
7344
8151
  width: defaultActions.modalWidth,
8152
+ grid: defaultActions.viewFormGrid,
7345
8153
  onClose: () => setViewItem(null)
7346
8154
  }
7347
8155
  ),
@@ -7399,6 +8207,37 @@ function Table({
7399
8207
  }
7400
8208
  }
7401
8209
  }
8210
+ ),
8211
+ bulkConfirm && (0, import_react_dom2.createPortal)(
8212
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
8213
+ "div",
8214
+ {
8215
+ className: "fixed inset-0 z-50 flex items-center justify-center p-4",
8216
+ style: { background: "rgba(0,0,0,0.5)" },
8217
+ onMouseDown: (e) => {
8218
+ if (e.target === e.currentTarget) setBulkConfirm(null);
8219
+ },
8220
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "relative w-full max-w-md rounded-2xl border border-border bg-card shadow-2xl flex flex-col", children: [
8221
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center justify-between px-6 py-4 border-b border-border", children: [
8222
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("h2", { className: "text-base font-semibold", children: "Confirm Delete" }),
8223
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { onClick: () => setBulkConfirm(null), className: "text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.X, { className: "h-4 w-4" }) })
8224
+ ] }),
8225
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "px-6 py-4", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-sm text-muted-foreground", children: bulkConfirm === "selected" ? `Are you sure you want to delete ${selectedIds.length} selected record${selectedIds.length !== 1 ? "s" : ""}? This action cannot be undone.` : "Are you sure you want to delete all records? This action cannot be undone." }) }),
8226
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "px-6 py-4 border-t border-border flex justify-end gap-2", children: [
8227
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Button, { variant: "outline", size: "sm", onClick: () => setBulkConfirm(null), disabled: bulkLoading, children: "Cancel" }),
8228
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(Button, { variant: "danger", size: "sm", disabled: bulkLoading, onClick: async () => {
8229
+ if (bulkConfirm === "selected") await execBulkDeleteSelected();
8230
+ else await execDeleteAll();
8231
+ setBulkConfirm(null);
8232
+ }, children: [
8233
+ bulkLoading && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react17.Loader2, { className: "h-3.5 w-3.5 mr-1.5 animate-spin" }),
8234
+ bulkLoading ? "Deleting\u2026" : "Delete"
8235
+ ] })
8236
+ ] })
8237
+ ] })
8238
+ }
8239
+ ),
8240
+ document.body
7402
8241
  )
7403
8242
  ] });
7404
8243
  }
@@ -12853,6 +13692,12 @@ var axiosInstance = import_axios5.default.create({
12853
13692
  Accept: "application/json"
12854
13693
  }
12855
13694
  });
13695
+ axiosInstance.interceptors.request.use((config) => {
13696
+ if (config.data instanceof FormData) {
13697
+ delete config.headers["Content-Type"];
13698
+ }
13699
+ return config;
13700
+ });
12856
13701
 
12857
13702
  // src/lib/codego/request.ts
12858
13703
  var request = async (config) => {