@underverse-ui/underverse 0.2.73 → 0.2.75

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
@@ -594,7 +594,7 @@ var Card = ({
594
594
  {
595
595
  className: cn(
596
596
  "rounded-2xl md:rounded-3xl bg-card text-card-foreground transition-[transform,box-shadow,border-color,background-color] duration-300 ease-soft",
597
- "shadow-sm md:hover:shadow-md mx-2 md:mx-0 border border-border",
597
+ "shadow-sm md:hover:shadow-md border border-border",
598
598
  hoverable && "md:hover:-translate-y-0.5 md:hover:border-primary/15",
599
599
  clickable && "cursor-pointer active:translate-y-px md:hover:bg-accent/5",
600
600
  "backdrop-blur-sm",
@@ -13051,10 +13051,183 @@ TableCell.displayName = "TableCell";
13051
13051
  var TableCaption = import_react31.default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("caption", { ref, className: cn("mt-4 text-sm text-muted-foreground", className), ...props }));
13052
13052
  TableCaption.displayName = "TableCaption";
13053
13053
 
13054
- // ../../components/ui/DataTable.tsx
13054
+ // ../../components/ui/DataTable/DataTable.tsx
13055
13055
  var import_lucide_react27 = require("lucide-react");
13056
- var import_react32 = __toESM(require("react"), 1);
13056
+ var import_react35 = __toESM(require("react"), 1);
13057
+
13058
+ // ../../components/ui/DataTable/components/Pagination.tsx
13057
13059
  var import_jsx_runtime57 = require("react/jsx-runtime");
13060
+ function DataTablePagination({
13061
+ totalItems,
13062
+ curPage,
13063
+ curPageSize,
13064
+ setCurPage,
13065
+ pageSizeOptions,
13066
+ setCurPageSize
13067
+ }) {
13068
+ const totalPages = Math.ceil(totalItems / curPageSize);
13069
+ if (!(totalItems > 0 && totalPages > 1)) return null;
13070
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex items-center justify-between gap-2 px-1 pt-3 text-xs text-muted-foreground", children: [
13071
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "tabular-nums", children: [
13072
+ (curPage - 1) * curPageSize + 1,
13073
+ "-",
13074
+ Math.min(curPage * curPageSize, totalItems),
13075
+ "/",
13076
+ totalItems
13077
+ ] }),
13078
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex items-center gap-0.5", children: [
13079
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13080
+ Button_default,
13081
+ {
13082
+ variant: "ghost",
13083
+ size: "sm",
13084
+ className: "h-7 w-7 p-0 rounded-full",
13085
+ onClick: () => setCurPage(Math.max(1, curPage - 1)),
13086
+ disabled: curPage === 1,
13087
+ children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("svg", { className: "h-4 w-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) })
13088
+ }
13089
+ ),
13090
+ (() => {
13091
+ const pages = [];
13092
+ if (totalPages <= 5) {
13093
+ for (let i = 1; i <= totalPages; i++) pages.push(i);
13094
+ } else {
13095
+ pages.push(1);
13096
+ if (curPage > 3) pages.push("...");
13097
+ const start = Math.max(2, curPage - 1);
13098
+ const end = Math.min(totalPages - 1, curPage + 1);
13099
+ for (let i = start; i <= end; i++) pages.push(i);
13100
+ if (curPage < totalPages - 2) pages.push("...");
13101
+ pages.push(totalPages);
13102
+ }
13103
+ return pages.map(
13104
+ (p, i) => p === "..." ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("span", { className: "px-1 text-muted-foreground/60", children: "\u2026" }, `dots-${i}`) : /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13105
+ "button",
13106
+ {
13107
+ onClick: () => setCurPage(p),
13108
+ className: cn(
13109
+ "h-7 min-w-7 px-2 rounded-full text-xs font-medium transition-colors",
13110
+ curPage === p ? "bg-primary text-primary-foreground" : "hover:bg-accent hover:text-accent-foreground"
13111
+ ),
13112
+ children: p
13113
+ },
13114
+ p
13115
+ )
13116
+ );
13117
+ })(),
13118
+ /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13119
+ Button_default,
13120
+ {
13121
+ variant: "ghost",
13122
+ size: "sm",
13123
+ className: "h-7 w-7 p-0 rounded-full",
13124
+ onClick: () => setCurPage(Math.min(totalPages, curPage + 1)),
13125
+ disabled: curPage === totalPages,
13126
+ children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("svg", { className: "h-4 w-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) })
13127
+ }
13128
+ )
13129
+ ] }),
13130
+ pageSizeOptions && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13131
+ Combobox,
13132
+ {
13133
+ options: pageSizeOptions.map(String),
13134
+ value: String(curPageSize),
13135
+ onChange: (v) => {
13136
+ setCurPage(1);
13137
+ setCurPageSize(Number(v));
13138
+ },
13139
+ size: "sm",
13140
+ className: "w-20"
13141
+ }
13142
+ )
13143
+ ] });
13144
+ }
13145
+
13146
+ // ../../components/ui/DataTable/components/Toolbar.tsx
13147
+ var import_jsx_runtime58 = require("react/jsx-runtime");
13148
+ function DataTableToolbar({
13149
+ caption,
13150
+ toolbar,
13151
+ columns,
13152
+ visibleCols,
13153
+ setVisibleCols,
13154
+ enableDensityToggle,
13155
+ enableColumnVisibilityToggle,
13156
+ enableHeaderAlignToggle,
13157
+ density,
13158
+ setDensity,
13159
+ setHeaderAlign,
13160
+ labels,
13161
+ t
13162
+ }) {
13163
+ return /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)("div", { className: "flex items-center justify-between gap-4 mb-1", children: [
13164
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("div", { className: "text-sm text-muted-foreground", children: caption }),
13165
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)("div", { className: "flex items-center gap-2", children: [
13166
+ enableDensityToggle && /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
13167
+ DropdownMenu_default,
13168
+ {
13169
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(Button_default, { variant: "ghost", size: "sm", className: "h-8 px-2", children: [
13170
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("svg", { className: "w-4 h-4 mr-1", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 10h16M4 14h16M4 18h16" }) }),
13171
+ labels?.density || t("density")
13172
+ ] }),
13173
+ items: [
13174
+ { label: labels?.compact || t("compact"), onClick: () => setDensity("compact") },
13175
+ { label: labels?.normal || t("normal"), onClick: () => setDensity("normal") },
13176
+ { label: labels?.comfortable || t("comfortable"), onClick: () => setDensity("comfortable") }
13177
+ ]
13178
+ }
13179
+ ),
13180
+ enableColumnVisibilityToggle && /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
13181
+ DropdownMenu_default,
13182
+ {
13183
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(Button_default, { variant: "ghost", size: "sm", className: "h-8 px-2", children: [
13184
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("svg", { className: "w-4 h-4 mr-1", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
13185
+ "path",
13186
+ {
13187
+ strokeLinecap: "round",
13188
+ strokeLinejoin: "round",
13189
+ strokeWidth: 2,
13190
+ d: "M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"
13191
+ }
13192
+ ) }),
13193
+ labels?.columns || t("columns")
13194
+ ] }),
13195
+ children: columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(
13196
+ DropdownMenuItem,
13197
+ {
13198
+ onClick: () => {
13199
+ setVisibleCols((prev) => prev.includes(c.key) ? prev.filter((k) => k !== c.key) : [...prev, c.key]);
13200
+ },
13201
+ children: [
13202
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("input", { type: "checkbox", className: "mr-2 rounded-md border-border", readOnly: true, checked: visibleCols.includes(c.key) }),
13203
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("span", { className: "truncate", children: c.title })
13204
+ ]
13205
+ },
13206
+ c.key
13207
+ ))
13208
+ }
13209
+ ),
13210
+ enableHeaderAlignToggle && /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
13211
+ DropdownMenu_default,
13212
+ {
13213
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(Button_default, { variant: "ghost", size: "sm", className: "h-8 px-2", children: [
13214
+ /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("svg", { className: "w-4 h-4 mr-1", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h10M4 18h16" }) }),
13215
+ labels?.headerAlign || t("headerAlign")
13216
+ ] }),
13217
+ items: [
13218
+ { label: labels?.alignLeft || t("alignLeft"), onClick: () => setHeaderAlign("left") },
13219
+ { label: labels?.alignCenter || t("alignCenter"), onClick: () => setHeaderAlign("center") },
13220
+ { label: labels?.alignRight || t("alignRight"), onClick: () => setHeaderAlign("right") }
13221
+ ]
13222
+ }
13223
+ ),
13224
+ toolbar
13225
+ ] })
13226
+ ] });
13227
+ }
13228
+
13229
+ // ../../components/ui/DataTable/hooks/useDebounced.ts
13230
+ var import_react32 = __toESM(require("react"), 1);
13058
13231
  function useDebounced(value, delay = 300) {
13059
13232
  const [debounced, setDebounced] = import_react32.default.useState(value);
13060
13233
  import_react32.default.useEffect(() => {
@@ -13063,40 +13236,12 @@ function useDebounced(value, delay = 300) {
13063
13236
  }, [value, delay]);
13064
13237
  return debounced;
13065
13238
  }
13066
- function DataTable({
13067
- columns,
13068
- data,
13069
- rowKey,
13070
- loading: loading2,
13071
- total = 0,
13072
- page = 1,
13073
- pageSize = 20,
13074
- pageSizeOptions,
13075
- onQueryChange,
13076
- caption,
13077
- toolbar,
13078
- enableColumnVisibilityToggle = true,
13079
- enableDensityToggle = true,
13080
- enableHeaderAlignToggle = false,
13081
- striped = true,
13082
- // Mặc định bật màu nền sẽn kẽ cho các dòng
13083
- columnDividers = false,
13084
- className,
13085
- storageKey,
13086
- stickyHeader = true,
13087
- maxHeight = 500,
13088
- labels
13089
- }) {
13090
- const t = useTranslations("Common");
13091
- const [headerAlign, setHeaderAlign] = import_react32.default.useState("left");
13092
- const [visibleCols, setVisibleCols] = import_react32.default.useState(() => columns.filter((c) => c.visible !== false).map((c) => c.key));
13093
- const [filters, setFilters] = import_react32.default.useState({});
13094
- const [sort, setSort] = import_react32.default.useState(null);
13095
- const [density, setDensity] = import_react32.default.useState("normal");
13096
- const [curPage, setCurPage] = import_react32.default.useState(page);
13097
- const hasMounted = import_react32.default.useRef(false);
13098
- const loadedFromStorage = import_react32.default.useRef(false);
13099
- const [curPageSize, setCurPageSize] = import_react32.default.useState(() => {
13239
+
13240
+ // ../../components/ui/DataTable/hooks/usePageSizeStorage.ts
13241
+ var import_react33 = __toESM(require("react"), 1);
13242
+ function usePageSizeStorage({ pageSize, storageKey }) {
13243
+ const loadedFromStorage = import_react33.default.useRef(false);
13244
+ const [curPageSize, setCurPageSize] = import_react33.default.useState(() => {
13100
13245
  if (typeof window === "undefined" || !storageKey) return pageSize;
13101
13246
  try {
13102
13247
  const saved = localStorage.getItem(`datatable_${storageKey}_pageSize`);
@@ -13111,7 +13256,11 @@ function DataTable({
13111
13256
  }
13112
13257
  return pageSize;
13113
13258
  });
13114
- import_react32.default.useEffect(() => {
13259
+ const hasMounted = import_react33.default.useRef(false);
13260
+ import_react33.default.useEffect(() => {
13261
+ hasMounted.current = true;
13262
+ }, []);
13263
+ import_react33.default.useEffect(() => {
13115
13264
  if (typeof window === "undefined" || !storageKey) return;
13116
13265
  if (!hasMounted.current) return;
13117
13266
  try {
@@ -13119,49 +13268,33 @@ function DataTable({
13119
13268
  } catch {
13120
13269
  }
13121
13270
  }, [curPageSize, storageKey]);
13122
- import_react32.default.useEffect(() => {
13123
- const newColKeys = columns.filter((c) => c.visible !== false).map((c) => c.key);
13124
- setVisibleCols((prev) => {
13125
- const uniqueKeys = /* @__PURE__ */ new Set([...prev, ...newColKeys]);
13126
- return [...uniqueKeys].filter((k) => columns.some((c) => c.key === k));
13127
- });
13128
- }, [columns]);
13129
- const debouncedFilters = useDebounced(filters, 350);
13130
- import_react32.default.useEffect(() => {
13131
- setCurPage(page);
13132
- }, [page]);
13133
- import_react32.default.useEffect(() => {
13134
- if (storageKey && loadedFromStorage.current) {
13135
- return;
13136
- }
13271
+ import_react33.default.useEffect(() => {
13272
+ if (storageKey && loadedFromStorage.current) return;
13137
13273
  setCurPageSize(pageSize);
13138
13274
  }, [pageSize, storageKey]);
13139
- import_react32.default.useEffect(() => {
13140
- if (!onQueryChange) return;
13141
- if (!hasMounted.current) {
13142
- hasMounted.current = true;
13143
- return;
13144
- }
13145
- onQueryChange({ filters: debouncedFilters, sort, page: curPage, pageSize: curPageSize });
13146
- }, [debouncedFilters, sort, curPage, curPageSize]);
13147
- const densityRowClass = density === "compact" ? "h-9" : density === "comfortable" ? "h-14" : "h-12";
13148
- const cellPadding = density === "compact" ? "py-1.5 px-3" : density === "comfortable" ? "py-3 px-4" : "py-2.5 px-4";
13149
- const visibleColsSet = import_react32.default.useMemo(() => new Set(visibleCols), [visibleCols]);
13150
- const visibleColumns = columns.filter((c) => visibleColsSet.has(c.key));
13151
- const totalColumnsWidth = import_react32.default.useMemo(() => {
13152
- return visibleColumns.reduce((sum, col) => {
13153
- const colWidth = typeof col.width === "number" ? col.width : parseInt(String(col.width) || "150", 10);
13154
- return sum + colWidth;
13155
- }, 0);
13156
- }, [visibleColumns]);
13157
- const stickyPositions = import_react32.default.useMemo(() => {
13275
+ return { curPageSize, setCurPageSize, loadedFromStorage };
13276
+ }
13277
+
13278
+ // ../../components/ui/DataTable/hooks/useStickyColumns.ts
13279
+ var import_react34 = __toESM(require("react"), 1);
13280
+
13281
+ // ../../components/ui/DataTable/utils/columns.ts
13282
+ function getColumnWidth(col, fallback = 150) {
13283
+ if (typeof col.width === "number") return col.width;
13284
+ const raw = col.width ? String(col.width) : String(fallback);
13285
+ const parsed = parseInt(raw, 10);
13286
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
13287
+ }
13288
+
13289
+ // ../../components/ui/DataTable/hooks/useStickyColumns.ts
13290
+ function useStickyColumns(visibleColumns) {
13291
+ const stickyPositions = import_react34.default.useMemo(() => {
13158
13292
  const positions = {};
13159
13293
  let leftOffset = 0;
13160
13294
  for (const col of visibleColumns) {
13161
13295
  if (col.fixed === "left") {
13162
13296
  positions[col.key] = { left: leftOffset };
13163
- const colWidth = typeof col.width === "number" ? col.width : parseInt(String(col.width) || "150", 10);
13164
- leftOffset += colWidth;
13297
+ leftOffset += getColumnWidth(col);
13165
13298
  }
13166
13299
  }
13167
13300
  let rightOffset = 0;
@@ -13169,30 +13302,105 @@ function DataTable({
13169
13302
  const col = visibleColumns[i];
13170
13303
  if (col.fixed === "right") {
13171
13304
  positions[col.key] = { right: rightOffset };
13172
- const colWidth = typeof col.width === "number" ? col.width : parseInt(String(col.width) || "150", 10);
13173
- rightOffset += colWidth;
13305
+ rightOffset += getColumnWidth(col);
13174
13306
  }
13175
13307
  }
13176
13308
  return positions;
13177
13309
  }, [visibleColumns]);
13178
- const getStickyColumnClass = (col, isHeader = false) => {
13310
+ const getStickyColumnStyle = import_react34.default.useCallback(
13311
+ (col) => {
13312
+ if (!col.fixed) return {};
13313
+ const pos = stickyPositions[col.key];
13314
+ return {
13315
+ ...pos?.left !== void 0 && { left: pos.left },
13316
+ ...pos?.right !== void 0 && { right: pos.right }
13317
+ };
13318
+ },
13319
+ [stickyPositions]
13320
+ );
13321
+ const getStickyHeaderClass = import_react34.default.useCallback((col) => {
13179
13322
  if (!col.fixed) return "";
13180
13323
  return cn(
13181
13324
  "sticky",
13182
13325
  col.fixed === "left" && "left-0 shadow-[2px_0_5px_-2px_rgba(0,0,0,0.15)]",
13183
13326
  col.fixed === "right" && "right-0 shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.15)]",
13184
- // Header fixed column cần z-index cao hơn thead (z-30) để không bị che
13185
- isHeader ? "z-50 bg-muted!" : "z-10 bg-card!"
13327
+ "z-50 bg-muted!"
13186
13328
  );
13187
- };
13188
- const getStickyColumnStyle = (col) => {
13189
- if (!col.fixed) return {};
13190
- const pos = stickyPositions[col.key];
13191
- return {
13192
- ...pos?.left !== void 0 && { left: pos.left },
13193
- ...pos?.right !== void 0 && { right: pos.right }
13194
- };
13195
- };
13329
+ }, []);
13330
+ const getStickyCellClass = import_react34.default.useCallback((col, isStripedRow) => {
13331
+ if (!col.fixed) return "";
13332
+ return cn(
13333
+ "sticky z-10",
13334
+ col.fixed === "left" && "left-0 shadow-[2px_0_5px_-2px_rgba(0,0,0,0.15)]",
13335
+ col.fixed === "right" && "right-0 shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.15)]",
13336
+ isStripedRow ? "bg-muted!" : "bg-card!"
13337
+ );
13338
+ }, []);
13339
+ return { getStickyColumnStyle, getStickyHeaderClass, getStickyCellClass };
13340
+ }
13341
+
13342
+ // ../../components/ui/DataTable/DataTable.tsx
13343
+ var import_jsx_runtime59 = require("react/jsx-runtime");
13344
+ function DataTable({
13345
+ columns,
13346
+ data,
13347
+ rowKey,
13348
+ loading: loading2,
13349
+ total = 0,
13350
+ page = 1,
13351
+ pageSize = 20,
13352
+ pageSizeOptions,
13353
+ onQueryChange,
13354
+ caption,
13355
+ toolbar,
13356
+ enableColumnVisibilityToggle = true,
13357
+ enableDensityToggle = true,
13358
+ enableHeaderAlignToggle = false,
13359
+ striped = true,
13360
+ columnDividers = false,
13361
+ className,
13362
+ storageKey,
13363
+ stickyHeader = true,
13364
+ maxHeight = 500,
13365
+ labels
13366
+ }) {
13367
+ const t = useTranslations("Common");
13368
+ const [headerAlign, setHeaderAlign] = import_react35.default.useState("left");
13369
+ const [visibleCols, setVisibleCols] = import_react35.default.useState(() => columns.filter((c) => c.visible !== false).map((c) => c.key));
13370
+ const [filters, setFilters] = import_react35.default.useState({});
13371
+ const [sort, setSort] = import_react35.default.useState(null);
13372
+ const [density, setDensity] = import_react35.default.useState("normal");
13373
+ const [curPage, setCurPage] = import_react35.default.useState(page);
13374
+ const { curPageSize, setCurPageSize } = usePageSizeStorage({ pageSize, storageKey });
13375
+ import_react35.default.useEffect(() => {
13376
+ const newColKeys = columns.filter((c) => c.visible !== false).map((c) => c.key);
13377
+ setVisibleCols((prev) => {
13378
+ const uniqueKeys = /* @__PURE__ */ new Set([...prev, ...newColKeys]);
13379
+ return [...uniqueKeys].filter((k) => columns.some((c) => c.key === k));
13380
+ });
13381
+ }, [columns]);
13382
+ const debouncedFilters = useDebounced(filters, 350);
13383
+ import_react35.default.useEffect(() => {
13384
+ setCurPage(page);
13385
+ }, [page]);
13386
+ const isServerMode = Boolean(onQueryChange);
13387
+ const hasEmittedQuery = import_react35.default.useRef(false);
13388
+ import_react35.default.useEffect(() => {
13389
+ if (!onQueryChange) return;
13390
+ if (!hasEmittedQuery.current) {
13391
+ hasEmittedQuery.current = true;
13392
+ return;
13393
+ }
13394
+ onQueryChange({ filters: debouncedFilters, sort, page: curPage, pageSize: curPageSize });
13395
+ }, [debouncedFilters, sort, curPage, curPageSize, onQueryChange]);
13396
+ const densityRowClass = density === "compact" ? "h-9" : density === "comfortable" ? "h-14" : "h-12";
13397
+ const cellPadding = density === "compact" ? "py-1.5 px-3" : density === "comfortable" ? "py-3 px-4" : "py-2.5 px-4";
13398
+ const visibleColsSet = import_react35.default.useMemo(() => new Set(visibleCols), [visibleCols]);
13399
+ const visibleColumns = columns.filter((c) => visibleColsSet.has(c.key));
13400
+ const totalColumnsWidth = import_react35.default.useMemo(() => {
13401
+ return visibleColumns.reduce((sum, col) => sum + getColumnWidth(col), 0);
13402
+ }, [visibleColumns]);
13403
+ const { getStickyCellClass, getStickyColumnStyle, getStickyHeaderClass } = useStickyColumns(visibleColumns);
13196
13404
  const getRowKey = (row, idx) => {
13197
13405
  if (!rowKey) return String(idx);
13198
13406
  if (typeof rowKey === "function") return String(rowKey(row));
@@ -13201,11 +13409,9 @@ function DataTable({
13201
13409
  const renderFilterControl = (col) => {
13202
13410
  if (!col.filter) return null;
13203
13411
  const k = col.key;
13204
- const commonProps = {
13205
- className: "h-8 w-full text-sm"
13206
- };
13412
+ const commonProps = { className: "h-8 w-full text-sm" };
13207
13413
  if (col.filter.type === "text") {
13208
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13414
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13209
13415
  Input_default,
13210
13416
  {
13211
13417
  ...commonProps,
@@ -13220,7 +13426,7 @@ function DataTable({
13220
13426
  }
13221
13427
  if (col.filter.type === "select") {
13222
13428
  const options = col.filter.options || [];
13223
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13429
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13224
13430
  Combobox,
13225
13431
  {
13226
13432
  options: ["", ...options],
@@ -13236,7 +13442,7 @@ function DataTable({
13236
13442
  );
13237
13443
  }
13238
13444
  if (col.filter.type === "date") {
13239
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13445
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13240
13446
  DatePicker,
13241
13447
  {
13242
13448
  placeholder: col.filter.placeholder || `Select ${String(col.title)}`,
@@ -13250,122 +13456,105 @@ function DataTable({
13250
13456
  }
13251
13457
  return null;
13252
13458
  };
13253
- const renderHeader = /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(TableRow, { children: visibleColumns.map((col, colIdx) => {
13459
+ const renderHeader = /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(TableRow, { children: visibleColumns.map((col, colIdx) => {
13254
13460
  const prevCol = colIdx > 0 ? visibleColumns[colIdx - 1] : null;
13255
13461
  const isAfterFixedLeft = prevCol?.fixed === "left";
13256
13462
  const showBorderLeft = columnDividers && colIdx > 0 && !isAfterFixedLeft && !col.fixed;
13257
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13463
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13258
13464
  TableHead,
13259
13465
  {
13260
13466
  style: { width: col.width, ...getStickyColumnStyle(col) },
13261
13467
  className: cn(
13262
- // Use column-specific align if defined, otherwise use global headerAlign
13263
13468
  (col.align === "right" || !col.align && headerAlign === "right") && "text-right",
13264
13469
  (col.align === "center" || !col.align && headerAlign === "center") && "text-center",
13265
13470
  showBorderLeft && "border-l border-border/60",
13266
- getStickyColumnClass(col, true)
13471
+ getStickyHeaderClass(col)
13267
13472
  ),
13268
13473
  children: (() => {
13269
13474
  const isRightAlign = col.align === "right" || !col.align && headerAlign === "right";
13270
13475
  const isCenterAlign = col.align === "center" || !col.align && headerAlign === "center";
13271
- const titleContent = /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
13272
- "div",
13273
- {
13274
- className: cn(
13275
- "flex items-center gap-1",
13276
- // Cột fixed không cần shrink vì đã có width cố định
13277
- !col.fixed && "min-w-0 shrink"
13278
- ),
13279
- children: [
13280
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("span", { className: cn("font-medium text-sm", !col.fixed && "truncate"), children: col.title }),
13281
- col.sortable && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13282
- "button",
13283
- {
13284
- className: cn(
13285
- "p-1 rounded-lg transition-all duration-200 hover:bg-accent",
13286
- sort?.key === col.key ? "opacity-100 bg-accent" : "opacity-60 hover:opacity-100"
13287
- ),
13288
- onClick: () => {
13289
- setCurPage(1);
13290
- setSort((s) => {
13291
- if (!s || s.key !== col.key) return { key: col.key, order: "asc" };
13292
- if (s.order === "asc") return { key: col.key, order: "desc" };
13293
- return null;
13294
- });
13295
- },
13296
- "aria-label": "Sort",
13297
- title: `Sort by ${String(col.title)}`,
13298
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", className: "inline-block", children: [
13299
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13300
- "path",
13301
- {
13302
- d: "M7 8l3-3 3 3",
13303
- stroke: "currentColor",
13304
- strokeWidth: "1.5",
13305
- strokeLinecap: "round",
13306
- strokeLinejoin: "round",
13307
- opacity: sort?.key === col.key && sort.order === "asc" ? 1 : 0.4
13308
- }
13309
- ),
13310
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13311
- "path",
13312
- {
13313
- d: "M7 12l3 3 3-3",
13314
- stroke: "currentColor",
13315
- strokeWidth: "1.5",
13316
- strokeLinecap: "round",
13317
- strokeLinejoin: "round",
13318
- opacity: sort?.key === col.key && sort.order === "desc" ? 1 : 0.4
13319
- }
13320
- )
13321
- ] })
13322
- }
13323
- )
13324
- ]
13325
- }
13326
- );
13327
- const filterContent = col.filter && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13476
+ const titleContent = /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: cn("flex items-center gap-1", !col.fixed && "min-w-0 shrink"), children: [
13477
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("span", { className: cn("font-medium text-sm", !col.fixed && "truncate"), children: col.title }),
13478
+ col.sortable && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13479
+ "button",
13480
+ {
13481
+ className: cn(
13482
+ "p-1 rounded-lg transition-all duration-200 hover:bg-accent",
13483
+ sort?.key === col.key ? "opacity-100 bg-accent" : "opacity-60 hover:opacity-100"
13484
+ ),
13485
+ onClick: () => {
13486
+ setCurPage(1);
13487
+ setSort((s) => {
13488
+ if (!s || s.key !== col.key) return { key: col.key, order: "asc" };
13489
+ if (s.order === "asc") return { key: col.key, order: "desc" };
13490
+ return null;
13491
+ });
13492
+ },
13493
+ "aria-label": "Sort",
13494
+ title: `Sort by ${String(col.title)}`,
13495
+ children: /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", className: "inline-block", children: [
13496
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13497
+ "path",
13498
+ {
13499
+ d: "M7 8l3-3 3 3",
13500
+ stroke: "currentColor",
13501
+ strokeWidth: "1.5",
13502
+ strokeLinecap: "round",
13503
+ strokeLinejoin: "round",
13504
+ opacity: sort?.key === col.key && sort.order === "asc" ? 1 : 0.4
13505
+ }
13506
+ ),
13507
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13508
+ "path",
13509
+ {
13510
+ d: "M7 12l3 3 3-3",
13511
+ stroke: "currentColor",
13512
+ strokeWidth: "1.5",
13513
+ strokeLinecap: "round",
13514
+ strokeLinejoin: "round",
13515
+ opacity: sort?.key === col.key && sort.order === "desc" ? 1 : 0.4
13516
+ }
13517
+ )
13518
+ ] })
13519
+ }
13520
+ )
13521
+ ] });
13522
+ const filterContent = col.filter ? /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13328
13523
  Popover,
13329
13524
  {
13330
- placement: isRightAlign ? "bottom-end" : "bottom-start",
13331
- trigger: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13525
+ placement: "bottom-start",
13526
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13332
13527
  "button",
13333
13528
  {
13334
13529
  className: cn(
13335
- "p-1.5 rounded-lg hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
13336
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
13337
- filters[col.key] && "bg-accent text-foreground"
13530
+ "p-1.5 rounded-lg transition-all duration-200 hover:bg-accent",
13531
+ filters[col.key] ? "bg-accent text-primary" : "text-muted-foreground"
13338
13532
  ),
13339
13533
  "aria-label": "Filter",
13340
- title: "Filter",
13341
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_lucide_react27.Filter, { className: "h-4 w-4" })
13534
+ title: `Filter by ${String(col.title)}`,
13535
+ children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_lucide_react27.Filter, { className: "w-4 h-4" })
13342
13536
  }
13343
13537
  ),
13344
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "w-48 p-2 space-y-2", children: [
13345
- /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "text-xs font-medium text-muted-foreground mb-2", children: [
13346
- "Filter ",
13347
- col.title
13538
+ children: /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "p-3 w-64 space-y-3", children: [
13539
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "flex items-center justify-between", children: [
13540
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("div", { className: "text-sm font-medium", children: col.title }),
13541
+ filters[col.key] !== void 0 && filters[col.key] !== null && filters[col.key] !== "" && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13542
+ "button",
13543
+ {
13544
+ onClick: () => {
13545
+ setCurPage(1);
13546
+ setFilters((f) => ({ ...f, [col.key]: void 0 }));
13547
+ },
13548
+ className: "text-xs text-destructive hover:underline",
13549
+ children: t("clearFilter")
13550
+ }
13551
+ )
13348
13552
  ] }),
13349
- renderFilterControl(col),
13350
- filters[col.key] && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13351
- "button",
13352
- {
13353
- onClick: () => {
13354
- setCurPage(1);
13355
- setFilters((f) => {
13356
- const newFilters = { ...f };
13357
- delete newFilters[col.key];
13358
- return newFilters;
13359
- });
13360
- },
13361
- className: "text-xs text-destructive hover:underline",
13362
- children: t("clearFilter")
13363
- }
13364
- )
13553
+ renderFilterControl(col)
13365
13554
  ] })
13366
13555
  }
13367
- );
13368
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13556
+ ) : null;
13557
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13369
13558
  "div",
13370
13559
  {
13371
13560
  className: cn(
@@ -13374,10 +13563,10 @@ function DataTable({
13374
13563
  isCenterAlign && "justify-center",
13375
13564
  !isRightAlign && !isCenterAlign && "justify-start"
13376
13565
  ),
13377
- children: isRightAlign ? /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_jsx_runtime57.Fragment, { children: [
13566
+ children: isRightAlign ? /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(import_jsx_runtime59.Fragment, { children: [
13378
13567
  filterContent,
13379
13568
  titleContent
13380
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_jsx_runtime57.Fragment, { children: [
13569
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(import_jsx_runtime59.Fragment, { children: [
13381
13570
  titleContent,
13382
13571
  filterContent
13383
13572
  ] })
@@ -13388,13 +13577,12 @@ function DataTable({
13388
13577
  col.key
13389
13578
  );
13390
13579
  }) });
13391
- const isServerMode = Boolean(onQueryChange);
13392
- const processedData = import_react32.default.useMemo(() => {
13580
+ const processedData = import_react35.default.useMemo(() => {
13393
13581
  if (isServerMode) return data;
13394
13582
  let result = [...data];
13395
13583
  if (Object.keys(filters).length > 0) {
13396
- result = result.filter((row) => {
13397
- return Object.entries(filters).every(([key, value]) => {
13584
+ result = result.filter(
13585
+ (row) => Object.entries(filters).every(([key, value]) => {
13398
13586
  if (value === void 0 || value === null || value === "") return true;
13399
13587
  const col = columns.find((c) => c.key === key);
13400
13588
  const rowValue = col?.dataIndex ? row[col.dataIndex] : row[key];
@@ -13402,8 +13590,8 @@ function DataTable({
13402
13590
  return new Date(rowValue).toDateString() === value.toDateString();
13403
13591
  }
13404
13592
  return String(rowValue ?? "").toLowerCase().includes(String(value).toLowerCase());
13405
- });
13406
- });
13593
+ })
13594
+ );
13407
13595
  }
13408
13596
  if (sort) {
13409
13597
  result.sort((a, b) => {
@@ -13421,78 +13609,30 @@ function DataTable({
13421
13609
  return result;
13422
13610
  }, [data, isServerMode, filters, sort, columns]);
13423
13611
  const totalItems = isServerMode ? total : processedData.length;
13424
- const displayedData = isServerMode ? data : import_react32.default.useMemo(() => {
13612
+ const displayedData = isServerMode ? data : import_react35.default.useMemo(() => {
13425
13613
  const start = (curPage - 1) * curPageSize;
13426
- if (start >= processedData.length && curPage > 1) {
13427
- }
13428
13614
  return processedData.slice(start, start + curPageSize);
13429
13615
  }, [processedData, curPage, curPageSize]);
13430
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: cn("space-y-2", className), children: [
13431
- /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex items-center justify-between gap-4 mb-1", children: [
13432
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("div", { className: "text-sm text-muted-foreground", children: caption }),
13433
- /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex items-center gap-2", children: [
13434
- enableDensityToggle && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13435
- DropdownMenu_default,
13436
- {
13437
- trigger: /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(Button_default, { variant: "ghost", size: "sm", className: "h-8 px-2", children: [
13438
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("svg", { className: "w-4 h-4 mr-1", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 10h16M4 14h16M4 18h16" }) }),
13439
- labels?.density || t("density")
13440
- ] }),
13441
- items: [
13442
- { label: labels?.compact || t("compact"), onClick: () => setDensity("compact") },
13443
- { label: labels?.normal || t("normal"), onClick: () => setDensity("normal") },
13444
- { label: labels?.comfortable || t("comfortable"), onClick: () => setDensity("comfortable") }
13445
- ]
13446
- }
13447
- ),
13448
- enableColumnVisibilityToggle && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13449
- DropdownMenu_default,
13450
- {
13451
- trigger: /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(Button_default, { variant: "ghost", size: "sm", className: "h-8 px-2", children: [
13452
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("svg", { className: "w-4 h-4 mr-1", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13453
- "path",
13454
- {
13455
- strokeLinecap: "round",
13456
- strokeLinejoin: "round",
13457
- strokeWidth: 2,
13458
- d: "M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"
13459
- }
13460
- ) }),
13461
- labels?.columns || t("columns")
13462
- ] }),
13463
- children: columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
13464
- DropdownMenuItem,
13465
- {
13466
- onClick: () => {
13467
- setVisibleCols((prev) => prev.includes(c.key) ? prev.filter((k) => k !== c.key) : [...prev, c.key]);
13468
- },
13469
- children: [
13470
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("input", { type: "checkbox", className: "mr-2 rounded-md border-border", readOnly: true, checked: visibleCols.includes(c.key) }),
13471
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("span", { className: "truncate", children: c.title })
13472
- ]
13473
- },
13474
- c.key
13475
- ))
13476
- }
13477
- ),
13478
- enableHeaderAlignToggle && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13479
- DropdownMenu_default,
13480
- {
13481
- trigger: /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(Button_default, { variant: "ghost", size: "sm", className: "h-8 px-2", children: [
13482
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("svg", { className: "w-4 h-4 mr-1", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h10M4 18h16" }) }),
13483
- labels?.headerAlign || t("headerAlign")
13484
- ] }),
13485
- items: [
13486
- { label: labels?.alignLeft || t("alignLeft"), onClick: () => setHeaderAlign("left") },
13487
- { label: labels?.alignCenter || t("alignCenter"), onClick: () => setHeaderAlign("center") },
13488
- { label: labels?.alignRight || t("alignRight"), onClick: () => setHeaderAlign("right") }
13489
- ]
13490
- }
13491
- ),
13492
- toolbar
13493
- ] })
13494
- ] }),
13495
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13616
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: cn("space-y-2", className), children: [
13617
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13618
+ DataTableToolbar,
13619
+ {
13620
+ caption,
13621
+ toolbar,
13622
+ columns,
13623
+ visibleCols,
13624
+ setVisibleCols,
13625
+ enableDensityToggle,
13626
+ enableColumnVisibilityToggle,
13627
+ enableHeaderAlignToggle,
13628
+ density,
13629
+ setDensity,
13630
+ setHeaderAlign,
13631
+ labels,
13632
+ t
13633
+ }
13634
+ ),
13635
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13496
13636
  "div",
13497
13637
  {
13498
13638
  className: cn("relative rounded-2xl md:rounded-3xl border border-border/50", loading2 && "opacity-60 pointer-events-none"),
@@ -13501,7 +13641,7 @@ function DataTable({
13501
13641
  overflowY: "auto",
13502
13642
  overflowX: "auto"
13503
13643
  } : { overflowX: "auto" },
13504
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
13644
+ children: /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(
13505
13645
  Table,
13506
13646
  {
13507
13647
  containerClassName: cn("border-0 md:border-0 rounded-none md:rounded-none shadow-none bg-transparent", "overflow-visible"),
@@ -13511,11 +13651,11 @@ function DataTable({
13511
13651
  ),
13512
13652
  style: { minWidth: totalColumnsWidth > 0 ? `${totalColumnsWidth}px` : void 0 },
13513
13653
  children: [
13514
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(TableHeader, { children: renderHeader }),
13515
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(TableBody, { children: loading2 ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(TableCell, { colSpan: visibleColumns.length, className: "text-center py-8", children: /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex items-center justify-center gap-2 text-muted-foreground", children: [
13516
- /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("svg", { className: "animate-spin h-4 w-4", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
13517
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
13518
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13654
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(TableHeader, { children: renderHeader }),
13655
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(TableBody, { children: loading2 ? /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(TableCell, { colSpan: visibleColumns.length, className: "text-center py-8", children: /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "flex items-center justify-center gap-2 text-muted-foreground", children: [
13656
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("svg", { className: "animate-spin h-4 w-4", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
13657
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
13658
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13519
13659
  "path",
13520
13660
  {
13521
13661
  className: "opacity-75",
@@ -13524,18 +13664,17 @@ function DataTable({
13524
13664
  }
13525
13665
  )
13526
13666
  ] }),
13527
- /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("span", { className: "text-sm", children: [
13667
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("span", { className: "text-sm", children: [
13528
13668
  t("loading"),
13529
13669
  "\u2026"
13530
13670
  ] })
13531
- ] }) }) }) : !displayedData || displayedData.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(TableCell, { colSpan: visibleColumns.length, className: "text-center py-6 text-muted-foreground", children: t("noData") }) }) : displayedData.map((row, idx) => {
13671
+ ] }) }) }) : !displayedData || displayedData.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(TableCell, { colSpan: visibleColumns.length, className: "text-center py-6 text-muted-foreground", children: t("noData") }) }) : displayedData.map((row, idx) => {
13532
13672
  const isLastRow = idx === displayedData.length - 1;
13533
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13673
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13534
13674
  TableRow,
13535
13675
  {
13536
13676
  className: cn(densityRowClass),
13537
13677
  style: {
13538
- // content-visibility: auto for rendering performance (skip off-screen rows)
13539
13678
  contentVisibility: "auto",
13540
13679
  containIntrinsicSize: density === "compact" ? "0 36px" : density === "comfortable" ? "0 56px" : "0 48px"
13541
13680
  },
@@ -13545,7 +13684,7 @@ function DataTable({
13545
13684
  const prevCol = colIdx > 0 ? visibleColumns[colIdx - 1] : null;
13546
13685
  const isAfterFixedLeft = prevCol?.fixed === "left";
13547
13686
  const showBorderLeft = columnDividers && colIdx > 0 && !isAfterFixedLeft && !col.fixed;
13548
- return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13687
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13549
13688
  TableCell,
13550
13689
  {
13551
13690
  style: getStickyColumnStyle(col),
@@ -13556,13 +13695,8 @@ function DataTable({
13556
13695
  showBorderLeft && "border-l border-border/60",
13557
13696
  isLastRow && col === visibleColumns[0] && "rounded-bl-2xl md:rounded-bl-3xl",
13558
13697
  isLastRow && col === visibleColumns[visibleColumns.length - 1] && "rounded-br-2xl md:rounded-br-3xl",
13559
- // Cột cố định cần solid background
13560
- col.fixed ? cn(
13561
- "sticky z-10",
13562
- col.fixed === "left" && "left-0 shadow-[2px_0_5px_-2px_rgba(0,0,0,0.15)]",
13563
- col.fixed === "right" && "right-0 shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.15)]",
13564
- isStripedRow ? "bg-muted!" : "bg-card!"
13565
- ) : isStripedRow && "bg-muted/50"
13698
+ getStickyCellClass(col, isStripedRow),
13699
+ !col.fixed && isStripedRow && "bg-muted/50"
13566
13700
  ),
13567
13701
  children: col.render ? col.render(value, row, idx) : String(value ?? "")
13568
13702
  },
@@ -13578,90 +13712,26 @@ function DataTable({
13578
13712
  )
13579
13713
  }
13580
13714
  ),
13581
- totalItems > 0 && Math.ceil(totalItems / curPageSize) > 1 && /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex items-center justify-between gap-2 px-1 pt-3 text-xs text-muted-foreground", children: [
13582
- /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "tabular-nums", children: [
13583
- (curPage - 1) * curPageSize + 1,
13584
- "-",
13585
- Math.min(curPage * curPageSize, totalItems),
13586
- "/",
13587
- totalItems
13588
- ] }),
13589
- /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex items-center gap-0.5", children: [
13590
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13591
- Button_default,
13592
- {
13593
- variant: "ghost",
13594
- size: "sm",
13595
- className: "h-7 w-7 p-0 rounded-full",
13596
- onClick: () => setCurPage(Math.max(1, curPage - 1)),
13597
- disabled: curPage === 1,
13598
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("svg", { className: "h-4 w-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) })
13599
- }
13600
- ),
13601
- (() => {
13602
- const totalPages = Math.ceil(totalItems / curPageSize);
13603
- const pages = [];
13604
- if (totalPages <= 5) {
13605
- for (let i = 1; i <= totalPages; i++) pages.push(i);
13606
- } else {
13607
- pages.push(1);
13608
- if (curPage > 3) pages.push("...");
13609
- const start = Math.max(2, curPage - 1);
13610
- const end = Math.min(totalPages - 1, curPage + 1);
13611
- for (let i = start; i <= end; i++) pages.push(i);
13612
- if (curPage < totalPages - 2) pages.push("...");
13613
- pages.push(totalPages);
13614
- }
13615
- return pages.map(
13616
- (p, i) => p === "..." ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("span", { className: "px-1 text-muted-foreground/60", children: "\u2026" }, `dots-${i}`) : /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13617
- "button",
13618
- {
13619
- onClick: () => setCurPage(p),
13620
- className: cn(
13621
- "h-7 min-w-7 px-2 rounded-full text-xs font-medium transition-colors",
13622
- curPage === p ? "bg-primary text-primary-foreground" : "hover:bg-accent hover:text-accent-foreground"
13623
- ),
13624
- children: p
13625
- },
13626
- p
13627
- )
13628
- );
13629
- })(),
13630
- /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13631
- Button_default,
13632
- {
13633
- variant: "ghost",
13634
- size: "sm",
13635
- className: "h-7 w-7 p-0 rounded-full",
13636
- onClick: () => setCurPage(Math.min(Math.ceil(totalItems / curPageSize), curPage + 1)),
13637
- disabled: curPage === Math.ceil(totalItems / curPageSize),
13638
- children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("svg", { className: "h-4 w-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) })
13639
- }
13640
- )
13641
- ] }),
13642
- pageSizeOptions && /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
13643
- Combobox,
13644
- {
13645
- options: pageSizeOptions.map(String),
13646
- value: String(curPageSize),
13647
- onChange: (v) => {
13648
- setCurPage(1);
13649
- setCurPageSize(Number(v));
13650
- },
13651
- size: "sm",
13652
- className: "w-20"
13653
- }
13654
- )
13655
- ] })
13715
+ /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
13716
+ DataTablePagination,
13717
+ {
13718
+ totalItems,
13719
+ curPage,
13720
+ curPageSize,
13721
+ setCurPage,
13722
+ pageSizeOptions,
13723
+ setCurPageSize
13724
+ }
13725
+ )
13656
13726
  ] });
13657
13727
  }
13658
13728
  var DataTable_default = DataTable;
13659
13729
 
13660
13730
  // ../../components/ui/Form.tsx
13661
- var React49 = __toESM(require("react"), 1);
13731
+ var React52 = __toESM(require("react"), 1);
13662
13732
  var import_react_hook_form = require("react-hook-form");
13663
- var import_jsx_runtime58 = require("react/jsx-runtime");
13664
- var FormConfigContext = React49.createContext({ size: "md" });
13733
+ var import_jsx_runtime60 = require("react/jsx-runtime");
13734
+ var FormConfigContext = React52.createContext({ size: "md" });
13665
13735
  var FormWrapper = ({
13666
13736
  children,
13667
13737
  onSubmit,
@@ -13674,24 +13744,24 @@ var FormWrapper = ({
13674
13744
  const methods = (0, import_react_hook_form.useForm)({
13675
13745
  defaultValues: initialValues
13676
13746
  });
13677
- React49.useEffect(() => {
13747
+ React52.useEffect(() => {
13678
13748
  if (initialValues) {
13679
13749
  methods.reset(initialValues);
13680
13750
  }
13681
13751
  }, [JSON.stringify(initialValues)]);
13682
13752
  const { validationSchema: _, ...formProps } = props;
13683
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_react_hook_form.FormProvider, { ...methods, children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(FormConfigContext.Provider, { value: { size }, children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("form", { onSubmit: methods.handleSubmit(onSubmit), className, ...formProps, children }) }) });
13753
+ return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_react_hook_form.FormProvider, { ...methods, children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FormConfigContext.Provider, { value: { size }, children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("form", { onSubmit: methods.handleSubmit(onSubmit), className, ...formProps, children }) }) });
13684
13754
  };
13685
13755
  var Form = FormWrapper;
13686
- var FormFieldContext = React49.createContext({});
13756
+ var FormFieldContext = React52.createContext({});
13687
13757
  var FormField = ({
13688
13758
  ...props
13689
13759
  }) => {
13690
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(FormFieldContext.Provider, { value: { name: props.name }, children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_react_hook_form.Controller, { ...props }) });
13760
+ return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FormFieldContext.Provider, { value: { name: props.name }, children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_react_hook_form.Controller, { ...props }) });
13691
13761
  };
13692
13762
  var useFormField = () => {
13693
- const fieldContext = React49.useContext(FormFieldContext);
13694
- const itemContext = React49.useContext(FormItemContext);
13763
+ const fieldContext = React52.useContext(FormFieldContext);
13764
+ const itemContext = React52.useContext(FormItemContext);
13695
13765
  const { getFieldState, formState } = (0, import_react_hook_form.useFormContext)();
13696
13766
  if (!fieldContext) {
13697
13767
  try {
@@ -13712,27 +13782,27 @@ var useFormField = () => {
13712
13782
  ...fieldState
13713
13783
  };
13714
13784
  };
13715
- var FormItemContext = React49.createContext({});
13716
- var FormItem = React49.forwardRef(({ className, ...props }, ref) => {
13717
- const id = React49.useId();
13718
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(FormItemContext.Provider, { value: { id }, children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("div", { ref, className: cn("space-y-2", className), ...props }) });
13785
+ var FormItemContext = React52.createContext({});
13786
+ var FormItem = React52.forwardRef(({ className, ...props }, ref) => {
13787
+ const id = React52.useId();
13788
+ return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FormItemContext.Provider, { value: { id }, children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("div", { ref, className: cn("space-y-2", className), ...props }) });
13719
13789
  });
13720
13790
  FormItem.displayName = "FormItem";
13721
- var FormLabel = React49.forwardRef(
13791
+ var FormLabel = React52.forwardRef(
13722
13792
  ({ className, children, required, ...props }, ref) => {
13723
13793
  const { error, formItemId } = useFormField();
13724
- const config = React49.useContext(FormConfigContext);
13794
+ const config = React52.useContext(FormConfigContext);
13725
13795
  const sizeClass = config.size === "sm" ? "text-xs" : config.size === "lg" ? "text-base" : "text-sm";
13726
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(Label, { ref, className: cn(sizeClass, error && "text-destructive", className), htmlFor: formItemId, ...props, children: [
13796
+ return /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(Label, { ref, className: cn(sizeClass, error && "text-destructive", className), htmlFor: formItemId, ...props, children: [
13727
13797
  children,
13728
- required && /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("span", { className: "text-destructive ml-1", children: "*" })
13798
+ required && /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("span", { className: "text-destructive ml-1", children: "*" })
13729
13799
  ] });
13730
13800
  }
13731
13801
  );
13732
13802
  FormLabel.displayName = "FormLabel";
13733
- var FormControl = React49.forwardRef(({ ...props }, ref) => {
13803
+ var FormControl = React52.forwardRef(({ ...props }, ref) => {
13734
13804
  const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
13735
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
13805
+ return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
13736
13806
  "div",
13737
13807
  {
13738
13808
  ref,
@@ -13744,37 +13814,37 @@ var FormControl = React49.forwardRef(({ ...props }, ref) => {
13744
13814
  );
13745
13815
  });
13746
13816
  FormControl.displayName = "FormControl";
13747
- var FormDescription = React49.forwardRef(({ className, ...props }, ref) => {
13817
+ var FormDescription = React52.forwardRef(({ className, ...props }, ref) => {
13748
13818
  const { formDescriptionId } = useFormField();
13749
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("p", { ref, id: formDescriptionId, className: cn("text-sm text-muted-foreground", className), ...props });
13819
+ return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("p", { ref, id: formDescriptionId, className: cn("text-sm text-muted-foreground", className), ...props });
13750
13820
  });
13751
13821
  FormDescription.displayName = "FormDescription";
13752
- var FormMessage = React49.forwardRef(({ className, children, ...props }, ref) => {
13822
+ var FormMessage = React52.forwardRef(({ className, children, ...props }, ref) => {
13753
13823
  const { error, formMessageId } = useFormField();
13754
13824
  const body = error ? String(error?.message) : children;
13755
13825
  if (!body) {
13756
13826
  return null;
13757
13827
  }
13758
- return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("p", { ref, id: formMessageId, className: cn("text-sm font-medium text-destructive", className), ...props, children: body });
13828
+ return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("p", { ref, id: formMessageId, className: cn("text-sm font-medium text-destructive", className), ...props, children: body });
13759
13829
  });
13760
13830
  FormMessage.displayName = "FormMessage";
13761
- var FormInput = React49.forwardRef(({ name, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(FormConfigContext.Consumer, { children: ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
13831
+ var FormInput = React52.forwardRef(({ name, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FormConfigContext.Consumer, { children: ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
13762
13832
  FormField,
13763
13833
  {
13764
13834
  name,
13765
- render: ({ field }) => /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(FormItem, { children: [
13766
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(FormControl, { children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(Input_default, { size: props.size ?? size, ...field, ...props }) }),
13767
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(FormMessage, {})
13835
+ render: ({ field }) => /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(FormItem, { children: [
13836
+ /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FormControl, { children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(Input_default, { size: props.size ?? size, ...field, ...props }) }),
13837
+ /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FormMessage, {})
13768
13838
  ] })
13769
13839
  }
13770
13840
  ) }));
13771
13841
  FormInput.displayName = "FormInput";
13772
- var FormCheckbox = React49.forwardRef(({ name, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(FormConfigContext.Consumer, { children: ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
13842
+ var FormCheckbox = React52.forwardRef(({ name, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FormConfigContext.Consumer, { children: ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
13773
13843
  FormField,
13774
13844
  {
13775
13845
  name,
13776
- render: ({ field }) => /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(FormItem, { children: [
13777
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(FormControl, { children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
13846
+ render: ({ field }) => /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(FormItem, { children: [
13847
+ /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FormControl, { children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
13778
13848
  Checkbox,
13779
13849
  {
13780
13850
  ref,
@@ -13788,21 +13858,21 @@ var FormCheckbox = React49.forwardRef(({ name, ...props }, ref) => /* @__PURE__
13788
13858
  ...props
13789
13859
  }
13790
13860
  ) }),
13791
- /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(FormMessage, {})
13861
+ /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FormMessage, {})
13792
13862
  ] })
13793
13863
  }
13794
13864
  ) }));
13795
13865
  FormCheckbox.displayName = "FormCheckbox";
13796
- var FormActions = React49.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("div", { ref, className: cn("flex gap-2 justify-end", className), ...props }));
13866
+ var FormActions = React52.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("div", { ref, className: cn("flex gap-2 justify-end", className), ...props }));
13797
13867
  FormActions.displayName = "FormActions";
13798
- var FormSubmitButton = React49.forwardRef(
13799
- ({ children, loading: loading2, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(FormConfigContext.Consumer, { children: ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(Button_default, { ref, type: "submit", size: props.size ?? size, disabled: loading2, ...props, children }) })
13868
+ var FormSubmitButton = React52.forwardRef(
13869
+ ({ children, loading: loading2, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FormConfigContext.Consumer, { children: ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(Button_default, { ref, type: "submit", size: props.size ?? size, disabled: loading2, ...props, children }) })
13800
13870
  );
13801
13871
  FormSubmitButton.displayName = "FormSubmitButton";
13802
13872
 
13803
13873
  // ../../components/ui/NotificationModal.tsx
13804
13874
  var import_lucide_react28 = require("lucide-react");
13805
- var import_jsx_runtime59 = require("react/jsx-runtime");
13875
+ var import_jsx_runtime61 = require("react/jsx-runtime");
13806
13876
  function NotificationModal({ isOpen, onClose, notification, titleText, openLinkText, closeText }) {
13807
13877
  const t = useTranslations("Common");
13808
13878
  if (!notification) return null;
@@ -13823,20 +13893,20 @@ function NotificationModal({ isOpen, onClose, notification, titleText, openLinkT
13823
13893
  onClose();
13824
13894
  }
13825
13895
  };
13826
- return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(Modal_default, { isOpen, onClose, title: titleText || t("notifications"), size: "md", children: /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "space-y-4", children: [
13827
- /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "flex items-center gap-2 pb-2 border-b border-border", children: [
13828
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("div", { className: cn("w-2 h-2 rounded-full", !notification.is_read ? "bg-primary" : "bg-border") }),
13829
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("span", { className: "text-xs text-muted-foreground", children: !notification.is_read ? t("newNotification") : t("readStatus") })
13896
+ return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(Modal_default, { isOpen, onClose, title: titleText || t("notifications"), size: "md", children: /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("div", { className: "space-y-4", children: [
13897
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("div", { className: "flex items-center gap-2 pb-2 border-b border-border", children: [
13898
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { className: cn("w-2 h-2 rounded-full", !notification.is_read ? "bg-primary" : "bg-border") }),
13899
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("span", { className: "text-xs text-muted-foreground", children: !notification.is_read ? t("newNotification") : t("readStatus") })
13830
13900
  ] }),
13831
- notification.title && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("h3", { className: "text-lg font-semibold text-foreground", children: notification.title }),
13832
- notification.body && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("div", { className: "text-sm text-muted-foreground whitespace-pre-wrap leading-relaxed", children: notification.body }),
13833
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("div", { className: "text-xs text-muted-foreground border-t border-border pt-2", children: formatTime3(notification.created_at) }),
13834
- /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "flex gap-2 justify-end pt-2", children: [
13835
- hasLink && /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(Button_default, { variant: "primary", size: "sm", onClick: handleLinkClick, className: "gap-2", children: [
13836
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_lucide_react28.ExternalLink, { className: "w-4 h-4" }),
13901
+ notification.title && /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("h3", { className: "text-lg font-semibold text-foreground", children: notification.title }),
13902
+ notification.body && /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { className: "text-sm text-muted-foreground whitespace-pre-wrap leading-relaxed", children: notification.body }),
13903
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { className: "text-xs text-muted-foreground border-t border-border pt-2", children: formatTime3(notification.created_at) }),
13904
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("div", { className: "flex gap-2 justify-end pt-2", children: [
13905
+ hasLink && /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(Button_default, { variant: "primary", size: "sm", onClick: handleLinkClick, className: "gap-2", children: [
13906
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_lucide_react28.ExternalLink, { className: "w-4 h-4" }),
13837
13907
  openLinkText || t("openLink")
13838
13908
  ] }),
13839
- /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(Button_default, { variant: "ghost", size: "sm", onClick: onClose, children: closeText || t("close") })
13909
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(Button_default, { variant: "ghost", size: "sm", onClick: onClose, children: closeText || t("close") })
13840
13910
  ] })
13841
13911
  ] }) });
13842
13912
  }
@@ -13846,9 +13916,9 @@ var NotificationModal_default = NotificationModal;
13846
13916
  var import_link2 = __toESM(require("next/link"), 1);
13847
13917
  var import_navigation = require("next/navigation");
13848
13918
  var import_lucide_react29 = require("lucide-react");
13849
- var import_jsx_runtime60 = require("react/jsx-runtime");
13919
+ var import_jsx_runtime62 = require("react/jsx-runtime");
13850
13920
  function MessengerIcon(props) {
13851
- return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("svg", { viewBox: "0 0 24 24", width: 24, height: 24, "aria-hidden": "true", ...props, children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
13921
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("svg", { viewBox: "0 0 24 24", width: 24, height: 24, "aria-hidden": "true", ...props, children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
13852
13922
  "path",
13853
13923
  {
13854
13924
  d: "M12 2C6.477 2 2 6.145 2 11.235c0 2.93 1.35 5.542 3.464 7.25v3.515l3.344-1.836c.894.247 1.843.375 2.192.375 5.523 0 10-4.145 10-9.235S17.523 2 12 2zm.994 12.444l-2.563-2.73-5.004 2.73 5.507-5.84 2.626 2.729 4.942-2.729-5.508 5.84z",
@@ -13857,7 +13927,7 @@ function MessengerIcon(props) {
13857
13927
  ) });
13858
13928
  }
13859
13929
  function ZaloIcon(props) {
13860
- return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("svg", { viewBox: "0 0 48 48", width: 20, height: 20, "aria-hidden": "true", ...props, children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
13930
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("svg", { viewBox: "0 0 48 48", width: 20, height: 20, "aria-hidden": "true", ...props, children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
13861
13931
  "path",
13862
13932
  {
13863
13933
  fill: "white",
@@ -13866,7 +13936,7 @@ function ZaloIcon(props) {
13866
13936
  ) });
13867
13937
  }
13868
13938
  function InstagramIcon(props) {
13869
- return /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("svg", { viewBox: "0 0 24 24", width: 20, height: 20, "aria-hidden": "true", fill: "white", ...props, children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("path", { d: "M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" }) });
13939
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("svg", { viewBox: "0 0 24 24", width: 20, height: 20, "aria-hidden": "true", fill: "white", ...props, children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("path", { d: "M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" }) });
13870
13940
  }
13871
13941
  function FloatingContacts({ className }) {
13872
13942
  const pathname = (0, import_navigation.usePathname)();
@@ -13901,8 +13971,8 @@ function FloatingContacts({ className }) {
13901
13971
  external: true
13902
13972
  }
13903
13973
  ];
13904
- return /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)("div", { className: cn("fixed bottom-6 right-4 z-100000", "flex flex-col items-end gap-3", className), "aria-label": "Quick contacts", children: [
13905
- /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
13974
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: cn("fixed bottom-6 right-4 z-100000", "flex flex-col items-end gap-3", className), "aria-label": "Quick contacts", children: [
13975
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
13906
13976
  import_link2.default,
13907
13977
  {
13908
13978
  href: `tel:${hotline.replace(/\D/g, "")}`,
@@ -13913,10 +13983,10 @@ function FloatingContacts({ className }) {
13913
13983
  "hover:scale-105 active:scale-95 transition-transform",
13914
13984
  "bg-[#22c55e]"
13915
13985
  ),
13916
- children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_lucide_react29.Phone, { className: "w-6 h-6" })
13986
+ children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(import_lucide_react29.Phone, { className: "w-6 h-6" })
13917
13987
  }
13918
13988
  ),
13919
- moreItems.map(({ key, href, label, bg, Icon, external }) => /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
13989
+ moreItems.map(({ key, href, label, bg, Icon, external }) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
13920
13990
  import_link2.default,
13921
13991
  {
13922
13992
  href,
@@ -13928,7 +13998,7 @@ function FloatingContacts({ className }) {
13928
13998
  "hover:scale-105 active:scale-95 transition-transform",
13929
13999
  bg
13930
14000
  ),
13931
- children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(Icon, { className: "w-6 h-6" })
14001
+ children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Icon, { className: "w-6 h-6" })
13932
14002
  },
13933
14003
  key
13934
14004
  ))
@@ -13937,7 +14007,7 @@ function FloatingContacts({ className }) {
13937
14007
 
13938
14008
  // ../../components/ui/AccessDenied.tsx
13939
14009
  var import_lucide_react30 = require("lucide-react");
13940
- var import_jsx_runtime61 = require("react/jsx-runtime");
14010
+ var import_jsx_runtime63 = require("react/jsx-runtime");
13941
14011
  var VARIANT_STYLES = {
13942
14012
  destructive: { bg: "bg-destructive/5", border: "border-destructive/20", text: "text-destructive" },
13943
14013
  warning: { bg: "bg-warning/5", border: "border-warning/20", text: "text-warning" },
@@ -13958,32 +14028,32 @@ function AccessDenied({
13958
14028
  }) {
13959
14029
  const styles = VARIANT_STYLES[variant];
13960
14030
  const UsedIcon = Icon || DEFAULT_ICONS[variant];
13961
- return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(Card_default, { className: cn("p-8 text-center shadow-sm", styles.bg, styles.border, className), children: /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
13962
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { className: cn("p-3 rounded-lg", styles.bg.replace("/5", "/10")), children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(UsedIcon, { className: cn("w-8 h-8", styles.text) }) }),
13963
- /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("div", { children: [
13964
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("h3", { className: cn("font-semibold mb-2", styles.text), children: title }),
13965
- /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("p", { className: cn(styles.text.replace("text-", "text-") + "/80", "text-sm"), children: description })
14031
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(Card_default, { className: cn("p-8 text-center shadow-sm", styles.bg, styles.border, className), children: /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
14032
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: cn("p-3 rounded-lg", styles.bg.replace("/5", "/10")), children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(UsedIcon, { className: cn("w-8 h-8", styles.text) }) }),
14033
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { children: [
14034
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("h3", { className: cn("font-semibold mb-2", styles.text), children: title }),
14035
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { className: cn(styles.text.replace("text-", "text-") + "/80", "text-sm"), children: description })
13966
14036
  ] }),
13967
- children && /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { className: "mt-2 flex flex-wrap gap-2 justify-center", children })
14037
+ children && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "mt-2 flex flex-wrap gap-2 justify-center", children })
13968
14038
  ] }) });
13969
14039
  }
13970
14040
 
13971
14041
  // ../../components/ui/ThemeToggleHeadless.tsx
13972
14042
  var import_lucide_react31 = require("lucide-react");
13973
- var import_react33 = require("react");
14043
+ var import_react36 = require("react");
13974
14044
  var import_react_dom7 = require("react-dom");
13975
- var import_jsx_runtime62 = require("react/jsx-runtime");
14045
+ var import_jsx_runtime64 = require("react/jsx-runtime");
13976
14046
  function ThemeToggleHeadless({
13977
14047
  theme,
13978
14048
  onChange,
13979
14049
  labels,
13980
14050
  className
13981
14051
  }) {
13982
- const [isOpen, setIsOpen] = (0, import_react33.useState)(false);
13983
- const [mounted, setMounted] = (0, import_react33.useState)(false);
13984
- const triggerRef = (0, import_react33.useRef)(null);
13985
- const [dropdownPosition, setDropdownPosition] = (0, import_react33.useState)(null);
13986
- (0, import_react33.useEffect)(() => setMounted(true), []);
14052
+ const [isOpen, setIsOpen] = (0, import_react36.useState)(false);
14053
+ const [mounted, setMounted] = (0, import_react36.useState)(false);
14054
+ const triggerRef = (0, import_react36.useRef)(null);
14055
+ const [dropdownPosition, setDropdownPosition] = (0, import_react36.useState)(null);
14056
+ (0, import_react36.useEffect)(() => setMounted(true), []);
13987
14057
  const themes = [
13988
14058
  { value: "light", label: labels?.light ?? "Light", icon: import_lucide_react31.Sun },
13989
14059
  { value: "dark", label: labels?.dark ?? "Dark", icon: import_lucide_react31.Moon },
@@ -14001,8 +14071,8 @@ function ThemeToggleHeadless({
14001
14071
  const top = rect.bottom + scrollTop + 8;
14002
14072
  return { top, left, width };
14003
14073
  };
14004
- return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: cn("relative", className), children: [
14005
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
14074
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: cn("relative", className), children: [
14075
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
14006
14076
  Button_default,
14007
14077
  {
14008
14078
  variant: "ghost",
@@ -14020,25 +14090,25 @@ function ThemeToggleHeadless({
14020
14090
  "aria-haspopup": "menu",
14021
14091
  "aria-expanded": isOpen,
14022
14092
  "aria-label": labels?.heading ?? "Theme",
14023
- children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(CurrentIcon, { className: "h-5 w-5" })
14093
+ children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(CurrentIcon, { className: "h-5 w-5" })
14024
14094
  }
14025
14095
  ),
14026
- isOpen && /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)(import_jsx_runtime62.Fragment, { children: [
14027
- typeof window !== "undefined" && (0, import_react_dom7.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "fixed inset-0 z-9998", onClick: () => setIsOpen(false) }), document.body),
14096
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)(import_jsx_runtime64.Fragment, { children: [
14097
+ typeof window !== "undefined" && (0, import_react_dom7.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime64.jsx)("div", { className: "fixed inset-0 z-9998", onClick: () => setIsOpen(false) }), document.body),
14028
14098
  typeof window !== "undefined" && dropdownPosition && (0, import_react_dom7.createPortal)(
14029
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
14099
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
14030
14100
  "div",
14031
14101
  {
14032
14102
  className: "z-9999 bg-card border border-border rounded-lg shadow-lg overflow-hidden",
14033
14103
  style: { position: "absolute", top: dropdownPosition.top, left: dropdownPosition.left, width: dropdownPosition.width },
14034
14104
  onMouseDown: (e) => e.stopPropagation(),
14035
14105
  role: "menu",
14036
- children: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "p-2", children: [
14037
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "px-3 py-2 text-sm font-medium text-muted-foreground border-b border-border mb-2", children: labels?.heading ?? "Theme" }),
14106
+ children: /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: "p-2", children: [
14107
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("div", { className: "px-3 py-2 text-sm font-medium text-muted-foreground border-b border-border mb-2", children: labels?.heading ?? "Theme" }),
14038
14108
  themes.map((opt) => {
14039
14109
  const Icon = opt.icon;
14040
14110
  const active = theme === opt.value;
14041
- return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)(
14111
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)(
14042
14112
  Button_default,
14043
14113
  {
14044
14114
  variant: "ghost",
@@ -14054,9 +14124,9 @@ function ThemeToggleHeadless({
14054
14124
  role: "menuitemradio",
14055
14125
  "aria-checked": active,
14056
14126
  children: [
14057
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Icon, { className: "h-4 w-4" }),
14058
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "flex-1 text-left", children: opt.label }),
14059
- active && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "w-2 h-2 rounded-full bg-primary" })
14127
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(Icon, { className: "h-4 w-4" }),
14128
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { className: "flex-1 text-left", children: opt.label }),
14129
+ active && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("div", { className: "w-2 h-2 rounded-full bg-primary" })
14060
14130
  ]
14061
14131
  },
14062
14132
  opt.value
@@ -14072,10 +14142,10 @@ function ThemeToggleHeadless({
14072
14142
  }
14073
14143
 
14074
14144
  // ../../components/ui/LanguageSwitcherHeadless.tsx
14075
- var import_react34 = require("react");
14145
+ var import_react37 = require("react");
14076
14146
  var import_react_dom8 = require("react-dom");
14077
14147
  var import_lucide_react32 = require("lucide-react");
14078
- var import_jsx_runtime63 = require("react/jsx-runtime");
14148
+ var import_jsx_runtime65 = require("react/jsx-runtime");
14079
14149
  function LanguageSwitcherHeadless({
14080
14150
  locales,
14081
14151
  currentLocale,
@@ -14083,9 +14153,9 @@ function LanguageSwitcherHeadless({
14083
14153
  labels,
14084
14154
  className
14085
14155
  }) {
14086
- const [isOpen, setIsOpen] = (0, import_react34.useState)(false);
14087
- const [dropdownPosition, setDropdownPosition] = (0, import_react34.useState)(null);
14088
- const triggerButtonRef = (0, import_react34.useRef)(null);
14156
+ const [isOpen, setIsOpen] = (0, import_react37.useState)(false);
14157
+ const [dropdownPosition, setDropdownPosition] = (0, import_react37.useState)(null);
14158
+ const triggerButtonRef = (0, import_react37.useRef)(null);
14089
14159
  const currentLanguage = locales.find((l) => l.code === currentLocale) || locales[0];
14090
14160
  const calculatePosition = () => {
14091
14161
  const rect = triggerButtonRef.current?.getBoundingClientRect();
@@ -14097,8 +14167,8 @@ function LanguageSwitcherHeadless({
14097
14167
  const top = rect.bottom + scrollTop + 8;
14098
14168
  return { top, left, width };
14099
14169
  };
14100
- return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: cn("relative", className), children: [
14101
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
14170
+ return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: cn("relative", className), children: [
14171
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
14102
14172
  Button_default,
14103
14173
  {
14104
14174
  variant: "ghost",
@@ -14117,22 +14187,22 @@ function LanguageSwitcherHeadless({
14117
14187
  "aria-expanded": isOpen,
14118
14188
  "aria-label": labels?.heading ?? "Language",
14119
14189
  title: labels?.heading ?? "Language",
14120
- children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(import_lucide_react32.Globe, { className: "h-5 w-5" })
14190
+ children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(import_lucide_react32.Globe, { className: "h-5 w-5" })
14121
14191
  }
14122
14192
  ),
14123
- isOpen && /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(import_jsx_runtime63.Fragment, { children: [
14124
- typeof window !== "undefined" && (0, import_react_dom8.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "fixed inset-0 z-9998", onClick: () => setIsOpen(false) }), document.body),
14193
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)(import_jsx_runtime65.Fragment, { children: [
14194
+ typeof window !== "undefined" && (0, import_react_dom8.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime65.jsx)("div", { className: "fixed inset-0 z-9998", onClick: () => setIsOpen(false) }), document.body),
14125
14195
  typeof window !== "undefined" && dropdownPosition && (0, import_react_dom8.createPortal)(
14126
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
14196
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
14127
14197
  "div",
14128
14198
  {
14129
14199
  className: "z-9999 bg-card border border-border rounded-lg shadow-lg overflow-hidden",
14130
14200
  style: { position: "absolute", top: dropdownPosition.top, left: dropdownPosition.left, width: dropdownPosition.width },
14131
14201
  onMouseDown: (e) => e.stopPropagation(),
14132
14202
  role: "menu",
14133
- children: /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "p-2", children: [
14134
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "px-3 py-2 text-sm font-medium text-muted-foreground border-b border-border mb-2", children: labels?.heading ?? "Language" }),
14135
- locales.map((language) => /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(
14203
+ children: /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: "p-2", children: [
14204
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("div", { className: "px-3 py-2 text-sm font-medium text-muted-foreground border-b border-border mb-2", children: labels?.heading ?? "Language" }),
14205
+ locales.map((language) => /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)(
14136
14206
  Button_default,
14137
14207
  {
14138
14208
  variant: "ghost",
@@ -14145,9 +14215,9 @@ function LanguageSwitcherHeadless({
14145
14215
  role: "menuitemradio",
14146
14216
  "aria-checked": currentLocale === language.code,
14147
14217
  children: [
14148
- language.flag && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "text-lg", children: language.flag }),
14149
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "flex-1 text-left", children: language.name }),
14150
- currentLocale === language.code && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "w-2 h-2 rounded-full bg-primary" })
14218
+ language.flag && /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("span", { className: "text-lg", children: language.flag }),
14219
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("span", { className: "flex-1 text-left", children: language.name }),
14220
+ currentLocale === language.code && /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("div", { className: "w-2 h-2 rounded-full bg-primary" })
14151
14221
  ]
14152
14222
  },
14153
14223
  language.code
@@ -14577,7 +14647,7 @@ var VARIANT_STYLES_ALERT = {
14577
14647
  };
14578
14648
 
14579
14649
  // src/contexts/TranslationContext.tsx
14580
- var React51 = __toESM(require("react"), 1);
14650
+ var React54 = __toESM(require("react"), 1);
14581
14651
 
14582
14652
  // locales/en.json
14583
14653
  var en_default = {
@@ -14880,16 +14950,16 @@ var ja_default = {
14880
14950
  };
14881
14951
 
14882
14952
  // src/contexts/TranslationContext.tsx
14883
- var import_jsx_runtime64 = require("react/jsx-runtime");
14953
+ var import_jsx_runtime66 = require("react/jsx-runtime");
14884
14954
  var defaultTranslations2 = {
14885
14955
  en: en_default,
14886
14956
  vi: vi_default,
14887
14957
  ko: ko_default,
14888
14958
  ja: ja_default
14889
14959
  };
14890
- var TranslationContext2 = React51.createContext(null);
14960
+ var TranslationContext2 = React54.createContext(null);
14891
14961
  var TranslationProvider = ({ children, locale = "en", translations }) => {
14892
- const t = React51.useCallback(
14962
+ const t = React54.useCallback(
14893
14963
  (namespace) => {
14894
14964
  return (key) => {
14895
14965
  const mergedTranslations = {
@@ -14914,10 +14984,10 @@ var TranslationProvider = ({ children, locale = "en", translations }) => {
14914
14984
  },
14915
14985
  [locale, translations]
14916
14986
  );
14917
- return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(TranslationContext2.Provider, { value: { locale, t }, children });
14987
+ return /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(TranslationContext2.Provider, { value: { locale, t }, children });
14918
14988
  };
14919
14989
  var useUnderverseTranslations = (namespace) => {
14920
- const context = React51.useContext(TranslationContext2);
14990
+ const context = React54.useContext(TranslationContext2);
14921
14991
  if (!context) {
14922
14992
  return (key) => {
14923
14993
  const parts = namespace.split(".");
@@ -14939,13 +15009,13 @@ var useUnderverseTranslations = (namespace) => {
14939
15009
  return context.t(namespace);
14940
15010
  };
14941
15011
  var useUnderverseLocale = () => {
14942
- const context = React51.useContext(TranslationContext2);
15012
+ const context = React54.useContext(TranslationContext2);
14943
15013
  return context?.locale || "en";
14944
15014
  };
14945
15015
 
14946
15016
  // src/hooks/useSmartTranslations.tsx
14947
- var React52 = __toESM(require("react"), 1);
14948
- var import_jsx_runtime65 = require("react/jsx-runtime");
15017
+ var React55 = __toESM(require("react"), 1);
15018
+ var import_jsx_runtime67 = require("react/jsx-runtime");
14949
15019
  var nextIntlHooks = null;
14950
15020
  try {
14951
15021
  const nextIntl = require("next-intl");
@@ -14956,12 +15026,12 @@ try {
14956
15026
  } catch {
14957
15027
  nextIntlHooks = null;
14958
15028
  }
14959
- var ForceInternalContext = React52.createContext(false);
15029
+ var ForceInternalContext = React55.createContext(false);
14960
15030
  var ForceInternalTranslationsProvider = ({ children }) => {
14961
- return /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(ForceInternalContext.Provider, { value: true, children });
15031
+ return /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(ForceInternalContext.Provider, { value: true, children });
14962
15032
  };
14963
15033
  function useSmartTranslations(namespace) {
14964
- const forceInternal = React52.useContext(ForceInternalContext);
15034
+ const forceInternal = React55.useContext(ForceInternalContext);
14965
15035
  const internalT = useUnderverseTranslations(namespace);
14966
15036
  if (forceInternal || !nextIntlHooks?.useTranslations) {
14967
15037
  return internalT;
@@ -14974,7 +15044,7 @@ function useSmartTranslations(namespace) {
14974
15044
  }
14975
15045
  }
14976
15046
  function useSmartLocale() {
14977
- const forceInternal = React52.useContext(ForceInternalContext);
15047
+ const forceInternal = React55.useContext(ForceInternalContext);
14978
15048
  const internalLocale = useUnderverseLocale();
14979
15049
  if (forceInternal || !nextIntlHooks?.useLocale) {
14980
15050
  return internalLocale;