@comitor/ui 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +138 -10
  2. package/dist/chart/chart-colors.d.ts +56 -0
  3. package/dist/chart/chart-colors.js +21 -0
  4. package/dist/chart/chart-colors.js.map +1 -0
  5. package/dist/chart/chart.d.ts +40 -0
  6. package/dist/chart/chart.js.map +1 -1
  7. package/dist/chart/comitor-chart.d.ts +21 -43
  8. package/dist/chart/comitor-chart.js +13 -24
  9. package/dist/chart/comitor-chart.js.map +1 -1
  10. package/dist/chart/index.d.ts +2 -1
  11. package/dist/chart/index.js +1 -0
  12. package/dist/chart/index.js.map +1 -1
  13. package/dist/components/composite/data-table-constants.d.ts +40 -0
  14. package/dist/components/composite/data-table-constants.js +13 -0
  15. package/dist/components/composite/data-table-constants.js.map +1 -0
  16. package/dist/components/composite/data-table.d.ts +2 -27
  17. package/dist/components/composite/data-table.js +1 -10
  18. package/dist/components/composite/data-table.js.map +1 -1
  19. package/dist/components/composite/date-picker-constants.d.ts +21 -0
  20. package/dist/components/composite/date-picker-constants.js +39 -0
  21. package/dist/components/composite/date-picker-constants.js.map +1 -0
  22. package/dist/components/composite/date-picker.d.ts +2 -18
  23. package/dist/components/composite/date-picker.js +6 -48
  24. package/dist/components/composite/date-picker.js.map +1 -1
  25. package/dist/components/composite/form-field-constants.d.ts +26 -0
  26. package/dist/components/composite/form-field-constants.js +13 -0
  27. package/dist/components/composite/form-field-constants.js.map +1 -0
  28. package/dist/components/composite/form-field.d.ts +1 -13
  29. package/dist/components/composite/form-field.js +1 -11
  30. package/dist/components/composite/form-field.js.map +1 -1
  31. package/dist/components/composite/search-filter-bar-constants.d.ts +52 -0
  32. package/dist/components/composite/search-filter-bar-constants.js +18 -0
  33. package/dist/components/composite/search-filter-bar-constants.js.map +1 -0
  34. package/dist/components/composite/search-filter-bar.d.ts +3 -37
  35. package/dist/components/composite/search-filter-bar.js +2 -15
  36. package/dist/components/composite/search-filter-bar.js.map +1 -1
  37. package/dist/components/composite/table-pagination-constants.d.ts +56 -0
  38. package/dist/components/composite/table-pagination-constants.js +18 -0
  39. package/dist/components/composite/table-pagination-constants.js.map +1 -0
  40. package/dist/components/composite/table-pagination.d.ts +2 -43
  41. package/dist/components/composite/table-pagination.js +4 -15
  42. package/dist/components/composite/table-pagination.js.map +1 -1
  43. package/dist/index.d.ts +10 -5
  44. package/dist/index.js +5 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/shell/shell-context.js +3 -2
  47. package/dist/shell/shell-context.js.map +1 -1
  48. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/chart/comitor-chart.tsx"],"sourcesContent":["\"use client\";\n\nimport type * as React from \"react\";\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Legend,\n Line,\n LineChart,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis\n} from \"recharts\";\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Biểu đồ \"lắp sẵn\" của Comitor — dựng nhanh line/bar/area chỉ với `data` + `series`.\n * Khác `chart.tsx` (primitive shadcn thô, tự lắp recharts), file này là tầng tiện dụng:\n * đã chọn sẵn trục, lưới, tooltip và bảng màu theo token.\n *\n * ⚠ Tên export ĐÃ ĐỔI so với comitor-ds vì hai file đụng nhau trong cùng entry `/chart`:\n * `ChartContainer` → `ComitorChartContainer`, `ChartTooltip` → `ComitorChartTooltip`.\n * Tên trần `ChartContainer`/`ChartTooltip` để dành cho primitive shadcn (giữ tương thích\n * `shadcn add`). Code mẫu cũ trong trang docs comitor-ds phải sửa theo.\n */\n\n/**\n * Bảng màu series mặc định — trỏ vào `--chart-1…5` trong `styles.css`.\n *\n * Vì sao không lấy thẳng `var(--gold-300)`, `var(--teal)`… như comitor-ds: bảng màu biểu đồ\n * cần đổi ĐỘC LẬP với brand (app đổi `--chart-*` ở `<body>` là xong), và `--chart-3` có bản\n * dark riêng (navy nguyên bản chìm vào nền tối).\n *\n * Dùng `var(--chart-N)` chứ KHÔNG dùng `var(--color-chart-N)`: bản `--chart-*` được khai\n * tường minh trong `:root` của `styles.css` nên chắc chắn tồn tại, còn `--color-chart-*` là\n * biến do `@theme inline` của Tailwind sinh ra.\n */\nexport const CHART_SERIES_COLORS = [\n \"var(--chart-1)\",\n \"var(--chart-2)\",\n \"var(--chart-3)\",\n \"var(--chart-4)\",\n \"var(--chart-5)\"\n] as const;\n\n/**\n * Màu gọi theo tên — dùng khi muốn chỉ định màu cho một series cụ thể\n * (`series: [{ key: \"loi\", color: CHART_COLORS.red }]`).\n *\n * ⚠ Khoá `gray` của comitor-ds đổi thành `ash`: thang xám của package là `--ash-*`,\n * `gray-*` để nguyên cho Tailwind (xem chú thích thang màu trong `styles.css`).\n */\nexport const CHART_COLORS = {\n gold: \"var(--chart-1)\",\n goldLight: \"var(--gold-100)\",\n teal: \"var(--chart-2)\",\n navy: \"var(--chart-3)\",\n green: \"var(--chart-4)\",\n red: \"var(--chart-5)\",\n ash: \"var(--ash-300)\"\n} as const;\n\nexport type ChartColorName = keyof typeof CHART_COLORS;\n\n/** Lấy màu cho series thứ `index`: ưu tiên màu tự khai, không có thì xoay vòng bảng mặc định. */\nfunction seriesColor(series: DataSeries, index: number): string {\n return series.color ?? CHART_SERIES_COLORS[index % CHART_SERIES_COLORS.length] ?? CHART_SERIES_COLORS[0];\n}\n\n// ─── Tooltip ────────────────────────────────────────────────────────────────\n\n/**\n * Một phần tử payload recharts bơm vào tooltip.\n *\n * Khai tại đây thay vì `TooltipProps<number, string>[\"payload\"]`: recharts **3** đã bỏ\n * `payload` khỏi props của `<Tooltip>` (chuyển sang context nội bộ), nên phép dẫn xuất đó\n * hỏng kiểu ngay trong file `.d.ts` phát hành. Xem chú thích cùng nội dung ở `chart.tsx`\n * (`ChartPayloadItem`) — cùng một lý do, cùng một cách chữa.\n */\nexport interface ComitorChartPayloadItem {\n value?: number;\n name?: string;\n dataKey?: string | number;\n color?: string;\n}\n\nexport interface ComitorChartTooltipProps {\n /** Do recharts bơm vào — đừng truyền tay. */\n active?: boolean;\n /** Do recharts bơm vào — đừng truyền tay. */\n payload?: ComitorChartPayloadItem[];\n /** Do recharts bơm vào — đừng truyền tay. */\n label?: string;\n /** Định dạng nhãn trục X (ví dụ đổi \"2026-01\" → \"Th01/2026\"). */\n labelFormatter?: (label: string) => string;\n /** Định dạng giá trị (ví dụ thêm \"₫\", \"%\"). Mặc định: `toLocaleString(\"vi-VN\")`. */\n valueFormatter?: (value: number) => string;\n}\n\n/**\n * Tooltip theo token Comitor. Truyền vào recharts dưới dạng element:\n * `<Tooltip content={<ComitorChartTooltip valueFormatter={f} />} />`.\n */\nexport function ComitorChartTooltip({\n active,\n payload,\n label,\n labelFormatter,\n valueFormatter\n}: ComitorChartTooltipProps) {\n if (!active || !payload?.length) return null;\n\n return (\n <div data-slot=\"comitor-chart-tooltip\" className=\"rounded-lg border border-border bg-card px-3 py-2 shadow-card\">\n {label && (\n <p className=\"text-xs font-medium text-foreground mb-1.5\">{labelFormatter ? labelFormatter(label) : label}</p>\n )}\n <div className=\"space-y-1\">\n {payload.map((entry, i) => {\n const value = entry.value;\n const display = value === undefined ? \"—\" : (valueFormatter?.(value) ?? value.toLocaleString(\"vi-VN\"));\n\n return (\n <div key={`${entry.dataKey ?? entry.name ?? i}`} className=\"flex items-center gap-2 text-xs\">\n <span className=\"h-2 w-2 rounded-full shrink-0\" style={{ backgroundColor: entry.color }} />\n <span className=\"text-muted-foreground\">{entry.name}:</span>\n <span className=\"font-medium text-foreground\">{display}</span>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\n// ─── Khung ──────────────────────────────────────────────────────────────────\n\nexport interface ComitorChartContainerProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Chiều cao khung. Số → px. Mặc định 300. */\n height?: number | string;\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/**\n * Khung `ResponsiveContainer` cao cố định. Khác `ChartContainer` của shadcn ở chỗ:\n * không cấp context `config`, không sinh biến `--color-<key>` — chỉ lo kích thước.\n */\nexport function ComitorChartContainer({\n height = 300,\n className,\n children,\n ref,\n ...props\n}: ComitorChartContainerProps) {\n return (\n <div\n ref={ref}\n data-slot=\"comitor-chart\"\n className={cn(\"w-full\", className)}\n style={{ height: typeof height === \"number\" ? `${height}px` : height }}\n {...props}\n >\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n {children as React.ReactElement}\n </ResponsiveContainer>\n </div>\n );\n}\n\n// ─── Kiểu dùng chung ────────────────────────────────────────────────────────\n\n/** Một series (một đường/cột/vùng) trong biểu đồ. */\nexport interface DataSeries {\n /** Tên trường trong `data`. */\n key: string;\n /** Nhãn hiển thị ở tooltip/chú giải. Mặc định lấy `key`. */\n name?: string;\n /** Màu — nên lấy từ `CHART_COLORS` hoặc `var(--chart-N)`, đừng ghi hex. */\n color?: string;\n}\n\nexport interface BaseChartProps {\n data: Record<string, unknown>[];\n /** Tên trường dùng cho trục X. */\n xAxisKey: string;\n height?: number | string;\n className?: string;\n showGrid?: boolean;\n showLegend?: boolean;\n valueFormatter?: (value: number) => string;\n}\n\n/** Style dùng chung cho tick của cả hai trục — gom lại để 3 biểu đồ không lệch nhau. */\nconst AXIS_TICK = { fontSize: 11, fill: \"var(--muted-foreground)\" } as const;\nconst AXIS_LINE = { stroke: \"var(--border)\" } as const;\nconst CHART_MARGIN = { top: 8, right: 8, left: 0, bottom: 0 } as const;\n\n// ─── Line ───────────────────────────────────────────────────────────────────\n\nexport interface ComitorLineChartProps extends BaseChartProps {\n series: DataSeries[];\n}\n\n/** Biểu đồ đường — dùng cho xu hướng theo thời gian. */\nexport function ComitorLineChart({\n data,\n xAxisKey,\n series,\n height = 300,\n className,\n showGrid = true,\n showLegend = false,\n valueFormatter\n}: ComitorLineChartProps) {\n return (\n <ComitorChartContainer height={height} className={className}>\n <LineChart data={data} margin={CHART_MARGIN}>\n {showGrid && <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--border)\" />}\n <XAxis dataKey={xAxisKey} tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <YAxis tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <Tooltip content={<ComitorChartTooltip valueFormatter={valueFormatter} />} />\n {showLegend && <Legend />}\n {series.map((s, i) => (\n <Line\n key={s.key}\n type=\"monotone\"\n dataKey={s.key}\n name={s.name || s.key}\n stroke={seriesColor(s, i)}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4, strokeWidth: 0 }}\n />\n ))}\n </LineChart>\n </ComitorChartContainer>\n );\n}\n\n// ─── Bar ────────────────────────────────────────────────────────────────────\n\nexport interface ComitorBarChartProps extends BaseChartProps {\n series: DataSeries[];\n /** Chồng các series lên nhau thay vì xếp cạnh. */\n stacked?: boolean;\n}\n\n/** Biểu đồ cột — dùng cho so sánh giữa các hạng mục. Lưới chỉ kẻ ngang. */\nexport function ComitorBarChart({\n data,\n xAxisKey,\n series,\n height = 300,\n className,\n showGrid = true,\n showLegend = false,\n stacked = false,\n valueFormatter\n}: ComitorBarChartProps) {\n return (\n <ComitorChartContainer height={height} className={className}>\n <BarChart data={data} margin={CHART_MARGIN}>\n {showGrid && <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--border)\" vertical={false} />}\n <XAxis dataKey={xAxisKey} tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <YAxis tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <Tooltip content={<ComitorChartTooltip valueFormatter={valueFormatter} />} />\n {showLegend && <Legend />}\n {series.map((s, i) => (\n <Bar\n key={s.key}\n dataKey={s.key}\n name={s.name || s.key}\n fill={seriesColor(s, i)}\n radius={[4, 4, 0, 0]}\n stackId={stacked ? \"stack\" : undefined}\n />\n ))}\n </BarChart>\n </ComitorChartContainer>\n );\n}\n\n// ─── Area ───────────────────────────────────────────────────────────────────\n\nexport interface ComitorAreaChartProps extends BaseChartProps {\n series: DataSeries[];\n /** Chồng các series lên nhau (tổng tích luỹ). */\n stacked?: boolean;\n}\n\n/** Biểu đồ vùng — xu hướng kèm cảm giác khối lượng; `fillOpacity` 0.15 để chồng lớp còn đọc được. */\nexport function ComitorAreaChart({\n data,\n xAxisKey,\n series,\n height = 300,\n className,\n showGrid = true,\n showLegend = false,\n stacked = false,\n valueFormatter\n}: ComitorAreaChartProps) {\n return (\n <ComitorChartContainer height={height} className={className}>\n <AreaChart data={data} margin={CHART_MARGIN}>\n {showGrid && <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--border)\" />}\n <XAxis dataKey={xAxisKey} tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <YAxis tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <Tooltip content={<ComitorChartTooltip valueFormatter={valueFormatter} />} />\n {showLegend && <Legend />}\n {series.map((s, i) => {\n const color = seriesColor(s, i);\n return (\n <Area\n key={s.key}\n type=\"monotone\"\n dataKey={s.key}\n name={s.name || s.key}\n stroke={color}\n fill={color}\n fillOpacity={0.15}\n strokeWidth={2}\n stackId={stacked ? \"stack\" : undefined}\n />\n );\n })}\n </AreaChart>\n </ComitorChartContainer>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/chart/comitor-chart.tsx"],"sourcesContent":["\"use client\";\n\nimport type * as React from \"react\";\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Legend,\n Line,\n LineChart,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis\n} from \"recharts\";\nimport { cn } from \"../lib/cn.js\";\n/*\n * `CHART_COLORS` / `CHART_SERIES_COLORS` sống ở `chart-colors.ts` — module KHÔNG có\n * `\"use client\"`. Chỉ IMPORT vào đây để dùng nội bộ, KHÔNG re-export: mọi export đi qua một\n * file client đều biến thành client-reference proxy ở phía Server Component (đọc ra\n * `undefined`, không throw). Barrel `chart/index.ts` xuất chúng thẳng từ `chart-colors.js`.\n * Lý do đầy đủ nằm ở đầu `chart-colors.ts`.\n */\nimport { CHART_SERIES_COLORS } from \"./chart-colors.js\";\n\n/**\n * Biểu đồ \"lắp sẵn\" của Comitor — dựng nhanh line/bar/area chỉ với `data` + `series`.\n * Khác `chart.tsx` (primitive shadcn thô, tự lắp recharts), file này là tầng tiện dụng:\n * đã chọn sẵn trục, lưới, tooltip và bảng màu theo token.\n *\n * ⚠ Tên export ĐÃ ĐỔI so với comitor-ds vì hai file đụng nhau trong cùng entry `/chart`:\n * `ChartContainer` → `ComitorChartContainer`, `ChartTooltip` → `ComitorChartTooltip`.\n * Tên trần `ChartContainer`/`ChartTooltip` để dành cho primitive shadcn (giữ tương thích\n * `shadcn add`). Code mẫu cũ trong trang docs comitor-ds phải sửa theo.\n */\n\n/** Lấy màu cho series thứ `index`: ưu tiên màu tự khai, không có thì xoay vòng bảng mặc định. */\nfunction seriesColor(series: DataSeries, index: number): string {\n return series.color ?? CHART_SERIES_COLORS[index % CHART_SERIES_COLORS.length] ?? CHART_SERIES_COLORS[0];\n}\n\n// ─── Tooltip ────────────────────────────────────────────────────────────────\n\n/**\n * Một phần tử payload recharts bơm vào tooltip.\n *\n * Khai tại đây thay vì `TooltipProps<number, string>[\"payload\"]`: recharts **3** đã bỏ\n * `payload` khỏi props của `<Tooltip>` (chuyển sang context nội bộ), nên phép dẫn xuất đó\n * hỏng kiểu ngay trong file `.d.ts` phát hành. Xem chú thích cùng nội dung ở `chart.tsx`\n * (`ChartPayloadItem`) — cùng một lý do, cùng một cách chữa.\n */\nexport interface ComitorChartPayloadItem {\n value?: number;\n name?: string;\n dataKey?: string | number;\n color?: string;\n}\n\nexport interface ComitorChartTooltipProps {\n /** Do recharts bơm vào — đừng truyền tay. */\n active?: boolean;\n /** Do recharts bơm vào — đừng truyền tay. */\n payload?: ComitorChartPayloadItem[];\n /** Do recharts bơm vào — đừng truyền tay. */\n label?: string;\n /** Định dạng nhãn trục X (ví dụ đổi \"2026-01\" → \"Th01/2026\"). */\n labelFormatter?: (label: string) => string;\n /** Định dạng giá trị (ví dụ thêm \"₫\", \"%\"). Mặc định: `toLocaleString(\"vi-VN\")`. */\n valueFormatter?: (value: number) => string;\n}\n\n/**\n * Tooltip theo token Comitor. Truyền vào recharts dưới dạng element:\n * `<Tooltip content={<ComitorChartTooltip valueFormatter={f} />} />`.\n */\nexport function ComitorChartTooltip({\n active,\n payload,\n label,\n labelFormatter,\n valueFormatter\n}: ComitorChartTooltipProps) {\n if (!active || !payload?.length) return null;\n\n return (\n <div data-slot=\"comitor-chart-tooltip\" className=\"rounded-lg border border-border bg-card px-3 py-2 shadow-card\">\n {label && (\n <p className=\"text-xs font-medium text-foreground mb-1.5\">{labelFormatter ? labelFormatter(label) : label}</p>\n )}\n <div className=\"space-y-1\">\n {payload.map((entry, i) => {\n const value = entry.value;\n const display = value === undefined ? \"—\" : (valueFormatter?.(value) ?? value.toLocaleString(\"vi-VN\"));\n\n return (\n <div key={`${entry.dataKey ?? entry.name ?? i}`} className=\"flex items-center gap-2 text-xs\">\n <span className=\"h-2 w-2 rounded-full shrink-0\" style={{ backgroundColor: entry.color }} />\n <span className=\"text-muted-foreground\">{entry.name}:</span>\n <span className=\"font-medium text-foreground\">{display}</span>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\n// ─── Khung ──────────────────────────────────────────────────────────────────\n\nexport interface ComitorChartContainerProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Chiều cao khung. Số → px. Mặc định 300. */\n height?: number | string;\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/**\n * Khung `ResponsiveContainer` cao cố định. Khác `ChartContainer` của shadcn ở chỗ:\n * không cấp context `config`, không sinh biến `--color-<key>` — chỉ lo kích thước.\n */\nexport function ComitorChartContainer({\n height = 300,\n className,\n children,\n ref,\n ...props\n}: ComitorChartContainerProps) {\n return (\n <div\n ref={ref}\n data-slot=\"comitor-chart\"\n className={cn(\"w-full\", className)}\n style={{ height: typeof height === \"number\" ? `${height}px` : height }}\n {...props}\n >\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n {children as React.ReactElement}\n </ResponsiveContainer>\n </div>\n );\n}\n\n// ─── Kiểu dùng chung ────────────────────────────────────────────────────────\n\n/** Một series (một đường/cột/vùng) trong biểu đồ. */\nexport interface DataSeries {\n /** Tên trường trong `data`. */\n key: string;\n /** Nhãn hiển thị ở tooltip/chú giải. Mặc định lấy `key`. */\n name?: string;\n /** Màu — nên lấy từ `CHART_COLORS` hoặc `var(--chart-N)`, đừng ghi hex. */\n color?: string;\n}\n\nexport interface BaseChartProps {\n data: Record<string, unknown>[];\n /** Tên trường dùng cho trục X. */\n xAxisKey: string;\n height?: number | string;\n className?: string;\n showGrid?: boolean;\n showLegend?: boolean;\n valueFormatter?: (value: number) => string;\n /**\n * Bật hiệu ứng vẽ dần khi mark xuất hiện. **Mặc định `false`** — khác mặc định `true` của\n * recharts, và đây là chủ đích.\n *\n * VÌ SAO TẮT MẶC ĐỊNH: với recharts 2.15.x + React 19.2 hiệu ứng KHÔNG bao giờ chạy tới\n * khung hình cuối, nên mark đứng nguyên ở khung đầu — tức là vô hình. Ba dạng cùng một lỗi,\n * đo được trên DOM thật:\n *\n * `<Bar>` `<g class=\"recharts-bar-rectangle\">` RỖNG — `Rectangle` trả `null` khi cao 0\n * `<Line>` path có `d` đúng nhưng `stroke-dasharray=\"0px 634px\"` — vẽ 0px\n * `<Area>` path có `d` đúng nhưng `<clipPath><rect width=\"0\">` — bị cắt sạch\n *\n * Kết quả giống hệt nhau ở cả ba: đủ trục, đủ lưới, đủ chú giải, KHÔNG có dữ liệu — và\n * không tự hết sau khi chờ. Một biểu đồ hiện ra ngay đáng giá hơn hiệu ứng, nên mặc định\n * đảo lại. Ai muốn hiệu ứng thì bật tường minh (`isAnimationActive` — recharts 3 chạy đúng).\n */\n isAnimationActive?: boolean;\n}\n\n/** Style dùng chung cho tick của cả hai trục — gom lại để 3 biểu đồ không lệch nhau. */\nconst AXIS_TICK = { fontSize: 11, fill: \"var(--muted-foreground)\" } as const;\nconst AXIS_LINE = { stroke: \"var(--border)\" } as const;\nconst CHART_MARGIN = { top: 8, right: 8, left: 0, bottom: 0 } as const;\n\n// ─── Line ───────────────────────────────────────────────────────────────────\n\nexport interface ComitorLineChartProps extends BaseChartProps {\n series: DataSeries[];\n}\n\n/** Biểu đồ đường — dùng cho xu hướng theo thời gian. */\nexport function ComitorLineChart({\n data,\n xAxisKey,\n series,\n height = 300,\n className,\n showGrid = true,\n showLegend = false,\n valueFormatter,\n isAnimationActive = false\n}: ComitorLineChartProps) {\n return (\n <ComitorChartContainer height={height} className={className}>\n <LineChart data={data} margin={CHART_MARGIN}>\n {showGrid && <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--border)\" />}\n <XAxis dataKey={xAxisKey} tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <YAxis tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <Tooltip content={<ComitorChartTooltip valueFormatter={valueFormatter} />} />\n {showLegend && <Legend />}\n {series.map((s, i) => (\n <Line\n key={s.key}\n type=\"monotone\"\n dataKey={s.key}\n name={s.name || s.key}\n stroke={seriesColor(s, i)}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4, strokeWidth: 0 }}\n isAnimationActive={isAnimationActive}\n />\n ))}\n </LineChart>\n </ComitorChartContainer>\n );\n}\n\n// ─── Bar ────────────────────────────────────────────────────────────────────\n\nexport interface ComitorBarChartProps extends BaseChartProps {\n series: DataSeries[];\n /** Chồng các series lên nhau thay vì xếp cạnh. */\n stacked?: boolean;\n}\n\n/** Biểu đồ cột — dùng cho so sánh giữa các hạng mục. Lưới chỉ kẻ ngang. */\nexport function ComitorBarChart({\n data,\n xAxisKey,\n series,\n height = 300,\n className,\n showGrid = true,\n showLegend = false,\n stacked = false,\n valueFormatter,\n isAnimationActive = false\n}: ComitorBarChartProps) {\n return (\n <ComitorChartContainer height={height} className={className}>\n <BarChart data={data} margin={CHART_MARGIN}>\n {showGrid && <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--border)\" vertical={false} />}\n <XAxis dataKey={xAxisKey} tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <YAxis tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <Tooltip content={<ComitorChartTooltip valueFormatter={valueFormatter} />} />\n {showLegend && <Legend />}\n {series.map((s, i) => (\n <Bar\n key={s.key}\n dataKey={s.key}\n name={s.name || s.key}\n fill={seriesColor(s, i)}\n radius={[4, 4, 0, 0]}\n stackId={stacked ? \"stack\" : undefined}\n isAnimationActive={isAnimationActive}\n />\n ))}\n </BarChart>\n </ComitorChartContainer>\n );\n}\n\n// ─── Area ───────────────────────────────────────────────────────────────────\n\nexport interface ComitorAreaChartProps extends BaseChartProps {\n series: DataSeries[];\n /** Chồng các series lên nhau (tổng tích luỹ). */\n stacked?: boolean;\n}\n\n/** Biểu đồ vùng — xu hướng kèm cảm giác khối lượng; `fillOpacity` 0.15 để chồng lớp còn đọc được. */\nexport function ComitorAreaChart({\n data,\n xAxisKey,\n series,\n height = 300,\n className,\n showGrid = true,\n showLegend = false,\n stacked = false,\n valueFormatter,\n isAnimationActive = false\n}: ComitorAreaChartProps) {\n return (\n <ComitorChartContainer height={height} className={className}>\n <AreaChart data={data} margin={CHART_MARGIN}>\n {showGrid && <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--border)\" />}\n <XAxis dataKey={xAxisKey} tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <YAxis tick={AXIS_TICK} axisLine={AXIS_LINE} tickLine={AXIS_LINE} />\n <Tooltip content={<ComitorChartTooltip valueFormatter={valueFormatter} />} />\n {showLegend && <Legend />}\n {series.map((s, i) => {\n const color = seriesColor(s, i);\n return (\n <Area\n key={s.key}\n type=\"monotone\"\n dataKey={s.key}\n name={s.name || s.key}\n stroke={color}\n fill={color}\n fillOpacity={0.15}\n strokeWidth={2}\n stackId={stacked ? \"stack\" : undefined}\n isAnimationActive={isAnimationActive}\n />\n );\n })}\n </AreaChart>\n </ComitorChartContainer>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,5 @@
1
1
  export { ChartConfig, ChartContainer, ChartContainerProps, ChartLegend, ChartLegendContent, ChartLegendContentProps, ChartPayloadItem, ChartStyle, ChartStyleProps, ChartTooltip, ChartTooltipContent, ChartTooltipContentProps, useChart } from './chart.js';
2
- export { BaseChartProps, CHART_COLORS, CHART_SERIES_COLORS, ChartColorName, ComitorAreaChart, ComitorAreaChartProps, ComitorBarChart, ComitorBarChartProps, ComitorChartContainer, ComitorChartContainerProps, ComitorChartPayloadItem, ComitorChartTooltip, ComitorChartTooltipProps, ComitorLineChart, ComitorLineChartProps, DataSeries } from './comitor-chart.js';
2
+ export { CHART_COLORS, CHART_SERIES_COLORS, ChartColorName } from './chart-colors.js';
3
+ export { BaseChartProps, ComitorAreaChart, ComitorAreaChartProps, ComitorBarChart, ComitorBarChartProps, ComitorChartContainer, ComitorChartContainerProps, ComitorChartPayloadItem, ComitorChartTooltip, ComitorChartTooltipProps, ComitorLineChart, ComitorLineChartProps, DataSeries } from './comitor-chart.js';
3
4
  import 'react';
4
5
  import 'recharts';
@@ -1,3 +1,4 @@
1
1
  export * from "./chart.js";
2
+ export * from "./chart-colors.js";
2
3
  export * from "./comitor-chart.js";
3
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/chart/index.ts"],"sourcesContent":["/**\n * Entry \"@comitor/ui/chart\" — Chart* (shadcn) + ComitorChart* + CHART_COLORS.\n *\n * Tách riêng vì `recharts` nặng (~100KB) và là peer OPTIONAL; đa số trang không vẽ biểu đồ.\n *\n * ⚠ Va tên đã xử lý ở tầng file: `chart.tsx` giữ tên trần `ChartContainer`/`ChartTooltip`\n * (API shadcn); bản tiện dụng của comitor-ds mang tiền tố `ComitorChartContainer`/\n * `ComitorChartTooltip`. Đừng đặt lại tên trần trong `comitor-chart.tsx`, barrel này\n * dùng `export *` nên sẽ vỡ ngay lúc build.\n */\nexport * from \"./chart.js\";\nexport * from \"./comitor-chart.js\";\n"],"mappings":"AAUA,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../../src/chart/index.ts"],"sourcesContent":["/**\n * Entry \"@comitor/ui/chart\" — Chart* (shadcn) + ComitorChart* + CHART_COLORS.\n *\n * Tách riêng vì `recharts` nặng (~100KB) và là peer OPTIONAL; đa số trang không vẽ biểu đồ.\n *\n * ⚠ Hai đường dùng, hai mặc định khác nhau về hiệu ứng vẽ dần. `Comitor*Chart` tắt sẵn\n * (`isAnimationActive` mặc định `false`). `ChartContainer` KHÔNG tắt hộ được — mark là children\n * do app viết — nên trên recharts 2.15.x app phải tự ghi `isAnimationActive={false}` lên từng\n * `<Bar>`/`<Line>`/`<Area>`, nếu không biểu đồ ra đủ trục đủ lưới mà KHÔNG có dữ liệu, im lặng.\n * Chi tiết + số đo ở JSDoc của `ChartContainer` trong `chart.tsx` và README §4.4.\n *\n * ⚠ Va tên đã xử lý ở tầng file: `chart.tsx` giữ tên trần `ChartContainer`/`ChartTooltip`\n * (API shadcn); bản tiện dụng của comitor-ds mang tiền tố `ComitorChartContainer`/\n * `ComitorChartTooltip`. Đừng đặt lại tên trần trong `comitor-chart.tsx`, barrel này\n * dùng `export *` nên sẽ vỡ ngay lúc build.\n */\n/*\n * ⚠ `chart-colors.js` phải đứng RIÊNG ở đây, không được đi vòng qua `comitor-chart.js`.\n * `comitor-chart.tsx` là file `\"use client\"`; bundler RSC cắt ranh giới theo FILE nên mọi\n * export chảy qua nó — kể cả re-export của một hằng dữ liệu — đều tới Server Component dưới\n * dạng client-reference proxy (`CHART_COLORS.gold` ra `undefined`, im lặng). Xuất thẳng từ\n * module thuần là cách duy nhất để Server Component đọc được giá trị thật.\n */\n\nexport * from \"./chart.js\";\nexport * from \"./chart-colors.js\";\nexport * from \"./comitor-chart.js\";\n"],"mappings":"AAwBA,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Nhãn mặc định của `DataTable` / `DataTableColumnToggle`.
3
+ *
4
+ * Module **CỐ Ý không có `"use client"`** — xem `src/chart/chart-colors.ts` để biết lý do đầy
5
+ * đủ: mọi export của một file `"use client"` đến tay Server Component dưới dạng
6
+ * client-reference proxy, đọc ra `undefined` mà KHÔNG throw, KHÔNG warning. Hằng dữ liệu thuần
7
+ * (nhãn mặc định, preset, danh sách chọn) là thứ trang server hay đọc nhất — spread để override
8
+ * một khoá, đối chiếu giá trị, dựng danh sách — nên phải sống ngoài ranh giới client.
9
+ *
10
+ * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP. `data-table.tsx` chỉ được `import`,
11
+ * KHÔNG được `export` lại (re-export qua file client là vô hiệu, và đụng tên với `export *`).
12
+ */
13
+ /**
14
+ * Toàn bộ chuỗi hiển thị (kể cả chuỗi dành riêng cho trình đọc màn hình) của `DataTable` và
15
+ * `DataTableColumnToggle` — app tiếng Anh (hoặc ngôn ngữ khác) override từng khoá qua prop
16
+ * `labels`. Mặc định là tiếng Việt.
17
+ *
18
+ * Cùng khuôn với `ShellLabels` ở tầng shell: gói dùng chung cho MỌI app Comitor nên chuỗi
19
+ * chôn cứng trong component là ngõ cụt cho app đa ngôn ngữ.
20
+ */
21
+ interface DataTableLabels {
22
+ /** aria-label ô chọn ở đầu bảng khi CHƯA chọn hết */
23
+ selectAllRows: string;
24
+ /** aria-label ô chọn ở đầu bảng khi ĐÃ chọn hết */
25
+ deselectAllRows: string;
26
+ /** aria-label ô chọn của một dòng khi dòng chưa được chọn */
27
+ selectRow: string;
28
+ /** aria-label ô chọn của một dòng khi dòng đã được chọn */
29
+ deselectRow: string;
30
+ /** Tiêu đề trạng thái rỗng mặc định (bỏ qua nếu app truyền `empty`) */
31
+ emptyTitle: string;
32
+ /** Mô tả trạng thái rỗng mặc định */
33
+ emptyDescription: string;
34
+ /** Nhãn nút + tiêu đề menu ẩn/hiện cột */
35
+ columnToggle: string;
36
+ }
37
+ /** Nhãn tiếng Việt mặc định — mọi khoá đều override được qua prop `labels`. */
38
+ declare const DEFAULT_DATA_TABLE_LABELS: DataTableLabels;
39
+
40
+ export { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels };
@@ -0,0 +1,13 @@
1
+ const DEFAULT_DATA_TABLE_LABELS = {
2
+ selectAllRows: "Ch\u1ECDn t\u1EA5t c\u1EA3 d\xF2ng \u0111ang hi\u1EC3n th\u1ECB",
3
+ deselectAllRows: "B\u1ECF ch\u1ECDn t\u1EA5t c\u1EA3",
4
+ selectRow: "Ch\u1ECDn d\xF2ng",
5
+ deselectRow: "B\u1ECF ch\u1ECDn d\xF2ng",
6
+ emptyTitle: "Ch\u01B0a c\xF3 d\u1EEF li\u1EC7u",
7
+ emptyDescription: "Danh s\xE1ch hi\u1EC7n \u0111ang tr\u1ED1ng.",
8
+ columnToggle: "C\u1ED9t hi\u1EC3n th\u1ECB"
9
+ };
10
+ export {
11
+ DEFAULT_DATA_TABLE_LABELS
12
+ };
13
+ //# sourceMappingURL=data-table-constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/composite/data-table-constants.ts"],"sourcesContent":["/**\n * Nhãn mặc định của `DataTable` / `DataTableColumnToggle`.\n *\n * Module **CỐ Ý không có `\"use client\"`** — xem `src/chart/chart-colors.ts` để biết lý do đầy\n * đủ: mọi export của một file `\"use client\"` đến tay Server Component dưới dạng\n * client-reference proxy, đọc ra `undefined` mà KHÔNG throw, KHÔNG warning. Hằng dữ liệu thuần\n * (nhãn mặc định, preset, danh sách chọn) là thứ trang server hay đọc nhất — spread để override\n * một khoá, đối chiếu giá trị, dựng danh sách — nên phải sống ngoài ranh giới client.\n *\n * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP. `data-table.tsx` chỉ được `import`,\n * KHÔNG được `export` lại (re-export qua file client là vô hiệu, và đụng tên với `export *`).\n */\n\n/**\n * Toàn bộ chuỗi hiển thị (kể cả chuỗi dành riêng cho trình đọc màn hình) của `DataTable` và\n * `DataTableColumnToggle` — app tiếng Anh (hoặc ngôn ngữ khác) override từng khoá qua prop\n * `labels`. Mặc định là tiếng Việt.\n *\n * Cùng khuôn với `ShellLabels` ở tầng shell: gói dùng chung cho MỌI app Comitor nên chuỗi\n * chôn cứng trong component là ngõ cụt cho app đa ngôn ngữ.\n */\nexport interface DataTableLabels {\n /** aria-label ô chọn ở đầu bảng khi CHƯA chọn hết */\n selectAllRows: string;\n /** aria-label ô chọn ở đầu bảng khi ĐÃ chọn hết */\n deselectAllRows: string;\n /** aria-label ô chọn của một dòng khi dòng chưa được chọn */\n selectRow: string;\n /** aria-label ô chọn của một dòng khi dòng đã được chọn */\n deselectRow: string;\n /** Tiêu đề trạng thái rỗng mặc định (bỏ qua nếu app truyền `empty`) */\n emptyTitle: string;\n /** Mô tả trạng thái rỗng mặc định */\n emptyDescription: string;\n /** Nhãn nút + tiêu đề menu ẩn/hiện cột */\n columnToggle: string;\n}\n\n/** Nhãn tiếng Việt mặc định — mọi khoá đều override được qua prop `labels`. */\nexport const DEFAULT_DATA_TABLE_LABELS: DataTableLabels = {\n selectAllRows: \"Chọn tất cả dòng đang hiển thị\",\n deselectAllRows: \"Bỏ chọn tất cả\",\n selectRow: \"Chọn dòng\",\n deselectRow: \"Bỏ chọn dòng\",\n emptyTitle: \"Chưa có dữ liệu\",\n emptyDescription: \"Danh sách hiện đang trống.\",\n columnToggle: \"Cột hiển thị\"\n};\n"],"mappings":"AAuCO,MAAM,4BAA6C;AAAA,EACxD,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,cAAc;AAChB;","names":[]}
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { ReactNode } from 'react';
3
+ import { DataTableLabels } from './data-table-constants.js';
3
4
 
