@underverse-ui/underverse 0.2.72 → 0.2.74

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