@apotech-os/ui-sdk 0.10.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1466 -129
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +302 -18
- package/dist/index.d.ts +302 -18
- package/dist/index.js +1412 -91
- package/dist/index.js.map +1 -1
- package/package.json +10 -4
package/dist/index.cjs
CHANGED
|
@@ -2,14 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
var clsx = require('clsx');
|
|
4
4
|
var tailwindMerge = require('tailwind-merge');
|
|
5
|
-
var
|
|
5
|
+
var React2 = require('react');
|
|
6
6
|
var dialog = require('@base-ui/react/dialog');
|
|
7
7
|
var menu = require('@base-ui/react/menu');
|
|
8
|
+
var checkbox = require('@base-ui/react/checkbox');
|
|
8
9
|
var popover = require('@base-ui/react/popover');
|
|
10
|
+
var select = require('@base-ui/react/select');
|
|
9
11
|
var _switch = require('@base-ui/react/switch');
|
|
10
12
|
var tabs = require('@base-ui/react/tabs');
|
|
11
13
|
var classVarianceAuthority = require('class-variance-authority');
|
|
12
14
|
var jsxRuntime = require('react/jsx-runtime');
|
|
15
|
+
var reactTable = require('@tanstack/react-table');
|
|
16
|
+
var reactVirtual = require('@tanstack/react-virtual');
|
|
17
|
+
var fr = require('date-fns/locale/fr');
|
|
18
|
+
var reactDayPicker = require('react-day-picker');
|
|
19
|
+
var dateFns = require('date-fns');
|
|
20
|
+
var recharts = require('recharts');
|
|
13
21
|
|
|
14
22
|
function _interopNamespace(e) {
|
|
15
23
|
if (e && e.__esModule) return e;
|
|
@@ -29,16 +37,16 @@ function _interopNamespace(e) {
|
|
|
29
37
|
return Object.freeze(n);
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
var
|
|
40
|
+
var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
|
|
33
41
|
|
|
34
42
|
// src/cn.ts
|
|
35
43
|
function cn(...inputs) {
|
|
36
44
|
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
37
45
|
}
|
|
38
|
-
var AppContextInternalCtx =
|
|
46
|
+
var AppContextInternalCtx = React2.createContext(null);
|
|
39
47
|
var AppContextProvider = AppContextInternalCtx.Provider;
|
|
40
48
|
function useAppContext() {
|
|
41
|
-
const ctx =
|
|
49
|
+
const ctx = React2.useContext(AppContextInternalCtx);
|
|
42
50
|
if (!ctx) throw new Error("useAppContext must be used inside an app page mounted by the shell");
|
|
43
51
|
return ctx;
|
|
44
52
|
}
|
|
@@ -51,10 +59,10 @@ var AppInvokeError = class extends Error {
|
|
|
51
59
|
this.name = "AppInvokeError";
|
|
52
60
|
}
|
|
53
61
|
};
|
|
54
|
-
var AppApiCtx =
|
|
62
|
+
var AppApiCtx = React2.createContext(null);
|
|
55
63
|
var AppApiProvider = AppApiCtx.Provider;
|
|
56
64
|
function useAppApi() {
|
|
57
|
-
const client =
|
|
65
|
+
const client = React2.useContext(AppApiCtx);
|
|
58
66
|
if (!client) throw new Error("useAppApi must be used inside an app page mounted by the shell");
|
|
59
67
|
return client;
|
|
60
68
|
}
|
|
@@ -62,16 +70,16 @@ function useAppSettings() {
|
|
|
62
70
|
const { appSlug } = useAppContext();
|
|
63
71
|
const api = useAppApi();
|
|
64
72
|
const path = `/data/apps/${appSlug}/settings`;
|
|
65
|
-
const [settings, setSettings] =
|
|
66
|
-
const [isLoading, setIsLoading] =
|
|
67
|
-
const [error, setError] =
|
|
68
|
-
const [isSaving, setIsSaving] =
|
|
69
|
-
const [saveError, setSaveError] =
|
|
70
|
-
const clientRef =
|
|
73
|
+
const [settings, setSettings] = React2.useState(void 0);
|
|
74
|
+
const [isLoading, setIsLoading] = React2.useState(true);
|
|
75
|
+
const [error, setError] = React2.useState(void 0);
|
|
76
|
+
const [isSaving, setIsSaving] = React2.useState(false);
|
|
77
|
+
const [saveError, setSaveError] = React2.useState(void 0);
|
|
78
|
+
const clientRef = React2.useRef(api);
|
|
71
79
|
clientRef.current = api;
|
|
72
|
-
const requestIdRef =
|
|
73
|
-
const isMountedRef =
|
|
74
|
-
const load =
|
|
80
|
+
const requestIdRef = React2.useRef(0);
|
|
81
|
+
const isMountedRef = React2.useRef(true);
|
|
82
|
+
const load = React2.useCallback(
|
|
75
83
|
async ({ silent } = {}) => {
|
|
76
84
|
const requestId = ++requestIdRef.current;
|
|
77
85
|
if (!silent) setIsLoading(true);
|
|
@@ -89,7 +97,7 @@ function useAppSettings() {
|
|
|
89
97
|
},
|
|
90
98
|
[path]
|
|
91
99
|
);
|
|
92
|
-
|
|
100
|
+
React2.useEffect(() => {
|
|
93
101
|
isMountedRef.current = true;
|
|
94
102
|
void load();
|
|
95
103
|
return () => {
|
|
@@ -97,7 +105,7 @@ function useAppSettings() {
|
|
|
97
105
|
requestIdRef.current += 1;
|
|
98
106
|
};
|
|
99
107
|
}, [load]);
|
|
100
|
-
const save =
|
|
108
|
+
const save = React2.useCallback(
|
|
101
109
|
async (patch) => {
|
|
102
110
|
setIsSaving(true);
|
|
103
111
|
setSaveError(void 0);
|
|
@@ -139,15 +147,15 @@ function useAppRun(runId, opts = {}) {
|
|
|
139
147
|
const api = useAppApi();
|
|
140
148
|
const intervalMs = opts.intervalMs ?? DEFAULT_RUN_POLL_INTERVAL_MS;
|
|
141
149
|
const maxIntervalMs = opts.maxIntervalMs ?? MAX_RUN_POLL_INTERVAL_MS;
|
|
142
|
-
const [run, setRun] =
|
|
143
|
-
const [isPolling, setIsPolling] =
|
|
144
|
-
const [error, setError] =
|
|
145
|
-
const clientRef =
|
|
150
|
+
const [run, setRun] = React2.useState(void 0);
|
|
151
|
+
const [isPolling, setIsPolling] = React2.useState(false);
|
|
152
|
+
const [error, setError] = React2.useState(void 0);
|
|
153
|
+
const clientRef = React2.useRef(api);
|
|
146
154
|
clientRef.current = api;
|
|
147
|
-
const requestIdRef =
|
|
148
|
-
const isMountedRef =
|
|
155
|
+
const requestIdRef = React2.useRef(0);
|
|
156
|
+
const isMountedRef = React2.useRef(true);
|
|
149
157
|
const path = runId !== null ? `/data/apps/${appSlug}/runs/${encodeURIComponent(runId)}` : null;
|
|
150
|
-
const fetchOnce =
|
|
158
|
+
const fetchOnce = React2.useCallback(async () => {
|
|
151
159
|
if (path === null) return { kind: "no-path" };
|
|
152
160
|
const requestId = ++requestIdRef.current;
|
|
153
161
|
try {
|
|
@@ -162,7 +170,7 @@ function useAppRun(runId, opts = {}) {
|
|
|
162
170
|
return { kind: "error", err };
|
|
163
171
|
}
|
|
164
172
|
}, [path]);
|
|
165
|
-
|
|
173
|
+
React2.useEffect(() => {
|
|
166
174
|
isMountedRef.current = true;
|
|
167
175
|
requestIdRef.current += 1;
|
|
168
176
|
setRun(void 0);
|
|
@@ -214,7 +222,7 @@ function useAppRun(runId, opts = {}) {
|
|
|
214
222
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
215
223
|
};
|
|
216
224
|
}, [path, fetchOnce, intervalMs, maxIntervalMs]);
|
|
217
|
-
const refresh =
|
|
225
|
+
const refresh = React2.useCallback(async () => {
|
|
218
226
|
await fetchOnce();
|
|
219
227
|
}, [fetchOnce]);
|
|
220
228
|
return { run, isPolling, error, refresh };
|
|
@@ -223,13 +231,13 @@ function useAppRuns(opts) {
|
|
|
223
231
|
const { appSlug } = useAppContext();
|
|
224
232
|
const api = useAppApi();
|
|
225
233
|
const follow = opts.follow ?? true;
|
|
226
|
-
const [runs, setRuns] =
|
|
227
|
-
const [isLoading, setIsLoading] =
|
|
228
|
-
const [error, setError] =
|
|
229
|
-
const clientRef =
|
|
234
|
+
const [runs, setRuns] = React2.useState([]);
|
|
235
|
+
const [isLoading, setIsLoading] = React2.useState(true);
|
|
236
|
+
const [error, setError] = React2.useState(void 0);
|
|
237
|
+
const clientRef = React2.useRef(api);
|
|
230
238
|
clientRef.current = api;
|
|
231
|
-
const requestIdRef =
|
|
232
|
-
const isMountedRef =
|
|
239
|
+
const requestIdRef = React2.useRef(0);
|
|
240
|
+
const isMountedRef = React2.useRef(true);
|
|
233
241
|
const params = {};
|
|
234
242
|
if (opts.kind) params["kind"] = opts.kind;
|
|
235
243
|
if (opts.key) params["key"] = opts.key;
|
|
@@ -237,7 +245,7 @@ function useAppRuns(opts) {
|
|
|
237
245
|
if (opts.limit) params["limit"] = String(opts.limit);
|
|
238
246
|
const paramsKey = JSON.stringify(params);
|
|
239
247
|
const path = `/data/apps/${appSlug}/runs`;
|
|
240
|
-
const fetchOnce =
|
|
248
|
+
const fetchOnce = React2.useCallback(
|
|
241
249
|
async ({ silent } = {}) => {
|
|
242
250
|
const requestId = ++requestIdRef.current;
|
|
243
251
|
if (!silent) setIsLoading(true);
|
|
@@ -260,7 +268,7 @@ function useAppRuns(opts) {
|
|
|
260
268
|
},
|
|
261
269
|
[path, paramsKey]
|
|
262
270
|
);
|
|
263
|
-
|
|
271
|
+
React2.useEffect(() => {
|
|
264
272
|
isMountedRef.current = true;
|
|
265
273
|
let timeoutHandle;
|
|
266
274
|
let cancelled = false;
|
|
@@ -278,7 +286,7 @@ function useAppRuns(opts) {
|
|
|
278
286
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
279
287
|
};
|
|
280
288
|
}, [fetchOnce, follow]);
|
|
281
|
-
const refresh =
|
|
289
|
+
const refresh = React2.useCallback(async () => {
|
|
282
290
|
await fetchOnce({ silent: true });
|
|
283
291
|
}, [fetchOnce]);
|
|
284
292
|
return { runs, isLoading, error, refresh };
|
|
@@ -316,7 +324,7 @@ function downloadCsv(rows, filename) {
|
|
|
316
324
|
downloadBlob(blob, filename);
|
|
317
325
|
}
|
|
318
326
|
function triggerProps(trigger) {
|
|
319
|
-
if (
|
|
327
|
+
if (React2__namespace.isValidElement(trigger)) {
|
|
320
328
|
return { render: trigger, nativeButton: trigger.type === "button" || trigger.type === Button };
|
|
321
329
|
}
|
|
322
330
|
return { render: /* @__PURE__ */ jsxRuntime.jsx("span", { children: trigger }), nativeButton: false };
|
|
@@ -330,7 +338,7 @@ var buttonVariants = classVarianceAuthority.cva(
|
|
|
330
338
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
331
339
|
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
332
340
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
333
|
-
destructive: "bg-destructive text-
|
|
341
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
334
342
|
},
|
|
335
343
|
size: {
|
|
336
344
|
default: "h-9 px-4 py-2",
|
|
@@ -342,7 +350,7 @@ var buttonVariants = classVarianceAuthority.cva(
|
|
|
342
350
|
defaultVariants: { variant: "default", size: "default" }
|
|
343
351
|
}
|
|
344
352
|
);
|
|
345
|
-
var Button =
|
|
353
|
+
var Button = React2__namespace.forwardRef(
|
|
346
354
|
({ className, variant, size, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx("button", { ref, className: cn(buttonVariants({ variant, size, className })), ...props })
|
|
347
355
|
);
|
|
348
356
|
Button.displayName = "Button";
|
|
@@ -396,7 +404,7 @@ function CardContent({ className, ...props }) {
|
|
|
396
404
|
function CardFooter({ className, ...props }) {
|
|
397
405
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex items-center p-6 pt-0", className), ...props });
|
|
398
406
|
}
|
|
399
|
-
var Input =
|
|
407
|
+
var Input = React2__namespace.forwardRef(({ className, type, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
400
408
|
"input",
|
|
401
409
|
{
|
|
402
410
|
type,
|
|
@@ -454,21 +462,55 @@ function TableHead({ className, ...props }) {
|
|
|
454
462
|
function TableCell({ className, ...props }) {
|
|
455
463
|
return /* @__PURE__ */ jsxRuntime.jsx("td", { className: cn("p-4 align-middle", className), ...props });
|
|
456
464
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
465
|
+
function Select({
|
|
466
|
+
value,
|
|
467
|
+
defaultValue,
|
|
468
|
+
onValueChange,
|
|
469
|
+
items,
|
|
470
|
+
placeholder = "Choisir\u2026",
|
|
471
|
+
disabled,
|
|
472
|
+
name,
|
|
473
|
+
className,
|
|
474
|
+
...props
|
|
475
|
+
}) {
|
|
476
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
477
|
+
select.Select.Root,
|
|
460
478
|
{
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
children
|
|
479
|
+
items,
|
|
480
|
+
value,
|
|
481
|
+
defaultValue,
|
|
482
|
+
onValueChange: (next) => onValueChange?.(String(next)),
|
|
483
|
+
disabled,
|
|
484
|
+
name,
|
|
485
|
+
children: [
|
|
486
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
487
|
+
select.Select.Trigger,
|
|
488
|
+
{
|
|
489
|
+
className: cn(
|
|
490
|
+
"flex h-9 w-full cursor-pointer items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
|
|
491
|
+
className
|
|
492
|
+
),
|
|
493
|
+
...props,
|
|
494
|
+
children: [
|
|
495
|
+
/* @__PURE__ */ jsxRuntime.jsx(select.Select.Value, { placeholder }),
|
|
496
|
+
/* @__PURE__ */ jsxRuntime.jsx(select.Select.Icon, { "aria-hidden": true, className: "text-xs text-muted-foreground", children: "\u25BE" })
|
|
497
|
+
]
|
|
498
|
+
}
|
|
499
|
+
),
|
|
500
|
+
/* @__PURE__ */ jsxRuntime.jsx(select.Select.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(select.Select.Positioner, { className: "z-50", sideOffset: 4, children: /* @__PURE__ */ jsxRuntime.jsx(select.Select.Popup, { className: "max-h-72 min-w-(--anchor-width) overflow-auto rounded-md border bg-card p-1 shadow-lg", children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
501
|
+
select.Select.Item,
|
|
502
|
+
{
|
|
503
|
+
value: item.value,
|
|
504
|
+
disabled: item.disabled,
|
|
505
|
+
className: "cursor-pointer rounded px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
506
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(select.Select.ItemText, { children: item.label })
|
|
507
|
+
},
|
|
508
|
+
item.value
|
|
509
|
+
)) }) }) })
|
|
510
|
+
]
|
|
468
511
|
}
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
Select.displayName = "Select";
|
|
512
|
+
);
|
|
513
|
+
}
|
|
472
514
|
function Tabs({ defaultValue, value, onValueChange, className, children }) {
|
|
473
515
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
474
516
|
tabs.Tabs.Root,
|
|
@@ -521,7 +563,7 @@ function TabsContent({
|
|
|
521
563
|
}
|
|
522
564
|
function Modal({ open, onClose, title, children, className }) {
|
|
523
565
|
return /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Root, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(dialog.Dialog.Portal, { children: [
|
|
524
|
-
/* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Backdrop, { "aria-label": "Close", className: "fixed inset-0 z-50 bg-
|
|
566
|
+
/* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Backdrop, { "aria-label": "Close", className: "fixed inset-0 z-50 bg-overlay" }),
|
|
525
567
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
526
568
|
dialog.Dialog.Popup,
|
|
527
569
|
{
|
|
@@ -562,7 +604,7 @@ function Spinner({ className, size = "md" }) {
|
|
|
562
604
|
}
|
|
563
605
|
);
|
|
564
606
|
}
|
|
565
|
-
var Link =
|
|
607
|
+
var Link = React2__namespace.forwardRef(
|
|
566
608
|
({ className, variant = "default", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
567
609
|
"a",
|
|
568
610
|
{
|
|
@@ -601,33 +643,6 @@ function Switch({
|
|
|
601
643
|
}
|
|
602
644
|
);
|
|
603
645
|
}
|
|
604
|
-
function DateRangePicker({ value, onChange, className, disabled }) {
|
|
605
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-center gap-2", className), children: [
|
|
606
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
607
|
-
"input",
|
|
608
|
-
{
|
|
609
|
-
type: "date",
|
|
610
|
-
disabled,
|
|
611
|
-
value: value.from,
|
|
612
|
-
max: value.to || void 0,
|
|
613
|
-
onChange: (e) => onChange({ ...value, from: e.target.value }),
|
|
614
|
-
className: "flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
615
|
-
}
|
|
616
|
-
),
|
|
617
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "\u2192" }),
|
|
618
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
619
|
-
"input",
|
|
620
|
-
{
|
|
621
|
-
type: "date",
|
|
622
|
-
disabled,
|
|
623
|
-
value: value.to,
|
|
624
|
-
min: value.from || void 0,
|
|
625
|
-
onChange: (e) => onChange({ ...value, to: e.target.value }),
|
|
626
|
-
className: "flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
627
|
-
}
|
|
628
|
-
)
|
|
629
|
-
] });
|
|
630
|
-
}
|
|
631
646
|
function IconX({ className }) {
|
|
632
647
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
633
648
|
"svg",
|
|
@@ -681,43 +696,69 @@ function IconCheck({ className }) {
|
|
|
681
696
|
}
|
|
682
697
|
);
|
|
683
698
|
}
|
|
684
|
-
var Textarea =
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
699
|
+
var Textarea = React2__namespace.forwardRef(function Textarea2({ className, ...props }, ref) {
|
|
700
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
701
|
+
"textarea",
|
|
702
|
+
{
|
|
703
|
+
ref,
|
|
704
|
+
className: cn(
|
|
705
|
+
"flex min-h-20 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
706
|
+
className
|
|
707
|
+
),
|
|
708
|
+
...props
|
|
709
|
+
}
|
|
710
|
+
);
|
|
711
|
+
});
|
|
712
|
+
function Checkbox({
|
|
713
|
+
checked,
|
|
714
|
+
defaultChecked,
|
|
715
|
+
onCheckedChange,
|
|
716
|
+
disabled,
|
|
717
|
+
label,
|
|
718
|
+
name,
|
|
719
|
+
className,
|
|
720
|
+
...props
|
|
721
|
+
}) {
|
|
722
|
+
const labelId = React2__namespace.useId();
|
|
723
|
+
const control = React2__namespace.useRef(null);
|
|
724
|
+
const box = /* @__PURE__ */ jsxRuntime.jsx(
|
|
725
|
+
checkbox.Checkbox.Root,
|
|
726
|
+
{
|
|
727
|
+
ref: control,
|
|
728
|
+
"aria-labelledby": label ? labelId : void 0,
|
|
729
|
+
checked,
|
|
730
|
+
defaultChecked,
|
|
731
|
+
onCheckedChange: (next) => onCheckedChange?.(next),
|
|
732
|
+
disabled,
|
|
733
|
+
name,
|
|
734
|
+
className: cn(
|
|
735
|
+
"flex size-4 shrink-0 cursor-pointer items-center justify-center rounded border border-input text-primary-foreground transition-colors data-[checked]:border-primary data-[checked]:bg-primary data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
|
|
736
|
+
className
|
|
737
|
+
),
|
|
738
|
+
...props,
|
|
739
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(checkbox.Checkbox.Indicator, { className: "text-[10px] leading-none", children: "\u2713" })
|
|
740
|
+
}
|
|
741
|
+
);
|
|
742
|
+
if (!label) return box;
|
|
743
|
+
return (
|
|
744
|
+
// Not a wrapping <label>: Base UI would associate it AND keep its own label,
|
|
745
|
+
// so the text and the control would both answer to the same name and
|
|
746
|
+
// `getByLabelText` would go ambiguous. Pointing `aria-labelledby` at the text
|
|
747
|
+
// names the control once; the click handler keeps the label clickable.
|
|
748
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-2 text-sm", children: [
|
|
749
|
+
box,
|
|
750
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
751
|
+
"span",
|
|
752
|
+
{
|
|
753
|
+
id: labelId,
|
|
754
|
+
className: "cursor-pointer",
|
|
755
|
+
onClick: () => control.current?.click(),
|
|
756
|
+
children: label
|
|
757
|
+
}
|
|
758
|
+
)
|
|
759
|
+
] })
|
|
760
|
+
);
|
|
761
|
+
}
|
|
721
762
|
function SidePanel({
|
|
722
763
|
open,
|
|
723
764
|
onClose,
|
|
@@ -727,7 +768,7 @@ function SidePanel({
|
|
|
727
768
|
className
|
|
728
769
|
}) {
|
|
729
770
|
return /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Root, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(dialog.Dialog.Portal, { children: [
|
|
730
|
-
/* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Backdrop, { "aria-label": "Fermer", className: "fixed inset-0 z-50 bg-
|
|
771
|
+
/* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Backdrop, { "aria-label": "Fermer", className: "fixed inset-0 z-50 bg-overlay" }),
|
|
731
772
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
732
773
|
dialog.Dialog.Popup,
|
|
733
774
|
{
|
|
@@ -765,9 +806,9 @@ function CopyButton({
|
|
|
765
806
|
size = "sm",
|
|
766
807
|
className
|
|
767
808
|
}) {
|
|
768
|
-
const [state, setState] =
|
|
769
|
-
const timeout =
|
|
770
|
-
|
|
809
|
+
const [state, setState] = React2__namespace.useState("idle");
|
|
810
|
+
const timeout = React2__namespace.useRef(void 0);
|
|
811
|
+
React2__namespace.useEffect(() => () => clearTimeout(timeout.current), []);
|
|
771
812
|
async function copy() {
|
|
772
813
|
const ok = await copyToClipboard(value);
|
|
773
814
|
setState(ok ? "copied" : "error");
|
|
@@ -839,9 +880,1289 @@ function DropdownMenu({ trigger, items, align = "end", className }) {
|
|
|
839
880
|
) }) })
|
|
840
881
|
] });
|
|
841
882
|
}
|
|
842
|
-
var
|
|
883
|
+
var EMPTY_STATE = {
|
|
884
|
+
search: "",
|
|
885
|
+
sort: null,
|
|
886
|
+
filters: {},
|
|
887
|
+
hidden: [],
|
|
888
|
+
selected: []
|
|
889
|
+
};
|
|
890
|
+
var MIN_COLUMN_WIDTH_PX = 64;
|
|
891
|
+
var DEFAULT_VIRTUALIZE_FROM = 100;
|
|
892
|
+
var ROW_HEIGHT_PX = 53;
|
|
893
|
+
var VIRTUAL_VIEWPORT_PX = 640;
|
|
894
|
+
function useDataTableState(controlled, initial, onStateChange) {
|
|
895
|
+
const [internal, setInternal] = React2__namespace.useState({ ...EMPTY_STATE, ...initial });
|
|
896
|
+
const state = React2__namespace.useMemo(
|
|
897
|
+
() => ({ ...EMPTY_STATE, ...initial, ...internal, ...controlled }),
|
|
898
|
+
[controlled, initial, internal]
|
|
899
|
+
);
|
|
900
|
+
const patch = (next) => {
|
|
901
|
+
const merged = { ...state, ...next };
|
|
902
|
+
setInternal(merged);
|
|
903
|
+
onStateChange?.(merged);
|
|
904
|
+
};
|
|
905
|
+
return [state, patch];
|
|
906
|
+
}
|
|
907
|
+
function DataTable({
|
|
908
|
+
columns,
|
|
909
|
+
rows,
|
|
910
|
+
rowId,
|
|
911
|
+
state: controlled,
|
|
912
|
+
defaultState,
|
|
913
|
+
onStateChange,
|
|
914
|
+
facets = [],
|
|
915
|
+
searchPlaceholder = "Rechercher\u2026",
|
|
916
|
+
selectable = false,
|
|
917
|
+
onRowClick,
|
|
918
|
+
pagination,
|
|
919
|
+
manual = false,
|
|
920
|
+
loading = false,
|
|
921
|
+
error,
|
|
922
|
+
empty = "Aucun r\xE9sultat.",
|
|
923
|
+
virtualizeFrom = DEFAULT_VIRTUALIZE_FROM,
|
|
924
|
+
className
|
|
925
|
+
}) {
|
|
926
|
+
const [state, patch] = useDataTableState(controlled, defaultState, onStateChange);
|
|
927
|
+
const [widths, setWidths] = React2__namespace.useState({});
|
|
928
|
+
const hidden = React2__namespace.useMemo(() => new Set(state.hidden), [state.hidden]);
|
|
929
|
+
const selected = React2__namespace.useMemo(() => new Set(state.selected), [state.selected]);
|
|
930
|
+
const visibleColumns = React2__namespace.useMemo(
|
|
931
|
+
() => columns.filter((column) => !hidden.has(column.key)),
|
|
932
|
+
[columns, hidden]
|
|
933
|
+
);
|
|
934
|
+
const columnDefs = React2__namespace.useMemo(
|
|
935
|
+
() => visibleColumns.map((column) => ({
|
|
936
|
+
id: column.key,
|
|
937
|
+
accessorFn: (row) => column.value ? column.value(row) : row[column.key],
|
|
938
|
+
enableSorting: column.sortable ?? false,
|
|
939
|
+
size: column.width,
|
|
940
|
+
cell: ({ row }) => column.cell ? column.cell(row.original) : String(row.original[column.key] ?? "")
|
|
941
|
+
})),
|
|
942
|
+
[visibleColumns]
|
|
943
|
+
);
|
|
944
|
+
const sorting = React2__namespace.useMemo(
|
|
945
|
+
() => state.sort ? [{ id: state.sort.key, desc: state.sort.direction === "desc" }] : [],
|
|
946
|
+
[state.sort]
|
|
947
|
+
);
|
|
948
|
+
const table = reactTable.useReactTable({
|
|
949
|
+
data: rows,
|
|
950
|
+
columns: columnDefs,
|
|
951
|
+
state: { sorting, globalFilter: manual ? "" : state.search },
|
|
952
|
+
getRowId: (row) => rowId(row),
|
|
953
|
+
getCoreRowModel: reactTable.getCoreRowModel(),
|
|
954
|
+
...manual ? { manualSorting: true, manualFiltering: true } : { getSortedRowModel: reactTable.getSortedRowModel(), getFilteredRowModel: reactTable.getFilteredRowModel() }
|
|
955
|
+
});
|
|
956
|
+
const facetedRows = React2__namespace.useMemo(() => {
|
|
957
|
+
const active = Object.entries(state.filters).filter(([, filter]) => isApplied(filter));
|
|
958
|
+
if (manual || active.length === 0) return table.getRowModel().rows;
|
|
959
|
+
return table.getRowModel().rows.filter(
|
|
960
|
+
(row) => active.every(([key, filter]) => {
|
|
961
|
+
const column = columns.find((c) => c.key === key);
|
|
962
|
+
const raw = column?.value ? column.value(row.original) : row.original[key];
|
|
963
|
+
return matches(raw, filter);
|
|
964
|
+
})
|
|
965
|
+
);
|
|
966
|
+
}, [columns, manual, state.filters, table.getRowModel().rows]);
|
|
967
|
+
const bodyRef = React2__namespace.useRef(null);
|
|
968
|
+
const longEnoughToVirtualise = facetedRows.length > virtualizeFrom;
|
|
969
|
+
const virtualizer = reactVirtual.useVirtualizer({
|
|
970
|
+
count: facetedRows.length,
|
|
971
|
+
getScrollElement: () => bodyRef.current,
|
|
972
|
+
estimateSize: () => ROW_HEIGHT_PX,
|
|
973
|
+
overscan: 8,
|
|
974
|
+
enabled: longEnoughToVirtualise
|
|
975
|
+
});
|
|
976
|
+
const virtualItems = virtualizer.getVirtualItems();
|
|
977
|
+
const virtualise = longEnoughToVirtualise && virtualItems.length > 0;
|
|
978
|
+
const toggleSort = (key) => {
|
|
979
|
+
const next = state.sort?.key !== key ? { key, direction: "asc" } : state.sort.direction === "asc" ? { key, direction: "desc" } : null;
|
|
980
|
+
patch({ sort: next });
|
|
981
|
+
};
|
|
982
|
+
const toggleSelected = (id) => {
|
|
983
|
+
const next = new Set(selected);
|
|
984
|
+
if (next.has(id)) next.delete(id);
|
|
985
|
+
else next.add(id);
|
|
986
|
+
patch({ selected: [...next] });
|
|
987
|
+
};
|
|
988
|
+
const allVisibleSelected = facetedRows.length > 0 && facetedRows.every((row) => selected.has(row.id));
|
|
989
|
+
const toggleAll = () => {
|
|
990
|
+
patch({ selected: allVisibleSelected ? [] : facetedRows.map((row) => row.id) });
|
|
991
|
+
};
|
|
992
|
+
const setFilter = (facetKey, filter) => {
|
|
993
|
+
const next = { ...state.filters };
|
|
994
|
+
if (filter) next[facetKey] = filter;
|
|
995
|
+
else delete next[facetKey];
|
|
996
|
+
patch({ filters: next });
|
|
997
|
+
};
|
|
998
|
+
const renderedRows = virtualise ? virtualItems.map((item) => facetedRows[item.index]) : facetedRows;
|
|
999
|
+
const spaceAbove = virtualise ? virtualItems[0]?.start ?? 0 : 0;
|
|
1000
|
+
const spaceBelow = virtualise ? virtualizer.getTotalSize() - (virtualItems[virtualItems.length - 1]?.end ?? 0) : 0;
|
|
1001
|
+
const columnCount = visibleColumns.length + (selectable ? 1 : 0);
|
|
1002
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-3", className), children: [
|
|
1003
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1004
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1005
|
+
Input,
|
|
1006
|
+
{
|
|
1007
|
+
value: state.search,
|
|
1008
|
+
onChange: (event) => patch({ search: event.target.value }),
|
|
1009
|
+
placeholder: searchPlaceholder,
|
|
1010
|
+
"aria-label": searchPlaceholder,
|
|
1011
|
+
className: "h-9 max-w-xs"
|
|
1012
|
+
}
|
|
1013
|
+
),
|
|
1014
|
+
facets.map((facet) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1015
|
+
FacetFilter,
|
|
1016
|
+
{
|
|
1017
|
+
facet,
|
|
1018
|
+
filter: state.filters[facet.key],
|
|
1019
|
+
onChange: (filter) => setFilter(facet.key, filter)
|
|
1020
|
+
},
|
|
1021
|
+
facet.key
|
|
1022
|
+
)),
|
|
1023
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1024
|
+
ColumnPicker,
|
|
1025
|
+
{
|
|
1026
|
+
columns,
|
|
1027
|
+
hidden,
|
|
1028
|
+
onToggle: (key) => patch({
|
|
1029
|
+
hidden: hidden.has(key) ? state.hidden.filter((k) => k !== key) : [...state.hidden, key]
|
|
1030
|
+
})
|
|
1031
|
+
}
|
|
1032
|
+
),
|
|
1033
|
+
selectable && selected.size > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", role: "status", children: [
|
|
1034
|
+
selected.size,
|
|
1035
|
+
" s\xE9lectionn\xE9",
|
|
1036
|
+
selected.size > 1 ? "s" : ""
|
|
1037
|
+
] })
|
|
1038
|
+
] }),
|
|
1039
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1040
|
+
"div",
|
|
1041
|
+
{
|
|
1042
|
+
ref: bodyRef,
|
|
1043
|
+
className: cn("overflow-auto rounded-xl border bg-card", virtualise && "max-h-160"),
|
|
1044
|
+
style: virtualise ? { maxHeight: VIRTUAL_VIEWPORT_PX } : void 0,
|
|
1045
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(Table, { children: [
|
|
1046
|
+
/* @__PURE__ */ jsxRuntime.jsx(TableHeader, { className: virtualise ? "sticky top-0 z-10 bg-card" : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs(TableRow, { children: [
|
|
1047
|
+
selectable && /* @__PURE__ */ jsxRuntime.jsx(TableHead, { className: "w-10", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1048
|
+
Checkbox,
|
|
1049
|
+
{
|
|
1050
|
+
checked: allVisibleSelected,
|
|
1051
|
+
onCheckedChange: toggleAll,
|
|
1052
|
+
"aria-label": "Tout s\xE9lectionner"
|
|
1053
|
+
}
|
|
1054
|
+
) }),
|
|
1055
|
+
visibleColumns.map((column) => {
|
|
1056
|
+
const label = typeof column.header === "string" ? column.header : column.key;
|
|
1057
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1058
|
+
TableHead,
|
|
1059
|
+
{
|
|
1060
|
+
style: { width: widths[column.key] ?? column.width },
|
|
1061
|
+
className: cn("relative", column.align === "right" && "text-right"),
|
|
1062
|
+
"aria-sort": !column.sortable ? void 0 : state.sort?.key === column.key ? state.sort.direction === "asc" ? "ascending" : "descending" : "none",
|
|
1063
|
+
children: [
|
|
1064
|
+
column.sortable ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1065
|
+
"button",
|
|
1066
|
+
{
|
|
1067
|
+
type: "button",
|
|
1068
|
+
onClick: () => toggleSort(column.key),
|
|
1069
|
+
className: "inline-flex cursor-pointer items-center gap-1 hover:text-foreground",
|
|
1070
|
+
children: [
|
|
1071
|
+
column.header,
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1073
|
+
SortGlyph,
|
|
1074
|
+
{
|
|
1075
|
+
direction: state.sort?.key === column.key ? state.sort.direction : null
|
|
1076
|
+
}
|
|
1077
|
+
)
|
|
1078
|
+
]
|
|
1079
|
+
}
|
|
1080
|
+
) : column.header,
|
|
1081
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1082
|
+
ResizeHandle,
|
|
1083
|
+
{
|
|
1084
|
+
label,
|
|
1085
|
+
onResize: (delta, from) => setWidths((current) => ({
|
|
1086
|
+
...current,
|
|
1087
|
+
[column.key]: Math.max(MIN_COLUMN_WIDTH_PX, from + delta)
|
|
1088
|
+
}))
|
|
1089
|
+
}
|
|
1090
|
+
)
|
|
1091
|
+
]
|
|
1092
|
+
},
|
|
1093
|
+
column.key
|
|
1094
|
+
);
|
|
1095
|
+
})
|
|
1096
|
+
] }) }),
|
|
1097
|
+
/* @__PURE__ */ jsxRuntime.jsx(TableBody, { children: error ? /* @__PURE__ */ jsxRuntime.jsx(StatusRow, { colSpan: columnCount, tone: "error", children: error }) : loading ? /* @__PURE__ */ jsxRuntime.jsx(SkeletonRows, { columns: columnCount }) : facetedRows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(StatusRow, { colSpan: columnCount, children: empty }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1098
|
+
spaceAbove > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { "aria-hidden": true, style: { height: spaceAbove } }),
|
|
1099
|
+
renderedRows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1100
|
+
TableRow,
|
|
1101
|
+
{
|
|
1102
|
+
"data-state": selected.has(row.id) ? "selected" : void 0,
|
|
1103
|
+
className: onRowClick ? "cursor-pointer" : "hover:bg-transparent",
|
|
1104
|
+
onClick: onRowClick ? () => onRowClick(row.original) : void 0,
|
|
1105
|
+
children: [
|
|
1106
|
+
selectable && /* @__PURE__ */ jsxRuntime.jsx(TableCell, { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1107
|
+
Checkbox,
|
|
1108
|
+
{
|
|
1109
|
+
checked: selected.has(row.id),
|
|
1110
|
+
onCheckedChange: () => toggleSelected(row.id),
|
|
1111
|
+
"aria-label": `S\xE9lectionner ${row.id}`
|
|
1112
|
+
}
|
|
1113
|
+
) }),
|
|
1114
|
+
row.getVisibleCells().map((cell) => {
|
|
1115
|
+
const column = visibleColumns.find((c) => c.key === cell.column.id);
|
|
1116
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1117
|
+
TableCell,
|
|
1118
|
+
{
|
|
1119
|
+
className: column?.align === "right" ? "text-right" : void 0,
|
|
1120
|
+
children: reactTable.flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
1121
|
+
},
|
|
1122
|
+
cell.id
|
|
1123
|
+
);
|
|
1124
|
+
})
|
|
1125
|
+
]
|
|
1126
|
+
},
|
|
1127
|
+
row.id
|
|
1128
|
+
)),
|
|
1129
|
+
spaceBelow > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { "aria-hidden": true, style: { height: spaceBelow } })
|
|
1130
|
+
] }) })
|
|
1131
|
+
] })
|
|
1132
|
+
}
|
|
1133
|
+
),
|
|
1134
|
+
pagination && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
1135
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1136
|
+
Button,
|
|
1137
|
+
{
|
|
1138
|
+
variant: "outline",
|
|
1139
|
+
size: "sm",
|
|
1140
|
+
disabled: !pagination.hasPrevious,
|
|
1141
|
+
onClick: pagination.onPrevious,
|
|
1142
|
+
children: "Pr\xE9c\xE9dent"
|
|
1143
|
+
}
|
|
1144
|
+
),
|
|
1145
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1146
|
+
Button,
|
|
1147
|
+
{
|
|
1148
|
+
variant: "outline",
|
|
1149
|
+
size: "sm",
|
|
1150
|
+
disabled: !pagination.hasNext,
|
|
1151
|
+
onClick: pagination.onNext,
|
|
1152
|
+
children: "Suivant"
|
|
1153
|
+
}
|
|
1154
|
+
)
|
|
1155
|
+
] })
|
|
1156
|
+
] });
|
|
1157
|
+
}
|
|
1158
|
+
function ResizeHandle({
|
|
1159
|
+
label,
|
|
1160
|
+
onResize
|
|
1161
|
+
}) {
|
|
1162
|
+
const start = (event) => {
|
|
1163
|
+
const header = event.currentTarget.parentElement;
|
|
1164
|
+
if (!header) return;
|
|
1165
|
+
const from = header.getBoundingClientRect().width;
|
|
1166
|
+
const originX = event.clientX;
|
|
1167
|
+
const move = (moveEvent) => onResize(moveEvent.clientX - originX, from);
|
|
1168
|
+
const stop = () => {
|
|
1169
|
+
window.removeEventListener("pointermove", move);
|
|
1170
|
+
window.removeEventListener("pointerup", stop);
|
|
1171
|
+
};
|
|
1172
|
+
window.addEventListener("pointermove", move);
|
|
1173
|
+
window.addEventListener("pointerup", stop);
|
|
1174
|
+
event.preventDefault();
|
|
1175
|
+
};
|
|
1176
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1177
|
+
"span",
|
|
1178
|
+
{
|
|
1179
|
+
"aria-hidden": true,
|
|
1180
|
+
"data-resize-handle": label,
|
|
1181
|
+
onPointerDown: start,
|
|
1182
|
+
className: "absolute inset-y-0 right-0 w-1 cursor-col-resize select-none hover:bg-border"
|
|
1183
|
+
}
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
function SortGlyph({ direction }) {
|
|
1187
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "text-[10px] leading-none", children: direction === "asc" ? "\u25B2" : direction === "desc" ? "\u25BC" : "\u2195" });
|
|
1188
|
+
}
|
|
1189
|
+
function StatusRow({
|
|
1190
|
+
colSpan,
|
|
1191
|
+
tone,
|
|
1192
|
+
children
|
|
1193
|
+
}) {
|
|
1194
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1195
|
+
TableCell,
|
|
1196
|
+
{
|
|
1197
|
+
colSpan,
|
|
1198
|
+
className: cn(
|
|
1199
|
+
"text-sm",
|
|
1200
|
+
tone === "error" ? "text-status-blocked" : "text-muted-foreground"
|
|
1201
|
+
),
|
|
1202
|
+
children
|
|
1203
|
+
}
|
|
1204
|
+
) });
|
|
1205
|
+
}
|
|
1206
|
+
function SkeletonRows({ columns }) {
|
|
1207
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }, (_, row) => /* @__PURE__ */ jsxRuntime.jsx(TableRow, { className: "hover:bg-transparent", children: Array.from({ length: columns }, (_2, cell) => /* @__PURE__ */ jsxRuntime.jsx(TableCell, { children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block h-3 w-full animate-pulse rounded bg-muted" }) }, cell)) }, row)) });
|
|
1208
|
+
}
|
|
1209
|
+
var VALUELESS = /* @__PURE__ */ new Set(["empty", "nempty"]);
|
|
1210
|
+
var SINGLE_VALUE = /* @__PURE__ */ new Set([
|
|
1211
|
+
"contains",
|
|
1212
|
+
"ncontains",
|
|
1213
|
+
"gt",
|
|
1214
|
+
"lt",
|
|
1215
|
+
"gte",
|
|
1216
|
+
"lte"
|
|
1217
|
+
]);
|
|
1218
|
+
var OPERATOR_LABELS = {
|
|
1219
|
+
eq: "est",
|
|
1220
|
+
neq: "n'est pas",
|
|
1221
|
+
contains: "contient",
|
|
1222
|
+
ncontains: "ne contient pas",
|
|
1223
|
+
gt: "sup\xE9rieur \xE0",
|
|
1224
|
+
lt: "inf\xE9rieur \xE0",
|
|
1225
|
+
gte: "sup\xE9rieur ou \xE9gal \xE0",
|
|
1226
|
+
lte: "inf\xE9rieur ou \xE9gal \xE0",
|
|
1227
|
+
empty: "est vide",
|
|
1228
|
+
nempty: "n'est pas vide"
|
|
1229
|
+
};
|
|
1230
|
+
function isApplied(filter) {
|
|
1231
|
+
if (!filter) return false;
|
|
1232
|
+
return VALUELESS.has(filter.operator) || filter.values.length > 0;
|
|
1233
|
+
}
|
|
1234
|
+
function compare(value, bound) {
|
|
1235
|
+
if (value === "" || bound === "") return null;
|
|
1236
|
+
const boundNumber = Number(bound);
|
|
1237
|
+
if (!Number.isNaN(boundNumber)) {
|
|
1238
|
+
const valueNumber = Number(value);
|
|
1239
|
+
return Number.isNaN(valueNumber) ? null : valueNumber - boundNumber;
|
|
1240
|
+
}
|
|
1241
|
+
const boundDate = Date.parse(bound);
|
|
1242
|
+
if (!Number.isNaN(boundDate)) {
|
|
1243
|
+
const valueDate = Date.parse(value);
|
|
1244
|
+
return Number.isNaN(valueDate) ? null : valueDate - boundDate;
|
|
1245
|
+
}
|
|
1246
|
+
return value < bound ? -1 : value > bound ? 1 : 0;
|
|
1247
|
+
}
|
|
1248
|
+
function matches(raw, filter) {
|
|
1249
|
+
const value = raw == null ? "" : String(raw);
|
|
1250
|
+
const [bound = ""] = filter.values;
|
|
1251
|
+
const ordering = compare(value, bound);
|
|
1252
|
+
switch (filter.operator) {
|
|
1253
|
+
case "eq":
|
|
1254
|
+
return filter.values.includes(value);
|
|
1255
|
+
case "neq":
|
|
1256
|
+
return !filter.values.includes(value);
|
|
1257
|
+
case "contains":
|
|
1258
|
+
return value.toLowerCase().includes(bound.toLowerCase());
|
|
1259
|
+
case "ncontains":
|
|
1260
|
+
return !value.toLowerCase().includes(bound.toLowerCase());
|
|
1261
|
+
case "gt":
|
|
1262
|
+
return ordering !== null && ordering > 0;
|
|
1263
|
+
case "lt":
|
|
1264
|
+
return ordering !== null && ordering < 0;
|
|
1265
|
+
case "gte":
|
|
1266
|
+
return ordering !== null && ordering >= 0;
|
|
1267
|
+
case "lte":
|
|
1268
|
+
return ordering !== null && ordering <= 0;
|
|
1269
|
+
case "empty":
|
|
1270
|
+
return value === "";
|
|
1271
|
+
case "nempty":
|
|
1272
|
+
return value !== "";
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
function FacetFilter({
|
|
1276
|
+
facet,
|
|
1277
|
+
filter,
|
|
1278
|
+
onChange
|
|
1279
|
+
}) {
|
|
1280
|
+
const [open, setOpen] = React2__namespace.useState(false);
|
|
1281
|
+
const operators = facet.operators ?? ["eq"];
|
|
1282
|
+
const operator = filter?.operator ?? operators[0] ?? "eq";
|
|
1283
|
+
const values = filter?.values ?? [];
|
|
1284
|
+
const summary = !isApplied(filter) ? facet.label : VALUELESS.has(operator) ? `${facet.label} \xB7 ${OPERATOR_LABELS[operator]}` : `${facet.label} \xB7 ${values.length}`;
|
|
1285
|
+
const setOperator = (next) => {
|
|
1286
|
+
const sameKind = SINGLE_VALUE.has(next) === SINGLE_VALUE.has(operator);
|
|
1287
|
+
onChange({
|
|
1288
|
+
operator: next,
|
|
1289
|
+
values: VALUELESS.has(next) || !sameKind ? [] : SINGLE_VALUE.has(next) ? values.slice(0, 1) : values
|
|
1290
|
+
});
|
|
1291
|
+
};
|
|
1292
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1293
|
+
Popover,
|
|
1294
|
+
{
|
|
1295
|
+
open,
|
|
1296
|
+
onOpenChange: setOpen,
|
|
1297
|
+
trigger: (
|
|
1298
|
+
// Named for what it does: a chip reading just "Nom" tells a screen-reader
|
|
1299
|
+
// user nothing, and collides with the sortable column header of that name.
|
|
1300
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", "aria-label": `Filtrer par ${summary}`, children: summary })
|
|
1301
|
+
),
|
|
1302
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-56 flex-col gap-2", children: [
|
|
1303
|
+
operators.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1304
|
+
Select,
|
|
1305
|
+
{
|
|
1306
|
+
value: operator,
|
|
1307
|
+
"aria-label": `Op\xE9rateur \u2014 ${facet.label}`,
|
|
1308
|
+
onValueChange: (next) => setOperator(next),
|
|
1309
|
+
items: operators.map((candidate) => ({
|
|
1310
|
+
value: candidate,
|
|
1311
|
+
label: OPERATOR_LABELS[candidate]
|
|
1312
|
+
}))
|
|
1313
|
+
}
|
|
1314
|
+
),
|
|
1315
|
+
VALUELESS.has(operator) ? null : SINGLE_VALUE.has(operator) || !facet.options ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1316
|
+
Input,
|
|
1317
|
+
{
|
|
1318
|
+
value: values[0] ?? "",
|
|
1319
|
+
"aria-label": `Valeur \u2014 ${facet.label}`,
|
|
1320
|
+
onChange: (event) => onChange({ operator, values: event.target.value ? [event.target.value] : [] })
|
|
1321
|
+
}
|
|
1322
|
+
) : facet.options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1323
|
+
Checkbox,
|
|
1324
|
+
{
|
|
1325
|
+
checked: values.includes(option.value),
|
|
1326
|
+
onCheckedChange: () => onChange({
|
|
1327
|
+
operator,
|
|
1328
|
+
values: values.includes(option.value) ? values.filter((v) => v !== option.value) : [...values, option.value]
|
|
1329
|
+
}),
|
|
1330
|
+
label: option.label ?? option.value
|
|
1331
|
+
},
|
|
1332
|
+
option.value
|
|
1333
|
+
)),
|
|
1334
|
+
isApplied(filter) && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", onClick: () => onChange(null), children: "Effacer" })
|
|
1335
|
+
] })
|
|
1336
|
+
}
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
function ColumnPicker({
|
|
1340
|
+
columns,
|
|
1341
|
+
hidden,
|
|
1342
|
+
onToggle
|
|
1343
|
+
}) {
|
|
1344
|
+
const [open, setOpen] = React2__namespace.useState(false);
|
|
1345
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1346
|
+
Popover,
|
|
1347
|
+
{
|
|
1348
|
+
open,
|
|
1349
|
+
onOpenChange: setOpen,
|
|
1350
|
+
align: "end",
|
|
1351
|
+
trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", children: "Colonnes" }),
|
|
1352
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1", children: columns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1353
|
+
Checkbox,
|
|
1354
|
+
{
|
|
1355
|
+
checked: !hidden.has(column.key),
|
|
1356
|
+
onCheckedChange: () => onToggle(column.key),
|
|
1357
|
+
label: typeof column.header === "string" ? column.header : column.key
|
|
1358
|
+
},
|
|
1359
|
+
column.key
|
|
1360
|
+
)) })
|
|
1361
|
+
}
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
var PERIOD_PRESETS = [
|
|
1365
|
+
{ key: "today", label: "Aujourd'hui" },
|
|
1366
|
+
{ key: "yesterday", label: "Hier" },
|
|
1367
|
+
{ key: "last7", label: "7 derniers jours" },
|
|
1368
|
+
{ key: "last30", label: "30 derniers jours" },
|
|
1369
|
+
{ key: "last90", label: "90 derniers jours" },
|
|
1370
|
+
{ key: "thisMonth", label: "Mois en cours" },
|
|
1371
|
+
{ key: "lastMonth", label: "Mois dernier" },
|
|
1372
|
+
{ key: "thisQuarter", label: "Trimestre en cours" },
|
|
1373
|
+
{ key: "last12Months", label: "12 derniers mois" },
|
|
1374
|
+
{ key: "thisYear", label: "Ann\xE9e en cours" },
|
|
1375
|
+
{ key: "all", label: "Tout l'historique" }
|
|
1376
|
+
];
|
|
1377
|
+
var COMPARE_LABELS = {
|
|
1378
|
+
none: "Aucune",
|
|
1379
|
+
previous_period: "P\xE9riode pr\xE9c\xE9dente",
|
|
1380
|
+
previous_year: "Ann\xE9e pr\xE9c\xE9dente",
|
|
1381
|
+
custom: "Personnalis\xE9e"
|
|
1382
|
+
};
|
|
1383
|
+
var DAY_FORMAT = "yyyy-MM-dd";
|
|
1384
|
+
var toDay = (date) => dateFns.format(date, DAY_FORMAT);
|
|
1385
|
+
var fromDay = (day) => dateFns.parse(day, DAY_FORMAT, /* @__PURE__ */ new Date());
|
|
1386
|
+
var CALENDAR_UNIT = {
|
|
1387
|
+
thisMonth: "month",
|
|
1388
|
+
lastMonth: "month",
|
|
1389
|
+
thisQuarter: "quarter",
|
|
1390
|
+
thisYear: "year"
|
|
1391
|
+
};
|
|
1392
|
+
function presetRange(preset, today) {
|
|
1393
|
+
const day = toDay(today);
|
|
1394
|
+
switch (preset) {
|
|
1395
|
+
case "today":
|
|
1396
|
+
return { from: day, to: day };
|
|
1397
|
+
case "yesterday": {
|
|
1398
|
+
const yesterday = toDay(dateFns.subDays(today, 1));
|
|
1399
|
+
return { from: yesterday, to: yesterday };
|
|
1400
|
+
}
|
|
1401
|
+
case "last7":
|
|
1402
|
+
return { from: toDay(dateFns.subDays(today, 6)), to: day };
|
|
1403
|
+
case "last30":
|
|
1404
|
+
return { from: toDay(dateFns.subDays(today, 29)), to: day };
|
|
1405
|
+
case "last90":
|
|
1406
|
+
return { from: toDay(dateFns.subDays(today, 89)), to: day };
|
|
1407
|
+
case "thisMonth":
|
|
1408
|
+
return { from: toDay(dateFns.startOfMonth(today)), to: toDay(dateFns.endOfMonth(today)) };
|
|
1409
|
+
case "lastMonth": {
|
|
1410
|
+
const previous = dateFns.subMonths(today, 1);
|
|
1411
|
+
return { from: toDay(dateFns.startOfMonth(previous)), to: toDay(dateFns.endOfMonth(previous)) };
|
|
1412
|
+
}
|
|
1413
|
+
case "thisQuarter":
|
|
1414
|
+
return { from: toDay(dateFns.startOfQuarter(today)), to: toDay(dateFns.endOfQuarter(today)) };
|
|
1415
|
+
case "last12Months":
|
|
1416
|
+
return { from: toDay(dateFns.addDays(dateFns.subYears(today, 1), 1)), to: day };
|
|
1417
|
+
case "thisYear":
|
|
1418
|
+
return { from: toDay(dateFns.startOfYear(today)), to: toDay(dateFns.endOfYear(today)) };
|
|
1419
|
+
case "all":
|
|
1420
|
+
return { to: day };
|
|
1421
|
+
case "custom":
|
|
1422
|
+
return { from: day, to: day };
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
function comparisonRange(preset, range, compare2) {
|
|
1426
|
+
if (compare2 === "none" || compare2 === "custom" || range.from === void 0) return void 0;
|
|
1427
|
+
const from = fromDay(range.from);
|
|
1428
|
+
const to = fromDay(range.to);
|
|
1429
|
+
if (compare2 === "previous_year") {
|
|
1430
|
+
return { from: toDay(dateFns.subYears(from, 1)), to: toDay(dateFns.subYears(to, 1)) };
|
|
1431
|
+
}
|
|
1432
|
+
const unit = CALENDAR_UNIT[preset];
|
|
1433
|
+
if (unit === "month") {
|
|
1434
|
+
const previous = dateFns.subMonths(from, 1);
|
|
1435
|
+
return { from: toDay(dateFns.startOfMonth(previous)), to: toDay(dateFns.endOfMonth(previous)) };
|
|
1436
|
+
}
|
|
1437
|
+
if (unit === "quarter") {
|
|
1438
|
+
const previous = dateFns.addQuarters(from, -1);
|
|
1439
|
+
return { from: toDay(dateFns.startOfQuarter(previous)), to: toDay(dateFns.endOfQuarter(previous)) };
|
|
1440
|
+
}
|
|
1441
|
+
if (unit === "year") {
|
|
1442
|
+
const previous = dateFns.subYears(from, 1);
|
|
1443
|
+
return { from: toDay(dateFns.startOfYear(previous)), to: toDay(dateFns.endOfYear(previous)) };
|
|
1444
|
+
}
|
|
1445
|
+
const length = dateFns.differenceInCalendarDays(to, from) + 1;
|
|
1446
|
+
return { from: toDay(dateFns.subDays(from, length)), to: toDay(dateFns.subDays(to, length)) };
|
|
1447
|
+
}
|
|
1448
|
+
function makePeriod(preset, range, compare2, customCompareRange) {
|
|
1449
|
+
const resolved = compare2 === "custom" ? customCompareRange : comparisonRange(preset, range, compare2);
|
|
1450
|
+
const effective = resolved === void 0 ? "none" : compare2;
|
|
1451
|
+
return {
|
|
1452
|
+
preset,
|
|
1453
|
+
range,
|
|
1454
|
+
compare: effective,
|
|
1455
|
+
...resolved ? { compareRange: resolved } : {}
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
function periodFromPreset(preset, today, compare2 = "none") {
|
|
1459
|
+
return makePeriod(preset, presetRange(preset, today), compare2);
|
|
1460
|
+
}
|
|
1461
|
+
var MONTHS_ABBR = [
|
|
1462
|
+
"janv.",
|
|
1463
|
+
"f\xE9vr.",
|
|
1464
|
+
"mars",
|
|
1465
|
+
"avr.",
|
|
1466
|
+
"mai",
|
|
1467
|
+
"juin",
|
|
1468
|
+
"juil.",
|
|
1469
|
+
"ao\xFBt",
|
|
1470
|
+
"sept.",
|
|
1471
|
+
"oct.",
|
|
1472
|
+
"nov.",
|
|
1473
|
+
"d\xE9c."
|
|
1474
|
+
];
|
|
1475
|
+
var formatDay = (day) => {
|
|
1476
|
+
const date = fromDay(day);
|
|
1477
|
+
return `${date.getDate()} ${MONTHS_ABBR[date.getMonth()]} ${date.getFullYear()}`;
|
|
1478
|
+
};
|
|
1479
|
+
function formatRange(range) {
|
|
1480
|
+
if (range.from === void 0) return `Jusqu'au ${formatDay(range.to)}`;
|
|
1481
|
+
if (range.from === range.to) return formatDay(range.from);
|
|
1482
|
+
const from = fromDay(range.from);
|
|
1483
|
+
const to = fromDay(range.to);
|
|
1484
|
+
if (from.getFullYear() === to.getFullYear() && from.getMonth() === to.getMonth()) {
|
|
1485
|
+
return `${from.getDate()} \u2013 ${to.getDate()} ${MONTHS_ABBR[to.getMonth()]} ${to.getFullYear()}`;
|
|
1486
|
+
}
|
|
1487
|
+
return `${formatDay(range.from)} \u2013 ${formatDay(range.to)}`;
|
|
1488
|
+
}
|
|
1489
|
+
function formatPeriod(value) {
|
|
1490
|
+
const preset = PERIOD_PRESETS.find((entry) => entry.key === value.preset);
|
|
1491
|
+
if (preset) return preset.label;
|
|
1492
|
+
return isCompleteRange(value.range) ? formatRange(value.range) : "P\xE9riode";
|
|
1493
|
+
}
|
|
1494
|
+
var DAY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
1495
|
+
var isDay = (value) => value !== void 0 && DAY_PATTERN.test(value);
|
|
1496
|
+
function isCompleteRange(range) {
|
|
1497
|
+
return isDay(range.to) && (range.from === void 0 || isDay(range.from));
|
|
1498
|
+
}
|
|
1499
|
+
function spansTwoMonths(range) {
|
|
1500
|
+
if (!isDay(range.from) || !isDay(range.to)) return false;
|
|
1501
|
+
const from = fromDay(range.from);
|
|
1502
|
+
const to = fromDay(range.to);
|
|
1503
|
+
return from.getFullYear() !== to.getFullYear() || from.getMonth() !== to.getMonth();
|
|
1504
|
+
}
|
|
1505
|
+
function orderedRange(from, to) {
|
|
1506
|
+
return from <= to ? { from, to } : { from: to, to: from };
|
|
1507
|
+
}
|
|
1508
|
+
var COMPARE_MODES = [
|
|
1509
|
+
"none",
|
|
1510
|
+
"previous_period",
|
|
1511
|
+
"previous_year",
|
|
1512
|
+
"custom"
|
|
1513
|
+
];
|
|
1514
|
+
var DAY_PICKER_CLASS_NAMES = {
|
|
1515
|
+
// DayPicker renders its Nav as a sibling of the months, before them, so the
|
|
1516
|
+
// arrows are positioned against this box rather than against a caption.
|
|
1517
|
+
months: "relative flex gap-4",
|
|
1518
|
+
month: "space-y-2",
|
|
1519
|
+
month_caption: "flex h-8 items-center justify-center",
|
|
1520
|
+
caption_label: "text-sm font-medium capitalize",
|
|
1521
|
+
nav: "flex items-center justify-between absolute inset-x-0 h-8 px-1 pointer-events-none",
|
|
1522
|
+
button_previous: "pointer-events-auto inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40",
|
|
1523
|
+
button_next: "pointer-events-auto inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40",
|
|
1524
|
+
chevron: "h-4 w-4 fill-current",
|
|
1525
|
+
month_grid: "w-full border-collapse",
|
|
1526
|
+
weekdays: "flex",
|
|
1527
|
+
weekday: "w-9 text-xs font-normal text-muted-foreground",
|
|
1528
|
+
week: "flex w-full",
|
|
1529
|
+
day: "h-9 w-9 p-0 text-center text-sm",
|
|
1530
|
+
day_button: "h-9 w-9 rounded-md transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1531
|
+
today: "font-semibold text-primary",
|
|
1532
|
+
outside: "text-muted-foreground/50",
|
|
1533
|
+
disabled: "opacity-40",
|
|
1534
|
+
selected: "bg-primary/15",
|
|
1535
|
+
range_start: "rounded-l-md bg-primary [&_button]:bg-primary [&_button]:text-primary-foreground",
|
|
1536
|
+
range_end: "rounded-r-md bg-primary [&_button]:bg-primary [&_button]:text-primary-foreground",
|
|
1537
|
+
range_middle: "bg-primary/15 [&_button]:hover:bg-primary/25"
|
|
1538
|
+
};
|
|
1539
|
+
var COMPARE_MODIFIER_CLASS = "bg-chart-2/25 ring-1 ring-inset ring-chart-2/40";
|
|
1540
|
+
function draftOf(value) {
|
|
1541
|
+
return {
|
|
1542
|
+
preset: value.preset,
|
|
1543
|
+
// Anything that is not a day is "not chosen yet". The picker this replaced
|
|
1544
|
+
// accepted `{ from: '', to: '' }` for exactly that, and its callers still do.
|
|
1545
|
+
from: isDay(value.range.from) ? value.range.from : void 0,
|
|
1546
|
+
to: isDay(value.range.to) ? value.range.to : void 0,
|
|
1547
|
+
compare: value.compare,
|
|
1548
|
+
compareFrom: value.compareRange?.from,
|
|
1549
|
+
compareTo: value.compareRange?.to
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
function rangeOf(draft) {
|
|
1553
|
+
if (!isDay(draft.to)) return null;
|
|
1554
|
+
if (draft.preset === "all") return { to: draft.to };
|
|
1555
|
+
if (!isDay(draft.from)) return null;
|
|
1556
|
+
return { from: draft.from, to: draft.to };
|
|
1557
|
+
}
|
|
1558
|
+
function customCompareOf(draft) {
|
|
1559
|
+
if (!isDay(draft.compareFrom) || !isDay(draft.compareTo)) return void 0;
|
|
1560
|
+
return orderedRange(draft.compareFrom, draft.compareTo);
|
|
1561
|
+
}
|
|
1562
|
+
function previewCompare(draft) {
|
|
1563
|
+
const range = rangeOf(draft);
|
|
1564
|
+
if (!range) return void 0;
|
|
1565
|
+
if (draft.compare === "custom") return customCompareOf(draft);
|
|
1566
|
+
return comparisonRange(draft.preset, range, draft.compare);
|
|
1567
|
+
}
|
|
1568
|
+
function activeCompare(draft) {
|
|
1569
|
+
return draft.preset === "all" ? "none" : draft.compare;
|
|
1570
|
+
}
|
|
1571
|
+
function anchorMonth(range) {
|
|
1572
|
+
if (!range) return void 0;
|
|
1573
|
+
if (isDay(range.from)) return fromDay(range.from);
|
|
1574
|
+
return isDay(range.to) ? fromDay(range.to) : void 0;
|
|
1575
|
+
}
|
|
1576
|
+
function DateRangeCompare({
|
|
1577
|
+
value,
|
|
1578
|
+
onChange,
|
|
1579
|
+
comparable = true,
|
|
1580
|
+
presets = PERIOD_PRESETS,
|
|
1581
|
+
align = "start",
|
|
1582
|
+
disabled,
|
|
1583
|
+
className
|
|
1584
|
+
}) {
|
|
1585
|
+
const [open, setOpen] = React2__namespace.useState(false);
|
|
1586
|
+
const [draft, setDraft] = React2__namespace.useState(() => draftOf(value));
|
|
1587
|
+
const [month, setMonth] = React2__namespace.useState(void 0);
|
|
1588
|
+
const [months, setMonths] = React2__namespace.useState(1);
|
|
1589
|
+
const showRange = (range2) => {
|
|
1590
|
+
setMonth(anchorMonth(range2));
|
|
1591
|
+
setMonths(spansTwoMonths(range2) ? 2 : 1);
|
|
1592
|
+
};
|
|
1593
|
+
const openPanel = () => {
|
|
1594
|
+
setDraft(draftOf(value));
|
|
1595
|
+
showRange(value.range);
|
|
1596
|
+
setOpen(true);
|
|
1597
|
+
};
|
|
1598
|
+
const pickPreset = (preset) => {
|
|
1599
|
+
const resolved = periodFromPreset(preset, /* @__PURE__ */ new Date(), activeCompare(draft));
|
|
1600
|
+
setDraft({
|
|
1601
|
+
...draftOf(resolved),
|
|
1602
|
+
// The rail changes the period, not the comparison: the mode the user chose
|
|
1603
|
+
// survives, and `activeCompare` decides whether it can apply here.
|
|
1604
|
+
compare: draft.compare,
|
|
1605
|
+
compareFrom: draft.compareFrom,
|
|
1606
|
+
compareTo: draft.compareTo
|
|
1607
|
+
});
|
|
1608
|
+
showRange(resolved.range);
|
|
1609
|
+
};
|
|
1610
|
+
const pickDays = (selected) => {
|
|
1611
|
+
setDraft({
|
|
1612
|
+
...draft,
|
|
1613
|
+
preset: "custom",
|
|
1614
|
+
from: selected?.from ? toDay(selected.from) : void 0,
|
|
1615
|
+
to: selected?.to ? toDay(selected.to) : void 0
|
|
1616
|
+
});
|
|
1617
|
+
};
|
|
1618
|
+
const range = rangeOf(draft);
|
|
1619
|
+
const compareRange = previewCompare(draft);
|
|
1620
|
+
const apply = () => {
|
|
1621
|
+
if (!range) return;
|
|
1622
|
+
onChange(makePeriod(draft.preset, range, activeCompare(draft), customCompareOf(draft)));
|
|
1623
|
+
setOpen(false);
|
|
1624
|
+
};
|
|
1625
|
+
const selectedDays = isDay(draft.from) ? { from: fromDay(draft.from), to: isDay(draft.to) ? fromDay(draft.to) : void 0 } : void 0;
|
|
1626
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1627
|
+
Popover,
|
|
1628
|
+
{
|
|
1629
|
+
open,
|
|
1630
|
+
onOpenChange: (next) => next ? openPanel() : setOpen(false),
|
|
1631
|
+
align,
|
|
1632
|
+
className: cn("w-auto p-0", className),
|
|
1633
|
+
trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", disabled, children: formatPeriod(value) }),
|
|
1634
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex", children: [
|
|
1635
|
+
presets.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(PresetRail, { presets, selected: draft.preset, onSelect: pickPreset }),
|
|
1636
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
1637
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative p-3", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1638
|
+
reactDayPicker.DayPicker,
|
|
1639
|
+
{
|
|
1640
|
+
mode: "range",
|
|
1641
|
+
locale: fr.fr,
|
|
1642
|
+
weekStartsOn: 1,
|
|
1643
|
+
numberOfMonths: months,
|
|
1644
|
+
month,
|
|
1645
|
+
onMonthChange: setMonth,
|
|
1646
|
+
selected: selectedDays,
|
|
1647
|
+
onSelect: pickDays,
|
|
1648
|
+
modifiers: {
|
|
1649
|
+
compare: compareRange?.from ? [{ from: fromDay(compareRange.from), to: fromDay(compareRange.to) }] : []
|
|
1650
|
+
},
|
|
1651
|
+
modifiersClassNames: { compare: COMPARE_MODIFIER_CLASS },
|
|
1652
|
+
classNames: DAY_PICKER_CLASS_NAMES
|
|
1653
|
+
}
|
|
1654
|
+
) }),
|
|
1655
|
+
comparable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1656
|
+
CompareSection,
|
|
1657
|
+
{
|
|
1658
|
+
draft,
|
|
1659
|
+
compareRange,
|
|
1660
|
+
onModeChange: (compare2) => setDraft({ ...draft, compare: compare2 }),
|
|
1661
|
+
onCustomChange: (from, to) => setDraft({ ...draft, compareFrom: from, compareTo: to })
|
|
1662
|
+
}
|
|
1663
|
+
),
|
|
1664
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-4 border-t px-3 py-2", children: [
|
|
1665
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
1666
|
+
range ? formatRange(range) : "Choisissez une date de fin",
|
|
1667
|
+
compareRange && ` vs ${formatRange(compareRange)}`
|
|
1668
|
+
] }),
|
|
1669
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
1670
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", onClick: () => setOpen(false), children: "Annuler" }),
|
|
1671
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", onClick: apply, disabled: !range, children: "Appliquer" })
|
|
1672
|
+
] })
|
|
1673
|
+
] })
|
|
1674
|
+
] })
|
|
1675
|
+
] })
|
|
1676
|
+
}
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
function DateRangePicker({ value, onChange, className, disabled }) {
|
|
1680
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1681
|
+
DateRangeCompare,
|
|
1682
|
+
{
|
|
1683
|
+
value: { preset: "custom", range: value, compare: "none" },
|
|
1684
|
+
onChange: (next) => {
|
|
1685
|
+
if (next.range.from !== void 0) onChange({ from: next.range.from, to: next.range.to });
|
|
1686
|
+
},
|
|
1687
|
+
comparable: false,
|
|
1688
|
+
presets: [],
|
|
1689
|
+
disabled,
|
|
1690
|
+
className
|
|
1691
|
+
}
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
function PresetRail({
|
|
1695
|
+
presets,
|
|
1696
|
+
selected,
|
|
1697
|
+
onSelect
|
|
1698
|
+
}) {
|
|
1699
|
+
const refs = React2__namespace.useRef([]);
|
|
1700
|
+
const activeIndex = Math.max(
|
|
1701
|
+
0,
|
|
1702
|
+
presets.findIndex((preset) => preset.key === selected)
|
|
1703
|
+
);
|
|
1704
|
+
const moveTo = (index) => {
|
|
1705
|
+
const next = (index + presets.length) % presets.length;
|
|
1706
|
+
refs.current[next]?.focus();
|
|
1707
|
+
onSelect(presets[next].key);
|
|
1708
|
+
};
|
|
1709
|
+
const onKeyDown = (event, index) => {
|
|
1710
|
+
const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];
|
|
1711
|
+
if (step !== void 0) {
|
|
1712
|
+
event.preventDefault();
|
|
1713
|
+
moveTo(index + step);
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
if (event.key === "Home" || event.key === "End") {
|
|
1717
|
+
event.preventDefault();
|
|
1718
|
+
moveTo(event.key === "Home" ? 0 : presets.length - 1);
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1722
|
+
"div",
|
|
1723
|
+
{
|
|
1724
|
+
role: "radiogroup",
|
|
1725
|
+
"aria-label": "P\xE9riode pr\xE9d\xE9finie",
|
|
1726
|
+
className: "flex w-44 shrink-0 flex-col gap-0.5 border-r p-2",
|
|
1727
|
+
children: presets.map((preset, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1728
|
+
"button",
|
|
1729
|
+
{
|
|
1730
|
+
ref: (node) => {
|
|
1731
|
+
refs.current[index] = node;
|
|
1732
|
+
},
|
|
1733
|
+
type: "button",
|
|
1734
|
+
role: "radio",
|
|
1735
|
+
"aria-checked": preset.key === selected,
|
|
1736
|
+
tabIndex: index === activeIndex ? 0 : -1,
|
|
1737
|
+
onKeyDown: (event) => onKeyDown(event, index),
|
|
1738
|
+
onClick: () => onSelect(preset.key),
|
|
1739
|
+
className: cn(
|
|
1740
|
+
"rounded px-3 py-1.5 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1741
|
+
preset.key === selected ? "bg-muted font-medium text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
1742
|
+
),
|
|
1743
|
+
children: preset.label
|
|
1744
|
+
},
|
|
1745
|
+
preset.key
|
|
1746
|
+
))
|
|
1747
|
+
}
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
function CompareSection({
|
|
1751
|
+
draft,
|
|
1752
|
+
compareRange,
|
|
1753
|
+
onModeChange,
|
|
1754
|
+
onCustomChange
|
|
1755
|
+
}) {
|
|
1756
|
+
const derivable = draft.preset !== "all";
|
|
1757
|
+
const active = activeCompare(draft);
|
|
1758
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 border-t px-3 py-2", children: [
|
|
1759
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Comparer \xE0" }),
|
|
1760
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1", children: COMPARE_MODES.map((mode) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1761
|
+
"button",
|
|
1762
|
+
{
|
|
1763
|
+
type: "button",
|
|
1764
|
+
"aria-pressed": active === mode,
|
|
1765
|
+
disabled: mode !== "none" && !derivable,
|
|
1766
|
+
onClick: () => onModeChange(mode),
|
|
1767
|
+
className: cn(
|
|
1768
|
+
"rounded-full border px-2.5 py-1 text-xs transition-colors disabled:opacity-40",
|
|
1769
|
+
active === mode ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
1770
|
+
),
|
|
1771
|
+
children: COMPARE_LABELS[mode]
|
|
1772
|
+
},
|
|
1773
|
+
mode
|
|
1774
|
+
)) }),
|
|
1775
|
+
active === "custom" && // Two inputs rather than a second selection mode in the calendar: with
|
|
1776
|
+
// one grid and two ranges to fill, nothing on screen would say which one
|
|
1777
|
+
// a click is about to change.
|
|
1778
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1779
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1780
|
+
"input",
|
|
1781
|
+
{
|
|
1782
|
+
type: "date",
|
|
1783
|
+
"aria-label": "D\xE9but de la comparaison",
|
|
1784
|
+
value: draft.compareFrom ?? "",
|
|
1785
|
+
max: draft.compareTo,
|
|
1786
|
+
onChange: (event) => onCustomChange(event.target.value || void 0, draft.compareTo),
|
|
1787
|
+
className: "h-8 rounded-md border border-input bg-transparent px-2 text-sm"
|
|
1788
|
+
}
|
|
1789
|
+
),
|
|
1790
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "\u2192" }),
|
|
1791
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1792
|
+
"input",
|
|
1793
|
+
{
|
|
1794
|
+
type: "date",
|
|
1795
|
+
"aria-label": "Fin de la comparaison",
|
|
1796
|
+
value: draft.compareTo ?? "",
|
|
1797
|
+
min: draft.compareFrom,
|
|
1798
|
+
onChange: (event) => onCustomChange(draft.compareFrom, event.target.value || void 0),
|
|
1799
|
+
className: "h-8 rounded-md border border-input bg-transparent px-2 text-sm"
|
|
1800
|
+
}
|
|
1801
|
+
)
|
|
1802
|
+
] }),
|
|
1803
|
+
compareRange && active !== "custom" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: formatRange(compareRange) })
|
|
1804
|
+
] });
|
|
1805
|
+
}
|
|
1806
|
+
var CHART_COLORS = [
|
|
1807
|
+
"var(--chart-1)",
|
|
1808
|
+
"var(--chart-2)",
|
|
1809
|
+
"var(--chart-3)",
|
|
1810
|
+
"var(--chart-4)",
|
|
1811
|
+
"var(--chart-5)"
|
|
1812
|
+
];
|
|
1813
|
+
var COMPARE_COLOR = CHART_COLORS[1];
|
|
1814
|
+
var colorAt = (index) => CHART_COLORS[index % CHART_COLORS.length];
|
|
1815
|
+
var formatNumber = (value) => new Intl.NumberFormat("fr-FR").format(Math.round(value));
|
|
1816
|
+
var ChartContext = React2__namespace.createContext({ formatValue: formatNumber });
|
|
1817
|
+
function ChartContainer({
|
|
1818
|
+
children,
|
|
1819
|
+
height = 320,
|
|
1820
|
+
formatValue = formatNumber,
|
|
1821
|
+
label,
|
|
1822
|
+
className
|
|
1823
|
+
}) {
|
|
1824
|
+
const context = React2__namespace.useMemo(() => ({ formatValue }), [formatValue]);
|
|
1825
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ChartContext.Provider, { value: context, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1826
|
+
"div",
|
|
1827
|
+
{
|
|
1828
|
+
role: "img",
|
|
1829
|
+
"aria-label": label,
|
|
1830
|
+
style: { height },
|
|
1831
|
+
className: cn(
|
|
1832
|
+
"w-full text-xs",
|
|
1833
|
+
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground",
|
|
1834
|
+
"[&_.recharts-cartesian-grid_line]:stroke-border",
|
|
1835
|
+
"[&_.recharts-curve.recharts-tooltip-cursor]:stroke-border",
|
|
1836
|
+
"[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted/40",
|
|
1837
|
+
"[&_.recharts-sector]:outline-none",
|
|
1838
|
+
"[&_.recharts-surface]:outline-none",
|
|
1839
|
+
className
|
|
1840
|
+
),
|
|
1841
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children })
|
|
1842
|
+
}
|
|
1843
|
+
) });
|
|
1844
|
+
}
|
|
1845
|
+
function ChartTooltipContent({
|
|
1846
|
+
active,
|
|
1847
|
+
payload,
|
|
1848
|
+
label
|
|
1849
|
+
}) {
|
|
1850
|
+
const { formatValue } = React2__namespace.useContext(ChartContext);
|
|
1851
|
+
if (!active || !payload?.length) return null;
|
|
1852
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border bg-card px-2.5 py-1.5 text-xs shadow-md", children: [
|
|
1853
|
+
label !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-1 font-medium text-foreground", children: label }),
|
|
1854
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-0.5", children: payload.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2", children: [
|
|
1855
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1856
|
+
"span",
|
|
1857
|
+
{
|
|
1858
|
+
"aria-hidden": true,
|
|
1859
|
+
className: "h-2 w-2 shrink-0 rounded-[2px]",
|
|
1860
|
+
style: { backgroundColor: entry.color }
|
|
1861
|
+
}
|
|
1862
|
+
),
|
|
1863
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: entry.name }),
|
|
1864
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto font-medium tabular-nums text-foreground", children: typeof entry.value === "number" ? formatValue(entry.value) : entry.value })
|
|
1865
|
+
] }, `${entry.dataKey}-${index}`)) })
|
|
1866
|
+
] });
|
|
1867
|
+
}
|
|
1868
|
+
var ChartTooltip = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1869
|
+
recharts.Tooltip,
|
|
1870
|
+
{
|
|
1871
|
+
cursor: { strokeWidth: 1 },
|
|
1872
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(ChartTooltipContent, {}),
|
|
1873
|
+
wrapperStyle: { outline: "none" }
|
|
1874
|
+
}
|
|
1875
|
+
);
|
|
1876
|
+
function ChartLegendContent({ payload }) {
|
|
1877
|
+
if (!payload?.length) return null;
|
|
1878
|
+
return /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-wrap items-center justify-center gap-x-4 gap-y-1 pt-3", children: payload.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-1.5", children: [
|
|
1879
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1880
|
+
"span",
|
|
1881
|
+
{
|
|
1882
|
+
"aria-hidden": true,
|
|
1883
|
+
className: "h-2 w-2 shrink-0 rounded-[2px]",
|
|
1884
|
+
style: { backgroundColor: entry.color }
|
|
1885
|
+
}
|
|
1886
|
+
),
|
|
1887
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: entry.value })
|
|
1888
|
+
] }, `${entry.dataKey}-${index}`)) });
|
|
1889
|
+
}
|
|
1890
|
+
var ChartLegend = /* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { content: /* @__PURE__ */ jsxRuntime.jsx(ChartLegendContent, {}) });
|
|
1891
|
+
var COMPARE_LINE_PROPS = {
|
|
1892
|
+
stroke: COMPARE_COLOR,
|
|
1893
|
+
strokeDasharray: "4 4",
|
|
1894
|
+
strokeOpacity: 0.4,
|
|
1895
|
+
strokeWidth: 2,
|
|
1896
|
+
dot: false,
|
|
1897
|
+
activeDot: false,
|
|
1898
|
+
// Not a preference: Recharts animates `strokeDasharray` from `0px 0px`, so an
|
|
1899
|
+
// animated comparison line is drawn solid and only becomes dashed once the
|
|
1900
|
+
// animation ends. Off, it is dashed from the first frame — and a reference
|
|
1901
|
+
// line that draws itself in is pulling the eye towards the thing meant to
|
|
1902
|
+
// recede behind the real series anyway.
|
|
1903
|
+
isAnimationActive: false
|
|
1904
|
+
};
|
|
1905
|
+
var AXIS_PROPS = {
|
|
1906
|
+
tickLine: false,
|
|
1907
|
+
axisLine: false,
|
|
1908
|
+
tickMargin: 8
|
|
1909
|
+
};
|
|
1910
|
+
var MARK_PROPS = { isAnimationActive: false };
|
|
1911
|
+
function CartesianFrame({ x, formatValue }) {
|
|
1912
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1913
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { vertical: false, strokeDasharray: "3 3" }),
|
|
1914
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: x, ...AXIS_PROPS }),
|
|
1915
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { ...AXIS_PROPS, width: 56, tickFormatter: formatValue })
|
|
1916
|
+
] });
|
|
1917
|
+
}
|
|
1918
|
+
function accessibleLabel(series, label) {
|
|
1919
|
+
return label ?? series.map((entry) => entry.label).join(", ");
|
|
1920
|
+
}
|
|
1921
|
+
function showsLegend(props) {
|
|
1922
|
+
if (props.hideLegend) return false;
|
|
1923
|
+
return props.series.length > 1 || props.compareSeries !== void 0;
|
|
1924
|
+
}
|
|
1925
|
+
function BarChart(props) {
|
|
1926
|
+
const { data, x, series, compareSeries, formatValue = formatNumber } = props;
|
|
1927
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1928
|
+
ChartContainer,
|
|
1929
|
+
{
|
|
1930
|
+
height: props.height,
|
|
1931
|
+
formatValue,
|
|
1932
|
+
label: accessibleLabel(series, props.label),
|
|
1933
|
+
className: props.className,
|
|
1934
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.BarChart, { data, children: [
|
|
1935
|
+
/* @__PURE__ */ jsxRuntime.jsx(CartesianFrame, { x, formatValue }),
|
|
1936
|
+
ChartTooltip,
|
|
1937
|
+
showsLegend(props) && ChartLegend,
|
|
1938
|
+
series.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1939
|
+
recharts.Bar,
|
|
1940
|
+
{
|
|
1941
|
+
dataKey: entry.key,
|
|
1942
|
+
name: entry.label,
|
|
1943
|
+
fill: colorAt(entry.colorIndex ?? index),
|
|
1944
|
+
radius: [4, 4, 0, 0],
|
|
1945
|
+
...MARK_PROPS
|
|
1946
|
+
},
|
|
1947
|
+
entry.key
|
|
1948
|
+
)),
|
|
1949
|
+
compareSeries && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1950
|
+
recharts.Bar,
|
|
1951
|
+
{
|
|
1952
|
+
dataKey: compareSeries.key,
|
|
1953
|
+
name: compareSeries.label,
|
|
1954
|
+
fill: COMPARE_LINE_PROPS.stroke,
|
|
1955
|
+
fillOpacity: COMPARE_LINE_PROPS.strokeOpacity,
|
|
1956
|
+
radius: [4, 4, 0, 0],
|
|
1957
|
+
...MARK_PROPS
|
|
1958
|
+
}
|
|
1959
|
+
)
|
|
1960
|
+
] })
|
|
1961
|
+
}
|
|
1962
|
+
);
|
|
1963
|
+
}
|
|
1964
|
+
function StackedBarChart(props) {
|
|
1965
|
+
const { data, x, series, formatValue = formatNumber } = props;
|
|
1966
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1967
|
+
ChartContainer,
|
|
1968
|
+
{
|
|
1969
|
+
height: props.height,
|
|
1970
|
+
formatValue,
|
|
1971
|
+
label: accessibleLabel(series, props.label),
|
|
1972
|
+
className: props.className,
|
|
1973
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.BarChart, { data, children: [
|
|
1974
|
+
/* @__PURE__ */ jsxRuntime.jsx(CartesianFrame, { x, formatValue }),
|
|
1975
|
+
ChartTooltip,
|
|
1976
|
+
showsLegend(props) && ChartLegend,
|
|
1977
|
+
series.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1978
|
+
recharts.Bar,
|
|
1979
|
+
{
|
|
1980
|
+
dataKey: entry.key,
|
|
1981
|
+
name: entry.label,
|
|
1982
|
+
stackId: "stack",
|
|
1983
|
+
fill: colorAt(entry.colorIndex ?? index),
|
|
1984
|
+
radius: 0,
|
|
1985
|
+
...MARK_PROPS
|
|
1986
|
+
},
|
|
1987
|
+
entry.key
|
|
1988
|
+
))
|
|
1989
|
+
] })
|
|
1990
|
+
}
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
function LineChart(props) {
|
|
1994
|
+
const { data, x, series, compareSeries, formatValue = formatNumber } = props;
|
|
1995
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1996
|
+
ChartContainer,
|
|
1997
|
+
{
|
|
1998
|
+
height: props.height,
|
|
1999
|
+
formatValue,
|
|
2000
|
+
label: accessibleLabel(series, props.label),
|
|
2001
|
+
className: props.className,
|
|
2002
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.LineChart, { data, children: [
|
|
2003
|
+
/* @__PURE__ */ jsxRuntime.jsx(CartesianFrame, { x, formatValue }),
|
|
2004
|
+
ChartTooltip,
|
|
2005
|
+
showsLegend(props) && ChartLegend,
|
|
2006
|
+
series.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2007
|
+
recharts.Line,
|
|
2008
|
+
{
|
|
2009
|
+
type: "monotone",
|
|
2010
|
+
dataKey: entry.key,
|
|
2011
|
+
name: entry.label,
|
|
2012
|
+
stroke: colorAt(entry.colorIndex ?? index),
|
|
2013
|
+
strokeWidth: 2,
|
|
2014
|
+
dot: false,
|
|
2015
|
+
...MARK_PROPS
|
|
2016
|
+
},
|
|
2017
|
+
entry.key
|
|
2018
|
+
)),
|
|
2019
|
+
compareSeries && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2020
|
+
recharts.Line,
|
|
2021
|
+
{
|
|
2022
|
+
type: "monotone",
|
|
2023
|
+
dataKey: compareSeries.key,
|
|
2024
|
+
name: compareSeries.label,
|
|
2025
|
+
...COMPARE_LINE_PROPS
|
|
2026
|
+
}
|
|
2027
|
+
)
|
|
2028
|
+
] })
|
|
2029
|
+
}
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
function AreaChart(props) {
|
|
2033
|
+
const { data, x, series, compareSeries, formatValue = formatNumber } = props;
|
|
2034
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2035
|
+
ChartContainer,
|
|
2036
|
+
{
|
|
2037
|
+
height: props.height,
|
|
2038
|
+
formatValue,
|
|
2039
|
+
label: accessibleLabel(series, props.label),
|
|
2040
|
+
className: props.className,
|
|
2041
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.AreaChart, { data, children: [
|
|
2042
|
+
/* @__PURE__ */ jsxRuntime.jsx(CartesianFrame, { x, formatValue }),
|
|
2043
|
+
ChartTooltip,
|
|
2044
|
+
showsLegend(props) && ChartLegend,
|
|
2045
|
+
series.map((entry, index) => {
|
|
2046
|
+
const color = colorAt(entry.colorIndex ?? index);
|
|
2047
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2048
|
+
recharts.Area,
|
|
2049
|
+
{
|
|
2050
|
+
type: "monotone",
|
|
2051
|
+
dataKey: entry.key,
|
|
2052
|
+
name: entry.label,
|
|
2053
|
+
stroke: color,
|
|
2054
|
+
strokeWidth: 2,
|
|
2055
|
+
fill: color,
|
|
2056
|
+
fillOpacity: 0.15,
|
|
2057
|
+
...MARK_PROPS
|
|
2058
|
+
},
|
|
2059
|
+
entry.key
|
|
2060
|
+
);
|
|
2061
|
+
}),
|
|
2062
|
+
compareSeries && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2063
|
+
recharts.Area,
|
|
2064
|
+
{
|
|
2065
|
+
type: "monotone",
|
|
2066
|
+
dataKey: compareSeries.key,
|
|
2067
|
+
name: compareSeries.label,
|
|
2068
|
+
fill: "none",
|
|
2069
|
+
...COMPARE_LINE_PROPS
|
|
2070
|
+
}
|
|
2071
|
+
)
|
|
2072
|
+
] })
|
|
2073
|
+
}
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
function FunnelChart({
|
|
2077
|
+
data,
|
|
2078
|
+
formatValue = formatNumber,
|
|
2079
|
+
height,
|
|
2080
|
+
label,
|
|
2081
|
+
className
|
|
2082
|
+
}) {
|
|
2083
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2084
|
+
ChartContainer,
|
|
2085
|
+
{
|
|
2086
|
+
height,
|
|
2087
|
+
formatValue,
|
|
2088
|
+
label: label ?? data.map((step) => step.label).join(", "),
|
|
2089
|
+
className,
|
|
2090
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.FunnelChart, { children: [
|
|
2091
|
+
ChartTooltip,
|
|
2092
|
+
/* @__PURE__ */ jsxRuntime.jsxs(recharts.Funnel, { dataKey: "value", nameKey: "label", data, ...MARK_PROPS, children: [
|
|
2093
|
+
data.map((step, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: colorAt(index) }, step.label)),
|
|
2094
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2095
|
+
recharts.LabelList,
|
|
2096
|
+
{
|
|
2097
|
+
dataKey: "label",
|
|
2098
|
+
position: "right",
|
|
2099
|
+
className: "fill-foreground",
|
|
2100
|
+
stroke: "none",
|
|
2101
|
+
fontSize: 12
|
|
2102
|
+
}
|
|
2103
|
+
)
|
|
2104
|
+
] })
|
|
2105
|
+
] })
|
|
2106
|
+
}
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2109
|
+
function relativeDelta(value, compareValue) {
|
|
2110
|
+
if (compareValue === void 0 || compareValue === 0) return null;
|
|
2111
|
+
return (value - compareValue) / Math.abs(compareValue);
|
|
2112
|
+
}
|
|
2113
|
+
var DELTA_FORMAT = new Intl.NumberFormat("fr-FR", {
|
|
2114
|
+
style: "percent",
|
|
2115
|
+
maximumFractionDigits: 1,
|
|
2116
|
+
signDisplay: "exceptZero"
|
|
2117
|
+
});
|
|
2118
|
+
function KpiCard({
|
|
2119
|
+
label,
|
|
2120
|
+
value,
|
|
2121
|
+
compareValue,
|
|
2122
|
+
formatValue = formatNumber,
|
|
2123
|
+
lowerIsBetter = false,
|
|
2124
|
+
sparkline,
|
|
2125
|
+
className
|
|
2126
|
+
}) {
|
|
2127
|
+
const delta = relativeDelta(value, compareValue);
|
|
2128
|
+
const improved = delta === null ? null : delta > 0 !== lowerIsBetter;
|
|
2129
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(CardContent, { className: "space-y-1 p-4", children: [
|
|
2130
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: label }),
|
|
2131
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-semibold tabular-nums text-foreground", children: formatValue(value) }),
|
|
2132
|
+
delta !== null && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2133
|
+
"p",
|
|
2134
|
+
{
|
|
2135
|
+
className: cn(
|
|
2136
|
+
"text-xs font-medium tabular-nums",
|
|
2137
|
+
// Direction is in the sign as well as the colour: a red figure and
|
|
2138
|
+
// a green one differ by more than hue for nobody who cannot see it.
|
|
2139
|
+
improved ? "text-status-success" : "text-status-blocked"
|
|
2140
|
+
),
|
|
2141
|
+
children: [
|
|
2142
|
+
DELTA_FORMAT.format(delta),
|
|
2143
|
+
compareValue !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-1 font-normal text-muted-foreground", children: [
|
|
2144
|
+
"vs ",
|
|
2145
|
+
formatValue(compareValue)
|
|
2146
|
+
] })
|
|
2147
|
+
]
|
|
2148
|
+
}
|
|
2149
|
+
),
|
|
2150
|
+
sparkline && sparkline.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(ChartContainer, { height: 40, label: `Tendance \u2014 ${label}`, className: "pt-1", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.LineChart, { data: sparkline.map((point, index) => ({ index, point })), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2151
|
+
recharts.Line,
|
|
2152
|
+
{
|
|
2153
|
+
type: "monotone",
|
|
2154
|
+
dataKey: "point",
|
|
2155
|
+
stroke: colorAt(0),
|
|
2156
|
+
strokeWidth: 1.5,
|
|
2157
|
+
dot: false,
|
|
2158
|
+
...MARK_PROPS
|
|
2159
|
+
}
|
|
2160
|
+
) }) })
|
|
2161
|
+
] }) });
|
|
2162
|
+
}
|
|
2163
|
+
var ToastContext = React2.createContext(null);
|
|
843
2164
|
function useToast() {
|
|
844
|
-
const toast =
|
|
2165
|
+
const toast = React2.useContext(ToastContext);
|
|
845
2166
|
if (!toast) throw new Error("useToast must be used within ToastProvider");
|
|
846
2167
|
return toast;
|
|
847
2168
|
}
|
|
@@ -852,9 +2173,9 @@ var TONE = {
|
|
|
852
2173
|
info: "border-l-status-info"
|
|
853
2174
|
};
|
|
854
2175
|
function ToastProvider({ children }) {
|
|
855
|
-
const [toasts, setToasts] =
|
|
856
|
-
const nextId =
|
|
857
|
-
const toast =
|
|
2176
|
+
const [toasts, setToasts] = React2.useState([]);
|
|
2177
|
+
const nextId = React2.useRef(0);
|
|
2178
|
+
const toast = React2.useCallback((input) => {
|
|
858
2179
|
const id = nextId.current++;
|
|
859
2180
|
setToasts((prev) => [...prev, { id, ...input }]);
|
|
860
2181
|
setTimeout(() => setToasts((prev) => prev.filter((t) => t.id !== id)), TOAST_TTL_MS);
|
|
@@ -884,7 +2205,7 @@ var SIDES = {
|
|
|
884
2205
|
bottom: "top-full left-1/2 -translate-x-1/2 mt-1.5",
|
|
885
2206
|
left: "right-full top-1/2 -translate-y-1/2 mr-1.5"
|
|
886
2207
|
};
|
|
887
|
-
function
|
|
2208
|
+
function Tooltip2({ label, children, side = "top", className }) {
|
|
888
2209
|
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "group/tt relative inline-flex", children: [
|
|
889
2210
|
children,
|
|
890
2211
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -905,8 +2226,11 @@ function Tooltip({ label, children, side = "top", className }) {
|
|
|
905
2226
|
exports.AppApiProvider = AppApiProvider;
|
|
906
2227
|
exports.AppContextProvider = AppContextProvider;
|
|
907
2228
|
exports.AppInvokeError = AppInvokeError;
|
|
2229
|
+
exports.AreaChart = AreaChart;
|
|
908
2230
|
exports.Badge = Badge;
|
|
2231
|
+
exports.BarChart = BarChart;
|
|
909
2232
|
exports.Button = Button;
|
|
2233
|
+
exports.COMPARE_LABELS = COMPARE_LABELS;
|
|
910
2234
|
exports.Card = Card;
|
|
911
2235
|
exports.CardContent = CardContent;
|
|
912
2236
|
exports.CardDescription = CardDescription;
|
|
@@ -915,15 +2239,22 @@ exports.CardHeader = CardHeader;
|
|
|
915
2239
|
exports.CardTitle = CardTitle;
|
|
916
2240
|
exports.Checkbox = Checkbox;
|
|
917
2241
|
exports.CopyButton = CopyButton;
|
|
2242
|
+
exports.DataTable = DataTable;
|
|
2243
|
+
exports.DateRangeCompare = DateRangeCompare;
|
|
918
2244
|
exports.DateRangePicker = DateRangePicker;
|
|
919
2245
|
exports.DropdownMenu = DropdownMenu;
|
|
2246
|
+
exports.FunnelChart = FunnelChart;
|
|
920
2247
|
exports.Input = Input;
|
|
2248
|
+
exports.KpiCard = KpiCard;
|
|
2249
|
+
exports.LineChart = LineChart;
|
|
921
2250
|
exports.Link = Link;
|
|
922
2251
|
exports.Modal = Modal;
|
|
2252
|
+
exports.PERIOD_PRESETS = PERIOD_PRESETS;
|
|
923
2253
|
exports.Popover = Popover;
|
|
924
2254
|
exports.Select = Select;
|
|
925
2255
|
exports.SidePanel = SidePanel;
|
|
926
2256
|
exports.Spinner = Spinner;
|
|
2257
|
+
exports.StackedBarChart = StackedBarChart;
|
|
927
2258
|
exports.Switch = Switch;
|
|
928
2259
|
exports.Table = Table;
|
|
929
2260
|
exports.TableBody = TableBody;
|
|
@@ -937,13 +2268,19 @@ exports.TabsList = TabsList;
|
|
|
937
2268
|
exports.TabsTrigger = TabsTrigger;
|
|
938
2269
|
exports.Textarea = Textarea;
|
|
939
2270
|
exports.ToastProvider = ToastProvider;
|
|
940
|
-
exports.Tooltip =
|
|
2271
|
+
exports.Tooltip = Tooltip2;
|
|
941
2272
|
exports.badgeVariants = badgeVariants;
|
|
942
2273
|
exports.buttonVariants = buttonVariants;
|
|
943
2274
|
exports.cn = cn;
|
|
2275
|
+
exports.comparisonRange = comparisonRange;
|
|
944
2276
|
exports.copyToClipboard = copyToClipboard;
|
|
945
2277
|
exports.downloadBlob = downloadBlob;
|
|
946
2278
|
exports.downloadCsv = downloadCsv;
|
|
2279
|
+
exports.formatPeriod = formatPeriod;
|
|
2280
|
+
exports.formatRange = formatRange;
|
|
2281
|
+
exports.makePeriod = makePeriod;
|
|
2282
|
+
exports.periodFromPreset = periodFromPreset;
|
|
2283
|
+
exports.relativeDelta = relativeDelta;
|
|
947
2284
|
exports.toCsv = toCsv;
|
|
948
2285
|
exports.useAppApi = useAppApi;
|
|
949
2286
|
exports.useAppContext = useAppContext;
|