4
5
  type SortDirection = "asc" | "desc";
5
6
  interface DataTableSort {
@@ -7,32 +8,6 @@ interface DataTableSort {
7
8
  direction: SortDirection;
8
9
  }
9
10
  type DataTableAlign = "left" | "center" | "right";
10
- /**
11
- * Toàn bộ chuỗi hiển thị (kể cả chuỗi dành riêng cho trình đọc màn hình) của `DataTable` và
12
- * `DataTableColumnToggle` — app tiếng Anh (hoặc ngôn ngữ khác) override từng khoá qua prop
13
- * `labels`. Mặc định là tiếng Việt.
14
- *
15
- * Cùng khuôn với `ShellLabels` ở tầng shell: gói dùng chung cho MỌI app Comitor nên chuỗi
16
- * chôn cứng trong component là ngõ cụt cho app đa ngôn ngữ.
17
- */
18
- interface DataTableLabels {
19
- /** aria-label ô chọn ở đầu bảng khi CHƯA chọn hết */
20
- selectAllRows: string;
21
- /** aria-label ô chọn ở đầu bảng khi ĐÃ chọn hết */
22
- deselectAllRows: string;
23
- /** aria-label ô chọn của một dòng khi dòng chưa được chọn */
24
- selectRow: string;
25
- /** aria-label ô chọn của một dòng khi dòng đã được chọn */
26
- deselectRow: string;
27
- /** Tiêu đề trạng thái rỗng mặc định (bỏ qua nếu app truyền `empty`) */
28
- emptyTitle: string;
29
- /** Mô tả trạng thái rỗng mặc định */
30
- emptyDescription: string;
31
- /** Nhãn nút + tiêu đề menu ẩn/hiện cột */
32
- columnToggle: string;
33
- }
34
- /** Nhãn tiếng Việt mặc định — mọi khoá đều override được qua prop `labels`. */
35
- declare const DEFAULT_DATA_TABLE_LABELS: DataTableLabels;
36
11
  interface DataTableColumn<TRow> {
37
12
  id: string;
38
13
  /** Nội dung ô tiêu đề */
@@ -142,4 +117,4 @@ interface DataTableColumnToggleProps<TRow> {
142
117
  */
143
118
  declare function DataTableColumnToggle<TRow>({ columns, visibleColumnIds, onVisibleColumnIdsChange, label, labels: labelsProp, className }: DataTableColumnToggleProps<TRow>): React.JSX.Element;
144
119
 
145
- export { DEFAULT_DATA_TABLE_LABELS, DataTable, type DataTableAlign, type DataTableColumn, DataTableColumnToggle, type DataTableColumnToggleProps, type DataTableLabels, type DataTableProps, type DataTableSort, type SortDirection };
120
+ export { DataTable, type DataTableAlign, type DataTableColumn, DataTableColumnToggle, type DataTableColumnToggleProps, type DataTableProps, type DataTableSort, type SortDirection };
@@ -14,16 +14,8 @@ import {
14
14
  } from "../ui/dropdown-menu.js";
15
15
  import { Skeleton } from "../ui/skeleton.js";
16
16
  import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table.js";
17
+ import { DEFAULT_DATA_TABLE_LABELS } from "./data-table-constants.js";
17
18
  import { EmptyState } from "./empty-state.js";
18
- const DEFAULT_DATA_TABLE_LABELS = {
19
- selectAllRows: "Ch\u1ECDn t\u1EA5t c\u1EA3 d\xF2ng \u0111ang hi\u1EC3n th\u1ECB",
20
- deselectAllRows: "B\u1ECF ch\u1ECDn t\u1EA5t c\u1EA3",
21
- selectRow: "Ch\u1ECDn d\xF2ng",
22
- deselectRow: "B\u1ECF ch\u1ECDn d\xF2ng",
23
- emptyTitle: "Ch\u01B0a c\xF3 d\u1EEF li\u1EC7u",
24
- emptyDescription: "Danh s\xE1ch hi\u1EC7n \u0111ang tr\u1ED1ng.",
25
- columnToggle: "C\u1ED9t hi\u1EC3n th\u1ECB"
26
- };
27
19
  const alignClasses = {
28
20
  left: "text-left",
29
21
  center: "text-center",
@@ -293,7 +285,6 @@ function DataTableColumnToggle({
293
285
  ] });
294
286
  }
295
287
  export {
296
- DEFAULT_DATA_TABLE_LABELS,
297
288
  DataTable,
298
289
  DataTableColumnToggle
299
290
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/composite/data-table.tsx"],"sourcesContent":["\"use client\";\n\nimport { ArrowDown, ArrowUp, ChevronsUpDown, Inbox, SlidersHorizontal } from \"lucide-react\";\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Checkbox } from \"../ui/checkbox.js\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger\n} from \"../ui/dropdown-menu.js\";\nimport { Skeleton } from \"../ui/skeleton.js\";\nimport { TableBody, TableCell, TableHead, TableHeader, TableRow } from \"../ui/table.js\";\nimport { EmptyState } from \"./empty-state.js\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface DataTableSort {\n columnId: string;\n direction: SortDirection;\n}\n\nexport type DataTableAlign = \"left\" | \"center\" | \"right\";\n\n/**\n * Toàn bộ chuỗi hiển thị (kể cả chuỗi dành riêng cho trình đọc màn hình) của `DataTable` và\n * `DataTableColumnToggle` — app tiếng Anh (hoặc ngôn ngữ khác) override từng khoá qua prop\n * `labels`. Mặc định là tiếng Việt.\n *\n * Cùng khuôn với `ShellLabels` ở tầng shell: gói dùng chung cho MỌI app Comitor nên chuỗi\n * chôn cứng trong component là ngõ cụt cho app đa ngôn ngữ.\n */\nexport interface DataTableLabels {\n /** aria-label ô chọn ở đầu bảng khi CHƯA chọn hết */\n selectAllRows: string;\n /** aria-label ô chọn ở đầu bảng khi ĐÃ chọn hết */\n deselectAllRows: string;\n /** aria-label ô chọn của một dòng khi dòng chưa được chọn */\n selectRow: string;\n /** aria-label ô chọn của một dòng khi dòng đã được chọn */\n deselectRow: string;\n /** Tiêu đề trạng thái rỗng mặc định (bỏ qua nếu app truyền `empty`) */\n emptyTitle: string;\n /** Mô tả trạng thái rỗng mặc định */\n emptyDescription: string;\n /** Nhãn nút + tiêu đề menu ẩn/hiện cột */\n columnToggle: string;\n}\n\n/** Nhãn tiếng Việt mặc định — mọi khoá đều override được qua prop `labels`. */\nexport const DEFAULT_DATA_TABLE_LABELS: DataTableLabels = {\n selectAllRows: \"Chọn tất cả dòng đang hiển thị\",\n deselectAllRows: \"Bỏ chọn tất cả\",\n selectRow: \"Chọn dòng\",\n deselectRow: \"Bỏ chọn dòng\",\n emptyTitle: \"Chưa có dữ liệu\",\n emptyDescription: \"Danh sách hiện đang trống.\",\n columnToggle: \"Cột hiển thị\"\n};\n\nexport interface DataTableColumn<TRow> {\n id: string;\n /** Nội dung ô tiêu đề */\n header: ReactNode;\n /** Nhãn chữ dùng trong menu ẩn/hiện cột — bắt buộc khi `header` không phải chuỗi */\n label?: string;\n /** Render ô dữ liệu — nhận cả chỉ số dòng để đánh số thứ tự */\n cell: (row: TRow, rowIndex: number) => ReactNode;\n sortable?: boolean;\n align?: DataTableAlign;\n /**\n * Bề rộng cột tính bằng px. BẮT BUỘC với cột ghim (`pinned`) vì offset `left/right` của\n * sticky được tính cộng dồn từ giá trị này — thiếu width thì các cột ghim chồng lên nhau.\n */\n width?: number;\n minWidth?: number;\n /** Ghim cột khi cuộn ngang (bảng nhiều cột) */\n pinned?: \"left\" | \"right\";\n /** Cho phép người dùng ẩn cột này (mặc định true; cột định danh nên đặt false) */\n hideable?: boolean;\n headerClassName?: string;\n cellClassName?: string;\n}\n\nexport interface DataTableProps<TRow> {\n columns: DataTableColumn<TRow>[];\n rows: TRow[];\n /** Khoá ổn định của dòng — dùng cho `key` React và cho tập chọn */\n getRowId: (row: TRow, index: number) => string;\n\n /** Đang tải → hiện `loadingRowCount` dòng khung xương thay vì bảng trống */\n loading?: boolean;\n loadingRowCount?: number;\n /** Nội dung khi không có dòng nào (mặc định EmptyState theo `labels.emptyTitle`) */\n empty?: ReactNode;\n\n /** Sắp xếp — controlled; `null` = không sắp xếp. App tự sắp/tự gọi API. */\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n\n /** Chọn dòng bằng checkbox — controlled */\n selectable?: boolean;\n selectedIds?: string[];\n onSelectedIdsChange?: (ids: string[]) => void;\n /** Khoá chọn với một số dòng (vd bản ghi đã khoá) */\n isRowSelectable?: (row: TRow, index: number) => boolean;\n\n /**\n * Mã các cột đang hiện — controlled. Bỏ trống = hiện tất cả. Đổi bố cục đi qua\n * `DataTableColumnToggle`, app tự lưu theo người dùng (server, localStorage…).\n *\n * ⚠ Đây là một **TẬP HỢP**, không phải thứ tự: thứ tự cột luôn lấy từ mảng `columns`, thứ tự\n * bên trong mảng này bị bỏ qua. Tên có hậu tố `Ids` chính là để nói đúng thứ nó chứa —\n * 0.1.0 đặt `visibleColumns` khiến người đọc tưởng là mảng cột (`DataTableColumn[]`) hoặc\n * tưởng sắp lại mảng thì cột đổi chỗ.\n *\n * Cố ý KHÔNG dùng `Record<string, boolean>`: kiểu đó có trạng thái thứ ba — **khoá vắng\n * mặt**. App thêm cột mới ở bản sau thì bố cục người dùng đã lưu không có khoá đó, mỗi nơi\n * tự đoán hiện/ẩn → lỗi im lặng. Một mảng mã cột thì `JSON.stringify` là lưu được.\n *\n * Package cố ý KHÔNG tự ghi localStorage: ở SaaS nhiều workspace, bố cục bảng phải theo\n * cặp (người dùng × workspace) và thường cần đồng bộ giữa các máy — chuyện của app.\n */\n visibleColumnIds?: string[];\n\n /**\n * Bấm vào dòng (vd mở chi tiết bản ghi). Có prop này thì dòng vào chuỗi Tab và nhận\n * Enter/Space — mở chi tiết KHÔNG được là thao tác chỉ-chuột.\n */\n onRowClick?: (row: TRow, index: number) => void;\n rowClassName?: (row: TRow, index: number) => string | undefined;\n /** Dòng đang được làm nổi (vd bản ghi vừa mở ở panel bên) */\n isRowActive?: (row: TRow, index: number) => boolean;\n\n /** Tiêu đề dính đỉnh vùng cuộn (mặc định true) */\n stickyHeader?: boolean;\n /** Bề rộng tối thiểu của bảng — bật cuộn ngang khi màn hình hẹp */\n minWidth?: number | string;\n density?: \"comfortable\" | \"compact\";\n /** Ghi đè chuỗi hiển thị/a11y (mặc định tiếng Việt) — xem `DataTableLabels` */\n labels?: Partial<DataTableLabels>;\n /** Nhãn của bảng cho trình đọc màn hình (bắt buộc khi trang có nhiều bảng) */\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n className?: string;\n /** className của khung cuộn bọc ngoài */\n containerClassName?: string;\n}\n\nconst alignClasses: Record<DataTableAlign, string> = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\"\n};\n\n/** Offset sticky cộng dồn cho cột ghim trái/phải. */\nfunction getPinnedOffsets<TRow>(columns: DataTableColumn<TRow>[], selectable: boolean): Map<string, CSSProperties> {\n const styles = new Map<string, CSSProperties>();\n // Cột checkbox (nếu có) luôn ghim trái và chiếm 44px — cột ghim đầu tiên phải bắt đầu sau nó.\n let left = selectable ? 44 : 0;\n for (const column of columns) {\n if (column.pinned !== \"left\") continue;\n styles.set(column.id, { position: \"sticky\", left, zIndex: 2 });\n left += column.width ?? 0;\n }\n\n let right = 0;\n for (const column of [...columns].reverse()) {\n if (column.pinned !== \"right\") continue;\n styles.set(column.id, { position: \"sticky\", right, zIndex: 2 });\n right += column.width ?? 0;\n }\n\n return styles;\n}\n\nfunction nextSort(current: DataTableSort | null | undefined, columnId: string): DataTableSort | null {\n // asc → desc → bỏ sắp xếp. Vòng 3 trạng thái để người dùng quay lại thứ tự gốc của server\n // mà không phải tải lại trang.\n if (!current || current.columnId !== columnId) return { columnId, direction: \"asc\" };\n if (current.direction === \"asc\") return { columnId, direction: \"desc\" };\n return null;\n}\n\n/**\n * Bảng dữ liệu chuẩn của Comitor: tiêu đề dính, sắp xếp, chọn dòng, ghim cột, ẩn/hiện cột,\n * trạng thái tải và trạng thái rỗng.\n *\n * Mọi trạng thái đều **controlled** — package không giữ dữ liệu, không gọi API, không tự phân\n * trang. Nhờ vậy dùng được cả với dữ liệu client (mảng trong bộ nhớ) lẫn server-side\n * pagination mà không phải viết hai component.\n */\nexport function DataTable<TRow>({\n columns,\n rows,\n getRowId,\n loading = false,\n loadingRowCount = 5,\n empty,\n sort,\n onSortChange,\n selectable = false,\n selectedIds,\n onSelectedIdsChange,\n isRowSelectable,\n visibleColumnIds,\n onRowClick,\n rowClassName,\n isRowActive,\n stickyHeader = true,\n minWidth,\n density = \"comfortable\",\n labels: labelsProp,\n className,\n containerClassName,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby\n}: DataTableProps<TRow>) {\n const labels = { ...DEFAULT_DATA_TABLE_LABELS, ...labelsProp };\n // Thứ tự luôn theo `columns` — `visibleColumnIds` chỉ trả lời \"cột nào được hiện\".\n const shownColumns = visibleColumnIds ? columns.filter((column) => visibleColumnIds.includes(column.id)) : columns;\n const pinnedStyles = getPinnedOffsets(shownColumns, selectable);\n const colCount = shownColumns.length + (selectable ? 1 : 0);\n\n const selected = selectedIds ?? [];\n // Một lượt duyệt: vừa lọc dòng chọn được vừa lấy id — tránh indexOf() lồng trong map (O(n²))\n // và tránh sai khi hai dòng cùng tham chiếu object.\n const selectableIds: string[] = [];\n rows.forEach((row, index) => {\n if (isRowSelectable?.(row, index) ?? true) selectableIds.push(getRowId(row, index));\n });\n const allSelected = selectableIds.length > 0 && selectableIds.every((id) => selected.includes(id));\n const someSelected = !allSelected && selectableIds.some((id) => selected.includes(id));\n\n const toggleAll = () => {\n if (!onSelectedIdsChange) return;\n if (allSelected) {\n onSelectedIdsChange(selected.filter((id) => !selectableIds.includes(id)));\n return;\n }\n onSelectedIdsChange([...new Set([...selected, ...selectableIds])]);\n };\n\n const toggleRow = (id: string) => {\n if (!onSelectedIdsChange) return;\n onSelectedIdsChange(selected.includes(id) ? selected.filter((item) => item !== id) : [...selected, id]);\n };\n\n const cellPadding = density === \"compact\" ? \"px-3 py-1.5\" : \"px-4 py-3\";\n\n return (\n <div\n data-slot=\"data-table-container\"\n className={cn(\"relative w-full overflow-auto rounded-xl border border-border bg-card\", containerClassName)}\n >\n <table\n data-slot=\"data-table\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n aria-busy={loading || undefined}\n className={cn(\"w-full caption-bottom text-sm\", className)}\n style={minWidth != null ? { minWidth } : undefined}\n >\n <TableHeader className={cn(stickyHeader && \"sticky top-0 z-10\")}>\n {/* Nền ĐẶC đặt trên <tr>, không phải <thead>: ô ghim dùng `bg-inherit` mà\n background-color kế thừa từ CHA TRỰC TIẾP (<tr>). Để nền ở <thead> thì <tr> vẫn\n trong suốt → cột ghim nhìn xuyên thấy dữ liệu cuộn phía dưới. */}\n <TableRow className=\"bg-muted hover:bg-muted\">\n {selectable && (\n <TableHead\n className=\"w-11 bg-inherit\"\n style={{ position: \"sticky\", left: 0, zIndex: 3, width: 44, minWidth: 44 }}\n >\n <Checkbox\n checked={allSelected ? true : someSelected ? \"indeterminate\" : false}\n onCheckedChange={toggleAll}\n disabled={selectableIds.length === 0}\n aria-label={allSelected ? labels.deselectAllRows : labels.selectAllRows}\n />\n </TableHead>\n )}\n\n {shownColumns.map((column) => {\n const isSorted = sort?.columnId === column.id;\n const SortIcon = isSorted ? (sort?.direction === \"asc\" ? ArrowUp : ArrowDown) : ChevronsUpDown;\n const pinnedStyle = pinnedStyles.get(column.id);\n\n return (\n <TableHead\n key={column.id}\n scope=\"col\"\n aria-sort={isSorted ? (sort?.direction === \"asc\" ? \"ascending\" : \"descending\") : undefined}\n className={cn(\n alignClasses[column.align ?? \"left\"],\n pinnedStyle && \"bg-inherit\",\n column.headerClassName\n )}\n style={{\n ...pinnedStyle,\n width: column.width,\n minWidth: column.minWidth ?? column.width\n }}\n >\n {column.sortable && onSortChange ? (\n <button\n type=\"button\"\n onClick={() => onSortChange(nextSort(sort, column.id))}\n className={cn(\n \"-mx-1 inline-flex items-center gap-1 rounded-sm px-1 py-0.5 uppercase transition-colors hover:text-foreground\",\n column.align === \"right\" && \"flex-row-reverse\",\n isSorted && \"text-foreground\"\n )}\n >\n {column.header}\n {/* KHÔNG làm mờ icon chưa sắp xếp: nút này còn bấm được nên `opacity-*`\n không được miễn trừ ngưỡng 3:1 của WCAG 1.4.11 cho icon mang nghĩa.\n `opacity-50` cho ra 1,98:1 (light) / 2,23:1 (dark) trên nền `bg-muted`\n của hàng tiêu đề ✗; để nguyên `text-muted-foreground` là 4,89 / 4,86:1 ✓.\n Trạng thái ĐANG sắp xếp vẫn phân biệt được: nút đổi sang\n `text-foreground` và icon đổi hình (ArrowUp/ArrowDown ⇄ ChevronsUpDown). */}\n <SortIcon aria-hidden=\"true\" className=\"size-3.5\" />\n </button>\n ) : (\n column.header\n )}\n </TableHead>\n );\n })}\n </TableRow>\n </TableHeader>\n\n <TableBody>\n {loading &&\n Array.from({ length: loadingRowCount }).map((_, rowIndex) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: dòng khung xương không có danh tính dữ liệu\n <TableRow key={`skeleton-${rowIndex}`} className=\"hover:bg-transparent\">\n {selectable && (\n <TableCell className={cellPadding}>\n <Skeleton className=\"size-4 rounded-sm\" />\n </TableCell>\n )}\n {shownColumns.map((column) => (\n <TableCell key={column.id} className={cellPadding}>\n <Skeleton className=\"h-4 w-full max-w-[12rem]\" />\n </TableCell>\n ))}\n </TableRow>\n ))}\n\n {!loading && rows.length === 0 && (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell colSpan={colCount} className=\"p-0\">\n {empty ?? <EmptyState icon={Inbox} title={labels.emptyTitle} description={labels.emptyDescription} />}\n </TableCell>\n </TableRow>\n )}\n\n {!loading &&\n rows.map((row, rowIndex) => {\n const id = getRowId(row, rowIndex);\n const isSelected = selected.includes(id);\n const canSelect = isRowSelectable?.(row, rowIndex) ?? true;\n const active = isRowActive?.(row, rowIndex) ?? false;\n\n return (\n <TableRow\n key={id}\n data-state={isSelected ? \"selected\" : undefined}\n aria-selected={selectable ? isSelected : undefined}\n onClick={onRowClick ? () => onRowClick(row, rowIndex) : undefined}\n // Dòng bấm được phải dùng được bằng BÀN PHÍM: đưa <tr> vào chuỗi Tab và tự\n // xử lý Enter/Space. Cố ý KHÔNG đặt role=\"button\" — role đó đè role=\"row\"\n // ngầm, trình đọc màn hình sẽ mất cấu trúc bảng (số dòng/cột, tiêu đề cột);\n // dòng vẫn là dòng, chỉ thêm khả năng kích hoạt.\n tabIndex={onRowClick ? 0 : undefined}\n onKeyDown={\n onRowClick\n ? (event) => {\n // Chỉ nhận phím khi CHÍNH <tr> đang focus: Enter/Space bấm trên\n // control bên trong ô (checkbox, nút, link) đã có nghĩa riêng —\n // nổi bọt lên đây sẽ mở chi tiết ngoài ý muốn.\n if (event.target !== event.currentTarget) return;\n if (event.key !== \"Enter\" && event.key !== \" \") return;\n // Space mặc định cuộn trang, Enter có thể submit form bao ngoài.\n event.preventDefault();\n onRowClick(row, rowIndex);\n }\n : undefined\n }\n className={cn(\n // bg-card trên <tr> để ô ghim (`bg-inherit`) có màu đặc mà vẫn đổi theo\n // trạng thái chọn/hover của chính dòng đó.\n \"bg-card\",\n // Viền focus vẽ bằng `outline` chứ không `ring`: <tr> nằm trong bảng\n // border-collapse, box-shadow không được vẽ. Offset ÂM để viền nằm trong\n // lòng dòng, không bị khung cuộn cắt mất ở mép trái/phải.\n // Màu dùng `foreground` chứ không `ring` (gold #E8B824 chỉ ~1,9:1 trên nền\n // card — dưới 3:1 của WCAG 1.4.11 cho thành phần phi-văn-bản).\n onRowClick &&\n \"cursor-pointer focus-visible:outline-2 focus-visible:outline-foreground focus-visible:[outline-offset:-2px]\",\n active && \"bg-app-accent/5\",\n rowClassName?.(row, rowIndex)\n )}\n >\n {selectable && (\n <TableCell\n className={cn(cellPadding, \"bg-inherit\")}\n style={{ position: \"sticky\", left: 0, zIndex: 1, width: 44, minWidth: 44 }}\n >\n {/* Chặn nổi bọt: bấm checkbox không được kích hoạt onRowClick (mở chi tiết) */}\n {/* biome-ignore lint/a11y/noStaticElementInteractions: lớp bọc chỉ chặn sự kiện, tương tác nằm ở Checkbox bên trong */}\n {/* biome-ignore lint/a11y/useKeyWithClickEvents: phím bấm đã do Checkbox xử lý */}\n <span onClick={(event) => event.stopPropagation()}>\n <Checkbox\n checked={isSelected}\n disabled={!canSelect}\n onCheckedChange={() => toggleRow(id)}\n aria-label={isSelected ? labels.deselectRow : labels.selectRow}\n />\n </span>\n </TableCell>\n )}\n\n {shownColumns.map((column) => {\n const pinnedStyle = pinnedStyles.get(column.id);\n return (\n <TableCell\n key={column.id}\n className={cn(\n cellPadding,\n alignClasses[column.align ?? \"left\"],\n pinnedStyle && \"bg-inherit\",\n column.cellClassName\n )}\n style={{ ...pinnedStyle, width: column.width, minWidth: column.minWidth ?? column.width }}\n >\n {column.cell(row, rowIndex)}\n </TableCell>\n );\n })}\n </TableRow>\n );\n })}\n </TableBody>\n </table>\n </div>\n );\n}\n\nexport interface DataTableColumnToggleProps<TRow> {\n columns: DataTableColumn<TRow>[];\n /** Mã các cột đang hiện — cùng nguồn với `DataTable.visibleColumnIds` (tập hợp, không phải thứ tự) */\n visibleColumnIds: string[];\n onVisibleColumnIdsChange: (visibleColumnIds: string[]) => void;\n /** Nhãn nút + tiêu đề menu; không truyền thì lấy `labels.columnToggle` */\n label?: string;\n /** Ghi đè chuỗi hiển thị (mặc định tiếng Việt) — xem `DataTableLabels` */\n labels?: Partial<DataTableLabels>;\n className?: string;\n}\n\n/**\n * Menu ẩn/hiện cột đi kèm `DataTable` (đặt trên thanh công cụ của trang danh sách).\n *\n * Không cho ẩn hết: luôn giữ lại ít nhất một cột, nếu không bảng thành khối trắng vô nghĩa\n * và người dùng không có đường quay lại.\n */\nexport function DataTableColumnToggle<TRow>({\n columns,\n visibleColumnIds,\n onVisibleColumnIdsChange,\n label,\n labels: labelsProp,\n className\n}: DataTableColumnToggleProps<TRow>) {\n const labels = { ...DEFAULT_DATA_TABLE_LABELS, ...labelsProp };\n const text = label ?? labels.columnToggle;\n const hideable = columns.filter((column) => column.hideable !== false);\n\n const toggle = (columnId: string) => {\n // Bật lại một cột thì dựng lại danh sách theo thứ tự `columns` — giữ cho giá trị app lưu\n // luôn cùng một hình dạng, dù `DataTable` đằng nào cũng bỏ qua thứ tự bên trong.\n const next = visibleColumnIds.includes(columnId)\n ? visibleColumnIds.filter((id) => id !== columnId)\n : columns.filter((column) => visibleColumnIds.includes(column.id) || column.id === columnId).map((c) => c.id);\n if (next.length === 0) return;\n onVisibleColumnIdsChange(next);\n };\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className={className} leftIcon={<SlidersHorizontal className=\"size-4\" />}>\n {text}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-52\">\n <DropdownMenuLabel>{text}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {hideable.map((column) => (\n <DropdownMenuCheckboxItem\n key={column.id}\n checked={visibleColumnIds.includes(column.id)}\n onCheckedChange={() => toggle(column.id)}\n onSelect={(event) => event.preventDefault()}\n >\n {column.label ?? (typeof column.header === \"string\" ? column.header : column.id)}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/composite/data-table.tsx"],"sourcesContent":["\"use client\";\n\nimport { ArrowDown, ArrowUp, ChevronsUpDown, Inbox, SlidersHorizontal } from \"lucide-react\";\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Checkbox } from \"../ui/checkbox.js\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger\n} from \"../ui/dropdown-menu.js\";\nimport { Skeleton } from \"../ui/skeleton.js\";\nimport { TableBody, TableCell, TableHead, TableHeader, TableRow } from \"../ui/table.js\";\nimport { type DataTableLabels, DEFAULT_DATA_TABLE_LABELS } from \"./data-table-constants.js\";\nimport { EmptyState } from \"./empty-state.js\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface DataTableSort {\n columnId: string;\n direction: SortDirection;\n}\n\nexport type DataTableAlign = \"left\" | \"center\" | \"right\";\n\nexport interface DataTableColumn<TRow> {\n id: string;\n /** Nội dung ô tiêu đề */\n header: ReactNode;\n /** Nhãn chữ dùng trong menu ẩn/hiện cột — bắt buộc khi `header` không phải chuỗi */\n label?: string;\n /** Render ô dữ liệu — nhận cả chỉ số dòng để đánh số thứ tự */\n cell: (row: TRow, rowIndex: number) => ReactNode;\n sortable?: boolean;\n align?: DataTableAlign;\n /**\n * Bề rộng cột tính bằng px. BẮT BUỘC với cột ghim (`pinned`) vì offset `left/right` của\n * sticky được tính cộng dồn từ giá trị này — thiếu width thì các cột ghim chồng lên nhau.\n */\n width?: number;\n minWidth?: number;\n /** Ghim cột khi cuộn ngang (bảng nhiều cột) */\n pinned?: \"left\" | \"right\";\n /** Cho phép người dùng ẩn cột này (mặc định true; cột định danh nên đặt false) */\n hideable?: boolean;\n headerClassName?: string;\n cellClassName?: string;\n}\n\nexport interface DataTableProps<TRow> {\n columns: DataTableColumn<TRow>[];\n rows: TRow[];\n /** Khoá ổn định của dòng — dùng cho `key` React và cho tập chọn */\n getRowId: (row: TRow, index: number) => string;\n\n /** Đang tải → hiện `loadingRowCount` dòng khung xương thay vì bảng trống */\n loading?: boolean;\n loadingRowCount?: number;\n /** Nội dung khi không có dòng nào (mặc định EmptyState theo `labels.emptyTitle`) */\n empty?: ReactNode;\n\n /** Sắp xếp — controlled; `null` = không sắp xếp. App tự sắp/tự gọi API. */\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n\n /** Chọn dòng bằng checkbox — controlled */\n selectable?: boolean;\n selectedIds?: string[];\n onSelectedIdsChange?: (ids: string[]) => void;\n /** Khoá chọn với một số dòng (vd bản ghi đã khoá) */\n isRowSelectable?: (row: TRow, index: number) => boolean;\n\n /**\n * Mã các cột đang hiện — controlled. Bỏ trống = hiện tất cả. Đổi bố cục đi qua\n * `DataTableColumnToggle`, app tự lưu theo người dùng (server, localStorage…).\n *\n * ⚠ Đây là một **TẬP HỢP**, không phải thứ tự: thứ tự cột luôn lấy từ mảng `columns`, thứ tự\n * bên trong mảng này bị bỏ qua. Tên có hậu tố `Ids` chính là để nói đúng thứ nó chứa —\n * 0.1.0 đặt `visibleColumns` khiến người đọc tưởng là mảng cột (`DataTableColumn[]`) hoặc\n * tưởng sắp lại mảng thì cột đổi chỗ.\n *\n * Cố ý KHÔNG dùng `Record<string, boolean>`: kiểu đó có trạng thái thứ ba — **khoá vắng\n * mặt**. App thêm cột mới ở bản sau thì bố cục người dùng đã lưu không có khoá đó, mỗi nơi\n * tự đoán hiện/ẩn → lỗi im lặng. Một mảng mã cột thì `JSON.stringify` là lưu được.\n *\n * Package cố ý KHÔNG tự ghi localStorage: ở SaaS nhiều workspace, bố cục bảng phải theo\n * cặp (người dùng × workspace) và thường cần đồng bộ giữa các máy — chuyện của app.\n */\n visibleColumnIds?: string[];\n\n /**\n * Bấm vào dòng (vd mở chi tiết bản ghi). Có prop này thì dòng vào chuỗi Tab và nhận\n * Enter/Space — mở chi tiết KHÔNG được là thao tác chỉ-chuột.\n */\n onRowClick?: (row: TRow, index: number) => void;\n rowClassName?: (row: TRow, index: number) => string | undefined;\n /** Dòng đang được làm nổi (vd bản ghi vừa mở ở panel bên) */\n isRowActive?: (row: TRow, index: number) => boolean;\n\n /** Tiêu đề dính đỉnh vùng cuộn (mặc định true) */\n stickyHeader?: boolean;\n /** Bề rộng tối thiểu của bảng — bật cuộn ngang khi màn hình hẹp */\n minWidth?: number | string;\n density?: \"comfortable\" | \"compact\";\n /** Ghi đè chuỗi hiển thị/a11y (mặc định tiếng Việt) — xem `DataTableLabels` */\n labels?: Partial<DataTableLabels>;\n /** Nhãn của bảng cho trình đọc màn hình (bắt buộc khi trang có nhiều bảng) */\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n className?: string;\n /** className của khung cuộn bọc ngoài */\n containerClassName?: string;\n}\n\nconst alignClasses: Record<DataTableAlign, string> = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\"\n};\n\n/** Offset sticky cộng dồn cho cột ghim trái/phải. */\nfunction getPinnedOffsets<TRow>(columns: DataTableColumn<TRow>[], selectable: boolean): Map<string, CSSProperties> {\n const styles = new Map<string, CSSProperties>();\n // Cột checkbox (nếu có) luôn ghim trái và chiếm 44px — cột ghim đầu tiên phải bắt đầu sau nó.\n let left = selectable ? 44 : 0;\n for (const column of columns) {\n if (column.pinned !== \"left\") continue;\n styles.set(column.id, { position: \"sticky\", left, zIndex: 2 });\n left += column.width ?? 0;\n }\n\n let right = 0;\n for (const column of [...columns].reverse()) {\n if (column.pinned !== \"right\") continue;\n styles.set(column.id, { position: \"sticky\", right, zIndex: 2 });\n right += column.width ?? 0;\n }\n\n return styles;\n}\n\nfunction nextSort(current: DataTableSort | null | undefined, columnId: string): DataTableSort | null {\n // asc → desc → bỏ sắp xếp. Vòng 3 trạng thái để người dùng quay lại thứ tự gốc của server\n // mà không phải tải lại trang.\n if (!current || current.columnId !== columnId) return { columnId, direction: \"asc\" };\n if (current.direction === \"asc\") return { columnId, direction: \"desc\" };\n return null;\n}\n\n/**\n * Bảng dữ liệu chuẩn của Comitor: tiêu đề dính, sắp xếp, chọn dòng, ghim cột, ẩn/hiện cột,\n * trạng thái tải và trạng thái rỗng.\n *\n * Mọi trạng thái đều **controlled** — package không giữ dữ liệu, không gọi API, không tự phân\n * trang. Nhờ vậy dùng được cả với dữ liệu client (mảng trong bộ nhớ) lẫn server-side\n * pagination mà không phải viết hai component.\n */\nexport function DataTable<TRow>({\n columns,\n rows,\n getRowId,\n loading = false,\n loadingRowCount = 5,\n empty,\n sort,\n onSortChange,\n selectable = false,\n selectedIds,\n onSelectedIdsChange,\n isRowSelectable,\n visibleColumnIds,\n onRowClick,\n rowClassName,\n isRowActive,\n stickyHeader = true,\n minWidth,\n density = \"comfortable\",\n labels: labelsProp,\n className,\n containerClassName,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby\n}: DataTableProps<TRow>) {\n const labels = { ...DEFAULT_DATA_TABLE_LABELS, ...labelsProp };\n // Thứ tự luôn theo `columns` — `visibleColumnIds` chỉ trả lời \"cột nào được hiện\".\n const shownColumns = visibleColumnIds ? columns.filter((column) => visibleColumnIds.includes(column.id)) : columns;\n const pinnedStyles = getPinnedOffsets(shownColumns, selectable);\n const colCount = shownColumns.length + (selectable ? 1 : 0);\n\n const selected = selectedIds ?? [];\n // Một lượt duyệt: vừa lọc dòng chọn được vừa lấy id — tránh indexOf() lồng trong map (O(n²))\n // và tránh sai khi hai dòng cùng tham chiếu object.\n const selectableIds: string[] = [];\n rows.forEach((row, index) => {\n if (isRowSelectable?.(row, index) ?? true) selectableIds.push(getRowId(row, index));\n });\n const allSelected = selectableIds.length > 0 && selectableIds.every((id) => selected.includes(id));\n const someSelected = !allSelected && selectableIds.some((id) => selected.includes(id));\n\n const toggleAll = () => {\n if (!onSelectedIdsChange) return;\n if (allSelected) {\n onSelectedIdsChange(selected.filter((id) => !selectableIds.includes(id)));\n return;\n }\n onSelectedIdsChange([...new Set([...selected, ...selectableIds])]);\n };\n\n const toggleRow = (id: string) => {\n if (!onSelectedIdsChange) return;\n onSelectedIdsChange(selected.includes(id) ? selected.filter((item) => item !== id) : [...selected, id]);\n };\n\n const cellPadding = density === \"compact\" ? \"px-3 py-1.5\" : \"px-4 py-3\";\n\n return (\n <div\n data-slot=\"data-table-container\"\n className={cn(\"relative w-full overflow-auto rounded-xl border border-border bg-card\", containerClassName)}\n >\n <table\n data-slot=\"data-table\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n aria-busy={loading || undefined}\n className={cn(\"w-full caption-bottom text-sm\", className)}\n style={minWidth != null ? { minWidth } : undefined}\n >\n <TableHeader className={cn(stickyHeader && \"sticky top-0 z-10\")}>\n {/* Nền ĐẶC đặt trên <tr>, không phải <thead>: ô ghim dùng `bg-inherit` mà\n background-color kế thừa từ CHA TRỰC TIẾP (<tr>). Để nền ở <thead> thì <tr> vẫn\n trong suốt → cột ghim nhìn xuyên thấy dữ liệu cuộn phía dưới. */}\n <TableRow className=\"bg-muted hover:bg-muted\">\n {selectable && (\n <TableHead\n className=\"w-11 bg-inherit\"\n style={{ position: \"sticky\", left: 0, zIndex: 3, width: 44, minWidth: 44 }}\n >\n <Checkbox\n checked={allSelected ? true : someSelected ? \"indeterminate\" : false}\n onCheckedChange={toggleAll}\n disabled={selectableIds.length === 0}\n aria-label={allSelected ? labels.deselectAllRows : labels.selectAllRows}\n />\n </TableHead>\n )}\n\n {shownColumns.map((column) => {\n const isSorted = sort?.columnId === column.id;\n const SortIcon = isSorted ? (sort?.direction === \"asc\" ? ArrowUp : ArrowDown) : ChevronsUpDown;\n const pinnedStyle = pinnedStyles.get(column.id);\n\n return (\n <TableHead\n key={column.id}\n scope=\"col\"\n aria-sort={isSorted ? (sort?.direction === \"asc\" ? \"ascending\" : \"descending\") : undefined}\n className={cn(\n alignClasses[column.align ?? \"left\"],\n pinnedStyle && \"bg-inherit\",\n column.headerClassName\n )}\n style={{\n ...pinnedStyle,\n width: column.width,\n minWidth: column.minWidth ?? column.width\n }}\n >\n {column.sortable && onSortChange ? (\n <button\n type=\"button\"\n onClick={() => onSortChange(nextSort(sort, column.id))}\n className={cn(\n \"-mx-1 inline-flex items-center gap-1 rounded-sm px-1 py-0.5 uppercase transition-colors hover:text-foreground\",\n column.align === \"right\" && \"flex-row-reverse\",\n isSorted && \"text-foreground\"\n )}\n >\n {column.header}\n {/* KHÔNG làm mờ icon chưa sắp xếp: nút này còn bấm được nên `opacity-*`\n không được miễn trừ ngưỡng 3:1 của WCAG 1.4.11 cho icon mang nghĩa.\n `opacity-50` cho ra 1,98:1 (light) / 2,23:1 (dark) trên nền `bg-muted`\n của hàng tiêu đề ✗; để nguyên `text-muted-foreground` là 4,89 / 4,86:1 ✓.\n Trạng thái ĐANG sắp xếp vẫn phân biệt được: nút đổi sang\n `text-foreground` và icon đổi hình (ArrowUp/ArrowDown ⇄ ChevronsUpDown). */}\n <SortIcon aria-hidden=\"true\" className=\"size-3.5\" />\n </button>\n ) : (\n column.header\n )}\n </TableHead>\n );\n })}\n </TableRow>\n </TableHeader>\n\n <TableBody>\n {loading &&\n Array.from({ length: loadingRowCount }).map((_, rowIndex) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: dòng khung xương không có danh tính dữ liệu\n <TableRow key={`skeleton-${rowIndex}`} className=\"hover:bg-transparent\">\n {selectable && (\n <TableCell className={cellPadding}>\n <Skeleton className=\"size-4 rounded-sm\" />\n </TableCell>\n )}\n {shownColumns.map((column) => (\n <TableCell key={column.id} className={cellPadding}>\n <Skeleton className=\"h-4 w-full max-w-[12rem]\" />\n </TableCell>\n ))}\n </TableRow>\n ))}\n\n {!loading && rows.length === 0 && (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell colSpan={colCount} className=\"p-0\">\n {empty ?? <EmptyState icon={Inbox} title={labels.emptyTitle} description={labels.emptyDescription} />}\n </TableCell>\n </TableRow>\n )}\n\n {!loading &&\n rows.map((row, rowIndex) => {\n const id = getRowId(row, rowIndex);\n const isSelected = selected.includes(id);\n const canSelect = isRowSelectable?.(row, rowIndex) ?? true;\n const active = isRowActive?.(row, rowIndex) ?? false;\n\n return (\n <TableRow\n key={id}\n data-state={isSelected ? \"selected\" : undefined}\n aria-selected={selectable ? isSelected : undefined}\n onClick={onRowClick ? () => onRowClick(row, rowIndex) : undefined}\n // Dòng bấm được phải dùng được bằng BÀN PHÍM: đưa <tr> vào chuỗi Tab và tự\n // xử lý Enter/Space. Cố ý KHÔNG đặt role=\"button\" — role đó đè role=\"row\"\n // ngầm, trình đọc màn hình sẽ mất cấu trúc bảng (số dòng/cột, tiêu đề cột);\n // dòng vẫn là dòng, chỉ thêm khả năng kích hoạt.\n tabIndex={onRowClick ? 0 : undefined}\n onKeyDown={\n onRowClick\n ? (event) => {\n // Chỉ nhận phím khi CHÍNH <tr> đang focus: Enter/Space bấm trên\n // control bên trong ô (checkbox, nút, link) đã có nghĩa riêng —\n // nổi bọt lên đây sẽ mở chi tiết ngoài ý muốn.\n if (event.target !== event.currentTarget) return;\n if (event.key !== \"Enter\" && event.key !== \" \") return;\n // Space mặc định cuộn trang, Enter có thể submit form bao ngoài.\n event.preventDefault();\n onRowClick(row, rowIndex);\n }\n : undefined\n }\n className={cn(\n // bg-card trên <tr> để ô ghim (`bg-inherit`) có màu đặc mà vẫn đổi theo\n // trạng thái chọn/hover của chính dòng đó.\n \"bg-card\",\n // Viền focus vẽ bằng `outline` chứ không `ring`: <tr> nằm trong bảng\n // border-collapse, box-shadow không được vẽ. Offset ÂM để viền nằm trong\n // lòng dòng, không bị khung cuộn cắt mất ở mép trái/phải.\n // Màu dùng `foreground` chứ không `ring` (gold #E8B824 chỉ ~1,9:1 trên nền\n // card — dưới 3:1 của WCAG 1.4.11 cho thành phần phi-văn-bản).\n onRowClick &&\n \"cursor-pointer focus-visible:outline-2 focus-visible:outline-foreground focus-visible:[outline-offset:-2px]\",\n active && \"bg-app-accent/5\",\n rowClassName?.(row, rowIndex)\n )}\n >\n {selectable && (\n <TableCell\n className={cn(cellPadding, \"bg-inherit\")}\n style={{ position: \"sticky\", left: 0, zIndex: 1, width: 44, minWidth: 44 }}\n >\n {/* Chặn nổi bọt: bấm checkbox không được kích hoạt onRowClick (mở chi tiết) */}\n {/* biome-ignore lint/a11y/noStaticElementInteractions: lớp bọc chỉ chặn sự kiện, tương tác nằm ở Checkbox bên trong */}\n {/* biome-ignore lint/a11y/useKeyWithClickEvents: phím bấm đã do Checkbox xử lý */}\n <span onClick={(event) => event.stopPropagation()}>\n <Checkbox\n checked={isSelected}\n disabled={!canSelect}\n onCheckedChange={() => toggleRow(id)}\n aria-label={isSelected ? labels.deselectRow : labels.selectRow}\n />\n </span>\n </TableCell>\n )}\n\n {shownColumns.map((column) => {\n const pinnedStyle = pinnedStyles.get(column.id);\n return (\n <TableCell\n key={column.id}\n className={cn(\n cellPadding,\n alignClasses[column.align ?? \"left\"],\n pinnedStyle && \"bg-inherit\",\n column.cellClassName\n )}\n style={{ ...pinnedStyle, width: column.width, minWidth: column.minWidth ?? column.width }}\n >\n {column.cell(row, rowIndex)}\n </TableCell>\n );\n })}\n </TableRow>\n );\n })}\n </TableBody>\n </table>\n </div>\n );\n}\n\nexport interface DataTableColumnToggleProps<TRow> {\n columns: DataTableColumn<TRow>[];\n /** Mã các cột đang hiện — cùng nguồn với `DataTable.visibleColumnIds` (tập hợp, không phải thứ tự) */\n visibleColumnIds: string[];\n onVisibleColumnIdsChange: (visibleColumnIds: string[]) => void;\n /** Nhãn nút + tiêu đề menu; không truyền thì lấy `labels.columnToggle` */\n label?: string;\n /** Ghi đè chuỗi hiển thị (mặc định tiếng Việt) — xem `DataTableLabels` */\n labels?: Partial<DataTableLabels>;\n className?: string;\n}\n\n/**\n * Menu ẩn/hiện cột đi kèm `DataTable` (đặt trên thanh công cụ của trang danh sách).\n *\n * Không cho ẩn hết: luôn giữ lại ít nhất một cột, nếu không bảng thành khối trắng vô nghĩa\n * và người dùng không có đường quay lại.\n */\nexport function DataTableColumnToggle<TRow>({\n columns,\n visibleColumnIds,\n onVisibleColumnIdsChange,\n label,\n labels: labelsProp,\n className\n}: DataTableColumnToggleProps<TRow>) {\n const labels = { ...DEFAULT_DATA_TABLE_LABELS, ...labelsProp };\n const text = label ?? labels.columnToggle;\n const hideable = columns.filter((column) => column.hideable !== false);\n\n const toggle = (columnId: string) => {\n // Bật lại một cột thì dựng lại danh sách theo thứ tự `columns` — giữ cho giá trị app lưu\n // luôn cùng một hình dạng, dù `DataTable` đằng nào cũng bỏ qua thứ tự bên trong.\n const next = visibleColumnIds.includes(columnId)\n ? visibleColumnIds.filter((id) => id !== columnId)\n : columns.filter((column) => visibleColumnIds.includes(column.id) || column.id === columnId).map((c) => c.id);\n if (next.length === 0) return;\n onVisibleColumnIdsChange(next);\n };\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className={className} leftIcon={<SlidersHorizontal className=\"size-4\" />}>\n {text}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-52\">\n <DropdownMenuLabel>{text}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {hideable.map((column) => (\n <DropdownMenuCheckboxItem\n key={column.id}\n checked={visibleColumnIds.includes(column.id)}\n onCheckedChange={() => toggle(column.id)}\n onSelect={(event) => event.preventDefault()}\n >\n {column.label ?? (typeof column.header === \"string\" ? column.header : column.id)}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,21 @@
1
+ import { DateRange } from 'react-day-picker';
2
+
3
+ /** Định dạng ngày của Việt Nam — dùng thống nhất ở mọi app Comitor. */
4
+ declare const VI_DATE_FORMAT = "dd/MM/yyyy";
5
+ interface DatePreset {
6
+ id: string;
7
+ label: string;
8
+ /** Trả về ngày (DatePicker) hoặc khoảng ngày (DateRangePicker) tại thời điểm bấm */
9
+ getValue: () => Date;
10
+ }
11
+ interface DateRangePreset {
12
+ id: string;
13
+ label: string;
14
+ getValue: () => DateRange;
15
+ }
16
+ /** Preset mặc định cho ô chọn một ngày. */
17
+ declare const DEFAULT_DATE_PRESETS: DatePreset[];
18
+ /** Preset mặc định cho ô chọn khoảng ngày (báo cáo, bộ lọc danh sách). */
19
+ declare const DEFAULT_DATE_RANGE_PRESETS: DateRangePreset[];
20
+
21
+ export { DEFAULT_DATE_PRESETS, DEFAULT_DATE_RANGE_PRESETS, type DatePreset, type DateRangePreset, VI_DATE_FORMAT };
@@ -0,0 +1,39 @@
1
+ import { endOfMonth, startOfDay, startOfMonth, subDays, subMonths } from "date-fns";
2
+ const VI_DATE_FORMAT = "dd/MM/yyyy";
3
+ const DEFAULT_DATE_PRESETS = [
4
+ { id: "today", label: "H\xF4m nay", getValue: () => startOfDay(/* @__PURE__ */ new Date()) },
5
+ { id: "yesterday", label: "H\xF4m qua", getValue: () => startOfDay(subDays(/* @__PURE__ */ new Date(), 1)) },
6
+ { id: "week-ago", label: "7 ng\xE0y tr\u01B0\u1EDBc", getValue: () => startOfDay(subDays(/* @__PURE__ */ new Date(), 7)) }
7
+ ];
8
+ const DEFAULT_DATE_RANGE_PRESETS = [
9
+ { id: "today", label: "H\xF4m nay", getValue: () => ({ from: startOfDay(/* @__PURE__ */ new Date()), to: startOfDay(/* @__PURE__ */ new Date()) }) },
10
+ {
11
+ id: "last-7",
12
+ label: "7 ng\xE0y qua",
13
+ getValue: () => ({ from: startOfDay(subDays(/* @__PURE__ */ new Date(), 6)), to: startOfDay(/* @__PURE__ */ new Date()) })
14
+ },
15
+ {
16
+ id: "last-30",
17
+ label: "30 ng\xE0y qua",
18
+ getValue: () => ({ from: startOfDay(subDays(/* @__PURE__ */ new Date(), 29)), to: startOfDay(/* @__PURE__ */ new Date()) })
19
+ },
20
+ {
21
+ id: "this-month",
22
+ label: "Th\xE1ng n\xE0y",
23
+ getValue: () => ({ from: startOfMonth(/* @__PURE__ */ new Date()), to: startOfDay(/* @__PURE__ */ new Date()) })
24
+ },
25
+ {
26
+ id: "last-month",
27
+ label: "Th\xE1ng tr\u01B0\u1EDBc",
28
+ getValue: () => {
29
+ const previous = subMonths(/* @__PURE__ */ new Date(), 1);
30
+ return { from: startOfMonth(previous), to: endOfMonth(previous) };
31
+ }
32
+ }
33
+ ];
34
+ export {
35
+ DEFAULT_DATE_PRESETS,
36
+ DEFAULT_DATE_RANGE_PRESETS,
37
+ VI_DATE_FORMAT
38
+ };
39
+ //# sourceMappingURL=date-picker-constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/composite/date-picker-constants.ts"],"sourcesContent":["/**\n * Định dạng ngày và preset mặc định của `DatePicker` / `DateRangePicker`.\n *\n * Module **CỐ Ý không có `\"use client\"`** — xem `src/chart/chart-colors.ts` để biết lý do đầy\n * đủ: mọi export của một file `\"use client\"` đến tay Server Component dưới dạng\n * client-reference proxy, đọc ra `undefined` mà KHÔNG throw, KHÔNG warning. Hằng dữ liệu thuần\n * (nhãn mặc định, preset, sentinel) là thứ trang server hay đọc nhất — spread để override một\n * khoá, đối chiếu giá trị, dựng danh sách — nên phải sống ngoài ranh giới client.\n *\n * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP. `date-picker.tsx` chỉ được `import`,\n * KHÔNG được `export` lại (re-export qua file client là vô hiệu, và đụng tên với `export *`).\n */\nimport { endOfMonth, startOfDay, startOfMonth, subDays, subMonths } from \"date-fns\";\nimport type { DateRange } from \"react-day-picker\";\n\n/** Định dạng ngày của Việt Nam — dùng thống nhất ở mọi app Comitor. */\nexport const VI_DATE_FORMAT = \"dd/MM/yyyy\";\n\nexport interface DatePreset {\n id: string;\n label: string;\n /** Trả về ngày (DatePicker) hoặc khoảng ngày (DateRangePicker) tại thời điểm bấm */\n getValue: () => Date;\n}\n\nexport interface DateRangePreset {\n id: string;\n label: string;\n getValue: () => DateRange;\n}\n\n/** Preset mặc định cho ô chọn một ngày. */\nexport const DEFAULT_DATE_PRESETS: DatePreset[] = [\n { id: \"today\", label: \"Hôm nay\", getValue: () => startOfDay(new Date()) },\n { id: \"yesterday\", label: \"Hôm qua\", getValue: () => startOfDay(subDays(new Date(), 1)) },\n { id: \"week-ago\", label: \"7 ngày trước\", getValue: () => startOfDay(subDays(new Date(), 7)) }\n];\n\n/** Preset mặc định cho ô chọn khoảng ngày (báo cáo, bộ lọc danh sách). */\nexport const DEFAULT_DATE_RANGE_PRESETS: DateRangePreset[] = [\n { id: \"today\", label: \"Hôm nay\", getValue: () => ({ from: startOfDay(new Date()), to: startOfDay(new Date()) }) },\n {\n id: \"last-7\",\n label: \"7 ngày qua\",\n getValue: () => ({ from: startOfDay(subDays(new Date(), 6)), to: startOfDay(new Date()) })\n },\n {\n id: \"last-30\",\n label: \"30 ngày qua\",\n getValue: () => ({ from: startOfDay(subDays(new Date(), 29)), to: startOfDay(new Date()) })\n },\n {\n id: \"this-month\",\n label: \"Tháng này\",\n getValue: () => ({ from: startOfMonth(new Date()), to: startOfDay(new Date()) })\n },\n {\n id: \"last-month\",\n label: \"Tháng trước\",\n getValue: () => {\n const previous = subMonths(new Date(), 1);\n return { from: startOfMonth(previous), to: endOfMonth(previous) };\n }\n }\n];\n"],"mappings":"AAYA,SAAS,YAAY,YAAY,cAAc,SAAS,iBAAiB;AAIlE,MAAM,iBAAiB;AAgBvB,MAAM,uBAAqC;AAAA,EAChD,EAAE,IAAI,SAAS,OAAO,cAAW,UAAU,MAAM,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EACxE,EAAE,IAAI,aAAa,OAAO,cAAW,UAAU,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,CAAC,CAAC,EAAE;AAAA,EACxF,EAAE,IAAI,YAAY,OAAO,6BAAgB,UAAU,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,CAAC,CAAC,EAAE;AAC9F;AAGO,MAAM,6BAAgD;AAAA,EAC3D,EAAE,IAAI,SAAS,OAAO,cAAW,UAAU,OAAO,EAAE,MAAM,WAAW,oBAAI,KAAK,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE,GAAG;AAAA,EAChH;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,OAAO,EAAE,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EAC1F;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,OAAO,EAAE,MAAM,WAAW,QAAQ,oBAAI,KAAK,GAAG,EAAE,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EAC3F;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,OAAO,EAAE,MAAM,aAAa,oBAAI,KAAK,CAAC,GAAG,IAAI,WAAW,oBAAI,KAAK,CAAC,EAAE;AAAA,EAChF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU,MAAM;AACd,YAAM,WAAW,UAAU,oBAAI,KAAK,GAAG,CAAC;AACxC,aAAO,EAAE,MAAM,aAAa,QAAQ,GAAG,IAAI,WAAW,QAAQ,EAAE;AAAA,IAClE;AAAA,EACF;AACF;","names":[]}
@@ -1,24 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import { DateRange } from 'react-day-picker';
3
3
  export { DateRange } from 'react-day-picker';
4
+ import { DatePreset, DateRangePreset } from './date-picker-constants.js';
4
5
 
5
- /** Định dạng ngày của Việt Nam — dùng thống nhất ở mọi app Comitor. */
6
- declare const VI_DATE_FORMAT = "dd/MM/yyyy";
7
- interface DatePreset {
8
- id: string;
9
- label: string;
10
- /** Trả về ngày (DatePicker) hoặc khoảng ngày (DateRangePicker) tại thời điểm bấm */
11
- getValue: () => Date;
12
- }
13
- interface DateRangePreset {
14
- id: string;
15
- label: string;
16
- getValue: () => DateRange;
17
- }
18
- /** Preset mặc định cho ô chọn một ngày. */
19
- declare const DEFAULT_DATE_PRESETS: DatePreset[];
20
- /** Preset mặc định cho ô chọn khoảng ngày (báo cáo, bộ lọc danh sách). */
21
- declare const DEFAULT_DATE_RANGE_PRESETS: DateRangePreset[];
22
6
  interface DatePickerProps {
23
7
  value?: Date | null;
24
8
  onValueChange: (value: Date | null) => void;
@@ -74,4 +58,4 @@ interface DateRangePickerProps {
74
58
  */
75
59
  declare function DateRangePicker({ value, onValueChange, placeholder, disabled, minDate, maxDate, clearable, presets, numberOfMonths, className, id, ...aria }: DateRangePickerProps): React.JSX.Element;
76
60
 
77
- export { DEFAULT_DATE_PRESETS, DEFAULT_DATE_RANGE_PRESETS, DatePicker, type DatePickerProps, type DatePreset, DateRangePicker, type DateRangePickerProps, type DateRangePreset, VI_DATE_FORMAT };
61
+ export { DatePicker, type DatePickerProps, DateRangePicker, type DateRangePickerProps };
@@ -1,17 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import {
4
- endOfMonth,
5
- format as formatDate,
6
- isAfter,
7
- isBefore,
8
- isValid,
9
- parse,
10
- startOfDay,
11
- startOfMonth,
12
- subDays,
13
- subMonths
14
- } from "date-fns";
3
+ import { format as formatDate, isAfter, isBefore, isValid, parse, startOfDay } from "date-fns";
15
4
  import { vi } from "date-fns/locale";
16
5
  import { CalendarDays, X } from "lucide-react";
17
6
  import { useEffect, useState } from "react";
@@ -20,38 +9,10 @@ import { Button } from "../ui/button.js";
20
9
  import { Calendar } from "../ui/calendar.js";
21
10
  import { Input } from "../ui/input.js";
22
11
  import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
23
- const VI_DATE_FORMAT = "dd/MM/yyyy";
24
- const DEFAULT_DATE_PRESETS = [
25
- { id: "today", label: "H\xF4m nay", getValue: () => startOfDay(/* @__PURE__ */ new Date()) },
26
- { id: "yesterday", label: "H\xF4m qua", getValue: () => startOfDay(subDays(/* @__PURE__ */ new Date(), 1)) },
27
- { id: "week-ago", label: "7 ng\xE0y tr\u01B0\u1EDBc", getValue: () => startOfDay(subDays(/* @__PURE__ */ new Date(), 7)) }
28
- ];
29
- const DEFAULT_DATE_RANGE_PRESETS = [
30
- { id: "today", label: "H\xF4m nay", getValue: () => ({ from: startOfDay(/* @__PURE__ */ new Date()), to: startOfDay(/* @__PURE__ */ new Date()) }) },
31
- {
32
- id: "last-7",
33
- label: "7 ng\xE0y qua",
34
- getValue: () => ({ from: startOfDay(subDays(/* @__PURE__ */ new Date(), 6)), to: startOfDay(/* @__PURE__ */ new Date()) })
35
- },
36
- {
37
- id: "last-30",
38
- label: "30 ng\xE0y qua",
39
- getValue: () => ({ from: startOfDay(subDays(/* @__PURE__ */ new Date(), 29)), to: startOfDay(/* @__PURE__ */ new Date()) })
40
- },
41
- {
42
- id: "this-month",
43
- label: "Th\xE1ng n\xE0y",
44
- getValue: () => ({ from: startOfMonth(/* @__PURE__ */ new Date()), to: startOfDay(/* @__PURE__ */ new Date()) })
45
- },
46
- {
47
- id: "last-month",
48
- label: "Th\xE1ng tr\u01B0\u1EDBc",
49
- getValue: () => {
50
- const previous = subMonths(/* @__PURE__ */ new Date(), 1);
51
- return { from: startOfMonth(previous), to: endOfMonth(previous) };
52
- }
53
- }
54
- ];
12
+ import {
13
+ DEFAULT_DATE_RANGE_PRESETS,
14
+ VI_DATE_FORMAT
15
+ } from "./date-picker-constants.js";
55
16
  function isOutOfRange(date, minDate, maxDate) {
56
17
  if (minDate && isBefore(date, startOfDay(minDate))) return true;
57
18
  if (maxDate && isAfter(date, startOfDay(maxDate))) return true;
@@ -259,10 +220,7 @@ function DateRangePicker({
259
220
  );
260
221
  }
261
222
  export {
262
- DEFAULT_DATE_PRESETS,
263
- DEFAULT_DATE_RANGE_PRESETS,
264
223
  DatePicker,
265
- DateRangePicker,
266
- VI_DATE_FORMAT
224
+ DateRangePicker
267
225
  };
268
226
  //# sourceMappingURL=date-picker.js.map