@apotech-os/ui-sdk 1.0.0 → 2.1.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 +1615 -245
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +352 -23
- package/dist/index.d.ts +352 -23
- package/dist/index.js +1560 -213
- package/dist/index.js.map +1 -1
- package/package.json +11 -4
package/dist/index.cjs
CHANGED
|
@@ -2,9 +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
|
+
var dialog = require('@base-ui/react/dialog');
|
|
7
|
+
var menu = require('@base-ui/react/menu');
|
|
8
|
+
var checkbox = require('@base-ui/react/checkbox');
|
|
9
|
+
var popover = require('@base-ui/react/popover');
|
|
10
|
+
var select = require('@base-ui/react/select');
|
|
11
|
+
var _switch = require('@base-ui/react/switch');
|
|
12
|
+
var tabs = require('@base-ui/react/tabs');
|
|
6
13
|
var classVarianceAuthority = require('class-variance-authority');
|
|
7
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');
|
|
8
21
|
|
|
9
22
|
function _interopNamespace(e) {
|
|
10
23
|
if (e && e.__esModule) return e;
|
|
@@ -24,16 +37,16 @@ function _interopNamespace(e) {
|
|
|
24
37
|
return Object.freeze(n);
|
|
25
38
|
}
|
|
26
39
|
|
|
27
|
-
var
|
|
40
|
+
var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
|
|
28
41
|
|
|
29
42
|
// src/cn.ts
|
|
30
43
|
function cn(...inputs) {
|
|
31
44
|
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
32
45
|
}
|
|
33
|
-
var AppContextInternalCtx =
|
|
46
|
+
var AppContextInternalCtx = React2.createContext(null);
|
|
34
47
|
var AppContextProvider = AppContextInternalCtx.Provider;
|
|
35
48
|
function useAppContext() {
|
|
36
|
-
const ctx =
|
|
49
|
+
const ctx = React2.useContext(AppContextInternalCtx);
|
|
37
50
|
if (!ctx) throw new Error("useAppContext must be used inside an app page mounted by the shell");
|
|
38
51
|
return ctx;
|
|
39
52
|
}
|
|
@@ -46,10 +59,10 @@ var AppInvokeError = class extends Error {
|
|
|
46
59
|
this.name = "AppInvokeError";
|
|
47
60
|
}
|
|
48
61
|
};
|
|
49
|
-
var AppApiCtx =
|
|
62
|
+
var AppApiCtx = React2.createContext(null);
|
|
50
63
|
var AppApiProvider = AppApiCtx.Provider;
|
|
51
64
|
function useAppApi() {
|
|
52
|
-
const client =
|
|
65
|
+
const client = React2.useContext(AppApiCtx);
|
|
53
66
|
if (!client) throw new Error("useAppApi must be used inside an app page mounted by the shell");
|
|
54
67
|
return client;
|
|
55
68
|
}
|
|
@@ -57,16 +70,16 @@ function useAppSettings() {
|
|
|
57
70
|
const { appSlug } = useAppContext();
|
|
58
71
|
const api = useAppApi();
|
|
59
72
|
const path = `/data/apps/${appSlug}/settings`;
|
|
60
|
-
const [settings, setSettings] =
|
|
61
|
-
const [isLoading, setIsLoading] =
|
|
62
|
-
const [error, setError] =
|
|
63
|
-
const [isSaving, setIsSaving] =
|
|
64
|
-
const [saveError, setSaveError] =
|
|
65
|
-
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);
|
|
66
79
|
clientRef.current = api;
|
|
67
|
-
const requestIdRef =
|
|
68
|
-
const isMountedRef =
|
|
69
|
-
const load =
|
|
80
|
+
const requestIdRef = React2.useRef(0);
|
|
81
|
+
const isMountedRef = React2.useRef(true);
|
|
82
|
+
const load = React2.useCallback(
|
|
70
83
|
async ({ silent } = {}) => {
|
|
71
84
|
const requestId = ++requestIdRef.current;
|
|
72
85
|
if (!silent) setIsLoading(true);
|
|
@@ -84,7 +97,7 @@ function useAppSettings() {
|
|
|
84
97
|
},
|
|
85
98
|
[path]
|
|
86
99
|
);
|
|
87
|
-
|
|
100
|
+
React2.useEffect(() => {
|
|
88
101
|
isMountedRef.current = true;
|
|
89
102
|
void load();
|
|
90
103
|
return () => {
|
|
@@ -92,7 +105,7 @@ function useAppSettings() {
|
|
|
92
105
|
requestIdRef.current += 1;
|
|
93
106
|
};
|
|
94
107
|
}, [load]);
|
|
95
|
-
const save =
|
|
108
|
+
const save = React2.useCallback(
|
|
96
109
|
async (patch) => {
|
|
97
110
|
setIsSaving(true);
|
|
98
111
|
setSaveError(void 0);
|
|
@@ -134,15 +147,15 @@ function useAppRun(runId, opts = {}) {
|
|
|
134
147
|
const api = useAppApi();
|
|
135
148
|
const intervalMs = opts.intervalMs ?? DEFAULT_RUN_POLL_INTERVAL_MS;
|
|
136
149
|
const maxIntervalMs = opts.maxIntervalMs ?? MAX_RUN_POLL_INTERVAL_MS;
|
|
137
|
-
const [run, setRun] =
|
|
138
|
-
const [isPolling, setIsPolling] =
|
|
139
|
-
const [error, setError] =
|
|
140
|
-
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);
|
|
141
154
|
clientRef.current = api;
|
|
142
|
-
const requestIdRef =
|
|
143
|
-
const isMountedRef =
|
|
155
|
+
const requestIdRef = React2.useRef(0);
|
|
156
|
+
const isMountedRef = React2.useRef(true);
|
|
144
157
|
const path = runId !== null ? `/data/apps/${appSlug}/runs/${encodeURIComponent(runId)}` : null;
|
|
145
|
-
const fetchOnce =
|
|
158
|
+
const fetchOnce = React2.useCallback(async () => {
|
|
146
159
|
if (path === null) return { kind: "no-path" };
|
|
147
160
|
const requestId = ++requestIdRef.current;
|
|
148
161
|
try {
|
|
@@ -157,7 +170,7 @@ function useAppRun(runId, opts = {}) {
|
|
|
157
170
|
return { kind: "error", err };
|
|
158
171
|
}
|
|
159
172
|
}, [path]);
|
|
160
|
-
|
|
173
|
+
React2.useEffect(() => {
|
|
161
174
|
isMountedRef.current = true;
|
|
162
175
|
requestIdRef.current += 1;
|
|
163
176
|
setRun(void 0);
|
|
@@ -209,7 +222,7 @@ function useAppRun(runId, opts = {}) {
|
|
|
209
222
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
210
223
|
};
|
|
211
224
|
}, [path, fetchOnce, intervalMs, maxIntervalMs]);
|
|
212
|
-
const refresh =
|
|
225
|
+
const refresh = React2.useCallback(async () => {
|
|
213
226
|
await fetchOnce();
|
|
214
227
|
}, [fetchOnce]);
|
|
215
228
|
return { run, isPolling, error, refresh };
|
|
@@ -218,13 +231,13 @@ function useAppRuns(opts) {
|
|
|
218
231
|
const { appSlug } = useAppContext();
|
|
219
232
|
const api = useAppApi();
|
|
220
233
|
const follow = opts.follow ?? true;
|
|
221
|
-
const [runs, setRuns] =
|
|
222
|
-
const [isLoading, setIsLoading] =
|
|
223
|
-
const [error, setError] =
|
|
224
|
-
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);
|
|
225
238
|
clientRef.current = api;
|
|
226
|
-
const requestIdRef =
|
|
227
|
-
const isMountedRef =
|
|
239
|
+
const requestIdRef = React2.useRef(0);
|
|
240
|
+
const isMountedRef = React2.useRef(true);
|
|
228
241
|
const params = {};
|
|
229
242
|
if (opts.kind) params["kind"] = opts.kind;
|
|
230
243
|
if (opts.key) params["key"] = opts.key;
|
|
@@ -232,7 +245,7 @@ function useAppRuns(opts) {
|
|
|
232
245
|
if (opts.limit) params["limit"] = String(opts.limit);
|
|
233
246
|
const paramsKey = JSON.stringify(params);
|
|
234
247
|
const path = `/data/apps/${appSlug}/runs`;
|
|
235
|
-
const fetchOnce =
|
|
248
|
+
const fetchOnce = React2.useCallback(
|
|
236
249
|
async ({ silent } = {}) => {
|
|
237
250
|
const requestId = ++requestIdRef.current;
|
|
238
251
|
if (!silent) setIsLoading(true);
|
|
@@ -255,7 +268,7 @@ function useAppRuns(opts) {
|
|
|
255
268
|
},
|
|
256
269
|
[path, paramsKey]
|
|
257
270
|
);
|
|
258
|
-
|
|
271
|
+
React2.useEffect(() => {
|
|
259
272
|
isMountedRef.current = true;
|
|
260
273
|
let timeoutHandle;
|
|
261
274
|
let cancelled = false;
|
|
@@ -273,7 +286,7 @@ function useAppRuns(opts) {
|
|
|
273
286
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
274
287
|
};
|
|
275
288
|
}, [fetchOnce, follow]);
|
|
276
|
-
const refresh =
|
|
289
|
+
const refresh = React2.useCallback(async () => {
|
|
277
290
|
await fetchOnce({ silent: true });
|
|
278
291
|
}, [fetchOnce]);
|
|
279
292
|
return { runs, isLoading, error, refresh };
|
|
@@ -310,6 +323,12 @@ function downloadCsv(rows, filename) {
|
|
|
310
323
|
const blob = new Blob([`${BOM}${toCsv(rows)}`], { type: "text/csv;charset=utf-8" });
|
|
311
324
|
downloadBlob(blob, filename);
|
|
312
325
|
}
|
|
326
|
+
function triggerProps(trigger) {
|
|
327
|
+
if (React2__namespace.isValidElement(trigger)) {
|
|
328
|
+
return { render: trigger, nativeButton: trigger.type === "button" || trigger.type === Button };
|
|
329
|
+
}
|
|
330
|
+
return { render: /* @__PURE__ */ jsxRuntime.jsx("span", { children: trigger }), nativeButton: false };
|
|
331
|
+
}
|
|
313
332
|
var buttonVariants = classVarianceAuthority.cva(
|
|
314
333
|
"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
|
315
334
|
{
|
|
@@ -319,7 +338,7 @@ var buttonVariants = classVarianceAuthority.cva(
|
|
|
319
338
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
320
339
|
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
321
340
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
322
|
-
destructive: "bg-destructive text-
|
|
341
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
323
342
|
},
|
|
324
343
|
size: {
|
|
325
344
|
default: "h-9 px-4 py-2",
|
|
@@ -331,7 +350,7 @@ var buttonVariants = classVarianceAuthority.cva(
|
|
|
331
350
|
defaultVariants: { variant: "default", size: "default" }
|
|
332
351
|
}
|
|
333
352
|
);
|
|
334
|
-
var Button =
|
|
353
|
+
var Button = React2__namespace.forwardRef(
|
|
335
354
|
({ className, variant, size, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx("button", { ref, className: cn(buttonVariants({ variant, size, className })), ...props })
|
|
336
355
|
);
|
|
337
356
|
Button.displayName = "Button";
|
|
@@ -373,10 +392,19 @@ function CardHeader({ className, ...props }) {
|
|
|
373
392
|
function CardTitle({ className, ...props }) {
|
|
374
393
|
return /* @__PURE__ */ jsxRuntime.jsx("h3", { className: cn("text-lg font-semibold leading-none tracking-tight", className), ...props });
|
|
375
394
|
}
|
|
395
|
+
function CardDescription({
|
|
396
|
+
className,
|
|
397
|
+
...props
|
|
398
|
+
}) {
|
|
399
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("text-sm text-muted-foreground", className), ...props });
|
|
400
|
+
}
|
|
376
401
|
function CardContent({ className, ...props }) {
|
|
377
402
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("p-6 pt-0", className), ...props });
|
|
378
403
|
}
|
|
379
|
-
|
|
404
|
+
function CardFooter({ className, ...props }) {
|
|
405
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex items-center p-6 pt-0", className), ...props });
|
|
406
|
+
}
|
|
407
|
+
var Input = React2__namespace.forwardRef(({ className, type, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
380
408
|
"input",
|
|
381
409
|
{
|
|
382
410
|
type,
|
|
@@ -396,7 +424,13 @@ function TableHeader({
|
|
|
396
424
|
className,
|
|
397
425
|
...props
|
|
398
426
|
}) {
|
|
399
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
427
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
428
|
+
"thead",
|
|
429
|
+
{
|
|
430
|
+
className: cn("[&_tr]:border-b [&_tr]:hover:bg-transparent", className),
|
|
431
|
+
...props
|
|
432
|
+
}
|
|
433
|
+
);
|
|
400
434
|
}
|
|
401
435
|
function TableBody({ className, ...props }) {
|
|
402
436
|
return /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: cn("[&_tr:last-child]:border-0", className), ...props });
|
|
@@ -418,7 +452,7 @@ function TableHead({ className, ...props }) {
|
|
|
418
452
|
"th",
|
|
419
453
|
{
|
|
420
454
|
className: cn(
|
|
421
|
-
"h-10 px-4 text-left align-middle text-xs font-medium text-muted-foreground",
|
|
455
|
+
"h-10 px-4 text-left align-middle text-xs font-medium uppercase tracking-wide text-muted-foreground",
|
|
422
456
|
className
|
|
423
457
|
),
|
|
424
458
|
...props
|
|
@@ -428,57 +462,92 @@ function TableHead({ className, ...props }) {
|
|
|
428
462
|
function TableCell({ className, ...props }) {
|
|
429
463
|
return /* @__PURE__ */ jsxRuntime.jsx("td", { className: cn("p-4 align-middle", className), ...props });
|
|
430
464
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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,
|
|
434
478
|
{
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
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
|
+
]
|
|
442
511
|
}
|
|
443
|
-
)
|
|
444
|
-
);
|
|
445
|
-
Select.displayName = "Select";
|
|
446
|
-
var TabsContext = React__namespace.createContext(null);
|
|
447
|
-
function useTabsContext() {
|
|
448
|
-
const ctx = React__namespace.useContext(TabsContext);
|
|
449
|
-
if (!ctx) throw new Error("Tabs sub-component must be used within <Tabs>");
|
|
450
|
-
return ctx;
|
|
512
|
+
);
|
|
451
513
|
}
|
|
452
514
|
function Tabs({ defaultValue, value, onValueChange, className, children }) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
515
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
516
|
+
tabs.Tabs.Root,
|
|
517
|
+
{
|
|
518
|
+
className,
|
|
519
|
+
defaultValue,
|
|
520
|
+
value,
|
|
521
|
+
onValueChange: (next) => onValueChange?.(String(next)),
|
|
522
|
+
children
|
|
523
|
+
}
|
|
524
|
+
);
|
|
460
525
|
}
|
|
461
526
|
function TabsList({
|
|
462
527
|
className,
|
|
463
528
|
children
|
|
464
529
|
}) {
|
|
465
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
530
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
531
|
+
tabs.Tabs.List,
|
|
532
|
+
{
|
|
533
|
+
className: cn("inline-flex items-center gap-1 rounded-lg bg-muted p-1", className),
|
|
534
|
+
children
|
|
535
|
+
}
|
|
536
|
+
);
|
|
466
537
|
}
|
|
467
538
|
function TabsTrigger({
|
|
468
539
|
value,
|
|
469
540
|
className,
|
|
470
541
|
children
|
|
471
542
|
}) {
|
|
472
|
-
const { value: current, setValue } = useTabsContext();
|
|
473
|
-
const active = current === value;
|
|
474
543
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
475
|
-
|
|
544
|
+
tabs.Tabs.Tab,
|
|
476
545
|
{
|
|
477
|
-
|
|
478
|
-
onClick: () => setValue(value),
|
|
546
|
+
value,
|
|
479
547
|
className: cn(
|
|
480
548
|
"rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
|
|
481
|
-
|
|
549
|
+
"text-muted-foreground hover:text-foreground",
|
|
550
|
+
"data-[active]:bg-card data-[active]:text-foreground data-[active]:shadow-sm",
|
|
482
551
|
className
|
|
483
552
|
),
|
|
484
553
|
children
|
|
@@ -490,38 +559,25 @@ function TabsContent({
|
|
|
490
559
|
className,
|
|
491
560
|
children
|
|
492
561
|
}) {
|
|
493
|
-
|
|
494
|
-
if (current !== value) return null;
|
|
495
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children });
|
|
562
|
+
return /* @__PURE__ */ jsxRuntime.jsx(tabs.Tabs.Panel, { value, className, children });
|
|
496
563
|
}
|
|
497
564
|
function Modal({ open, onClose, title, children, className }) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
501
|
-
"button",
|
|
502
|
-
{
|
|
503
|
-
type: "button",
|
|
504
|
-
"aria-label": "Close",
|
|
505
|
-
className: "absolute inset-0 cursor-default bg-foreground/40",
|
|
506
|
-
onClick: onClose
|
|
507
|
-
}
|
|
508
|
-
),
|
|
565
|
+
return /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Root, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(dialog.Dialog.Portal, { children: [
|
|
566
|
+
/* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Backdrop, { "aria-label": "Close", className: "fixed inset-0 z-50 bg-overlay" }),
|
|
509
567
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
510
|
-
|
|
568
|
+
dialog.Dialog.Popup,
|
|
511
569
|
{
|
|
512
|
-
role: "dialog",
|
|
513
|
-
"aria-modal": true,
|
|
514
570
|
className: cn(
|
|
515
|
-
"
|
|
571
|
+
"fixed left-1/2 top-1/2 z-50 w-[calc(100%-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-xl border bg-card p-5 shadow-lg",
|
|
516
572
|
className
|
|
517
573
|
),
|
|
518
574
|
children: [
|
|
519
|
-
title && /* @__PURE__ */ jsxRuntime.jsx(
|
|
575
|
+
title && /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Title, { className: "mb-3 text-base font-semibold", children: title }),
|
|
520
576
|
children
|
|
521
577
|
]
|
|
522
578
|
}
|
|
523
579
|
)
|
|
524
|
-
] });
|
|
580
|
+
] }) });
|
|
525
581
|
}
|
|
526
582
|
var spinnerSizes = { sm: "size-4", md: "size-6", lg: "size-8" };
|
|
527
583
|
function Spinner({ className, size = "md" }) {
|
|
@@ -548,7 +604,7 @@ function Spinner({ className, size = "md" }) {
|
|
|
548
604
|
}
|
|
549
605
|
);
|
|
550
606
|
}
|
|
551
|
-
var Link =
|
|
607
|
+
var Link = React2__namespace.forwardRef(
|
|
552
608
|
({ className, variant = "default", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
553
609
|
"a",
|
|
554
610
|
{
|
|
@@ -571,58 +627,22 @@ function Switch({
|
|
|
571
627
|
...props
|
|
572
628
|
}) {
|
|
573
629
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
574
|
-
|
|
630
|
+
_switch.Switch.Root,
|
|
575
631
|
{
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
"aria-checked": checked,
|
|
632
|
+
checked,
|
|
633
|
+
onCheckedChange: (next) => onCheckedChange?.(next),
|
|
579
634
|
disabled,
|
|
580
|
-
onClick: () => onCheckedChange?.(!checked),
|
|
581
635
|
className: cn(
|
|
582
|
-
|
|
583
|
-
|
|
636
|
+
// `data-[disabled]`, not `disabled:` — the root is a span, so `:disabled`
|
|
637
|
+
// never matches it and the dimming would silently stop happening.
|
|
638
|
+
"relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full bg-input transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[checked]:bg-primary data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
|
|
584
639
|
className
|
|
585
640
|
),
|
|
586
641
|
...props,
|
|
587
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
588
|
-
"span",
|
|
589
|
-
{
|
|
590
|
-
className: cn(
|
|
591
|
-
"inline-block size-4 transform rounded-full bg-white shadow transition-transform",
|
|
592
|
-
checked ? "translate-x-4" : "translate-x-0.5"
|
|
593
|
-
)
|
|
594
|
-
}
|
|
595
|
-
)
|
|
642
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(_switch.Switch.Thumb, { className: "inline-block size-4 translate-x-0.5 rounded-full bg-white shadow transition-transform data-[checked]:translate-x-4" })
|
|
596
643
|
}
|
|
597
644
|
);
|
|
598
645
|
}
|
|
599
|
-
function DateRangePicker({ value, onChange, className, disabled }) {
|
|
600
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-center gap-2", className), children: [
|
|
601
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
602
|
-
"input",
|
|
603
|
-
{
|
|
604
|
-
type: "date",
|
|
605
|
-
disabled,
|
|
606
|
-
value: value.from,
|
|
607
|
-
max: value.to || void 0,
|
|
608
|
-
onChange: (e) => onChange({ ...value, from: e.target.value }),
|
|
609
|
-
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"
|
|
610
|
-
}
|
|
611
|
-
),
|
|
612
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "\u2192" }),
|
|
613
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
614
|
-
"input",
|
|
615
|
-
{
|
|
616
|
-
type: "date",
|
|
617
|
-
disabled,
|
|
618
|
-
value: value.to,
|
|
619
|
-
min: value.from || void 0,
|
|
620
|
-
onChange: (e) => onChange({ ...value, to: e.target.value }),
|
|
621
|
-
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"
|
|
622
|
-
}
|
|
623
|
-
)
|
|
624
|
-
] });
|
|
625
|
-
}
|
|
626
646
|
function IconX({ className }) {
|
|
627
647
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
628
648
|
"svg",
|
|
@@ -676,43 +696,69 @@ function IconCheck({ className }) {
|
|
|
676
696
|
}
|
|
677
697
|
);
|
|
678
698
|
}
|
|
679
|
-
var Textarea =
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
716
762
|
function SidePanel({
|
|
717
763
|
open,
|
|
718
764
|
onClose,
|
|
@@ -721,47 +767,26 @@ function SidePanel({
|
|
|
721
767
|
children,
|
|
722
768
|
className
|
|
723
769
|
}) {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
const onKeyDown = (e) => {
|
|
727
|
-
if (e.key === "Escape") onClose();
|
|
728
|
-
};
|
|
729
|
-
window.addEventListener("keydown", onKeyDown);
|
|
730
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
731
|
-
}, [open, onClose]);
|
|
732
|
-
if (!open) return null;
|
|
733
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50", children: [
|
|
734
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
735
|
-
"button",
|
|
736
|
-
{
|
|
737
|
-
type: "button",
|
|
738
|
-
"aria-label": "Fermer",
|
|
739
|
-
className: "absolute inset-0 cursor-default bg-foreground/40",
|
|
740
|
-
onClick: onClose
|
|
741
|
-
}
|
|
742
|
-
),
|
|
770
|
+
return /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Root, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(dialog.Dialog.Portal, { children: [
|
|
771
|
+
/* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Backdrop, { "aria-label": "Fermer", className: "fixed inset-0 z-50 bg-overlay" }),
|
|
743
772
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
744
|
-
|
|
773
|
+
dialog.Dialog.Popup,
|
|
745
774
|
{
|
|
746
|
-
role: "dialog",
|
|
747
|
-
"aria-modal": true,
|
|
748
775
|
className: cn(
|
|
749
|
-
"
|
|
776
|
+
"fixed inset-y-0 right-0 z-50 flex w-full max-w-lg flex-col border-l bg-card shadow-xl",
|
|
750
777
|
className
|
|
751
778
|
),
|
|
752
779
|
children: [
|
|
753
780
|
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex shrink-0 items-start gap-3 border-b px-5 py-4", children: [
|
|
754
781
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
755
|
-
title && /* @__PURE__ */ jsxRuntime.jsx(
|
|
756
|
-
description && /* @__PURE__ */ jsxRuntime.jsx(
|
|
782
|
+
title && /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Title, { className: "text-base font-semibold", children: title }),
|
|
783
|
+
description && /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog.Description, { className: "mt-0.5 text-xs text-muted-foreground", children: description })
|
|
757
784
|
] }),
|
|
758
785
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
759
|
-
|
|
786
|
+
dialog.Dialog.Close,
|
|
760
787
|
{
|
|
761
|
-
type: "button",
|
|
762
788
|
"aria-label": "Fermer",
|
|
763
789
|
className: "-mr-1 shrink-0 rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
764
|
-
onClick: onClose,
|
|
765
790
|
children: /* @__PURE__ */ jsxRuntime.jsx(IconX, { className: "size-4" })
|
|
766
791
|
}
|
|
767
792
|
)
|
|
@@ -770,7 +795,7 @@ function SidePanel({
|
|
|
770
795
|
]
|
|
771
796
|
}
|
|
772
797
|
)
|
|
773
|
-
] });
|
|
798
|
+
] }) });
|
|
774
799
|
}
|
|
775
800
|
var COPY_FEEDBACK_MS = 2e3;
|
|
776
801
|
function CopyButton({
|
|
@@ -781,9 +806,9 @@ function CopyButton({
|
|
|
781
806
|
size = "sm",
|
|
782
807
|
className
|
|
783
808
|
}) {
|
|
784
|
-
const [state, setState] =
|
|
785
|
-
const timeout =
|
|
786
|
-
|
|
809
|
+
const [state, setState] = React2__namespace.useState("idle");
|
|
810
|
+
const timeout = React2__namespace.useRef(void 0);
|
|
811
|
+
React2__namespace.useEffect(() => () => clearTimeout(timeout.current), []);
|
|
787
812
|
async function copy() {
|
|
788
813
|
const ok = await copyToClipboard(value);
|
|
789
814
|
setState(ok ? "copied" : "error");
|
|
@@ -813,89 +838,1423 @@ function Popover({
|
|
|
813
838
|
align = "start",
|
|
814
839
|
className
|
|
815
840
|
}) {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
833
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
834
|
-
|
|
835
|
-
|
|
841
|
+
return (
|
|
842
|
+
// The callback is re-wrapped so Base UI's event-details argument stops at the
|
|
843
|
+
// SDK boundary: the engine is an implementation detail, not part of the API.
|
|
844
|
+
/* @__PURE__ */ jsxRuntime.jsxs(popover.Popover.Root, { open, onOpenChange: (next) => onOpenChange(next), children: [
|
|
845
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block", children: /* @__PURE__ */ jsxRuntime.jsx(popover.Popover.Trigger, { ...triggerProps(trigger) }) }),
|
|
846
|
+
/* @__PURE__ */ jsxRuntime.jsx(popover.Popover.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(popover.Popover.Positioner, { side: "bottom", align, sideOffset: 4, className: "z-50", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
847
|
+
popover.Popover.Popup,
|
|
848
|
+
{
|
|
849
|
+
className: cn("min-w-48 rounded-md border bg-card p-2 shadow-lg", className),
|
|
850
|
+
children
|
|
851
|
+
}
|
|
852
|
+
) }) })
|
|
853
|
+
] })
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
function DropdownMenu({ trigger, items, align = "end", className }) {
|
|
857
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(menu.Menu.Root, { children: [
|
|
858
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block", children: /* @__PURE__ */ jsxRuntime.jsx(menu.Menu.Trigger, { ...triggerProps(trigger) }) }),
|
|
859
|
+
/* @__PURE__ */ jsxRuntime.jsx(menu.Menu.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(menu.Menu.Positioner, { side: "bottom", align, sideOffset: 4, className: "z-50", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
860
|
+
menu.Menu.Popup,
|
|
836
861
|
{
|
|
837
862
|
className: cn(
|
|
838
|
-
"
|
|
839
|
-
align === "end" ? "right-0" : "left-0",
|
|
863
|
+
"flex min-w-48 flex-col rounded-md border bg-card p-1 shadow-lg",
|
|
840
864
|
className
|
|
841
865
|
),
|
|
842
|
-
children
|
|
866
|
+
children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
867
|
+
menu.Menu.Item,
|
|
868
|
+
{
|
|
869
|
+
disabled: item.disabled,
|
|
870
|
+
onClick: item.onSelect,
|
|
871
|
+
className: cn(
|
|
872
|
+
"cursor-pointer rounded px-2 py-1.5 text-left text-sm outline-none data-[highlighted]:bg-accent data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
873
|
+
item.destructive && "text-destructive"
|
|
874
|
+
),
|
|
875
|
+
children: item.label
|
|
876
|
+
},
|
|
877
|
+
i
|
|
878
|
+
))
|
|
843
879
|
}
|
|
844
|
-
)
|
|
880
|
+
) }) })
|
|
845
881
|
] });
|
|
846
882
|
}
|
|
847
|
-
|
|
848
|
-
|
|
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
|
+
};
|
|
849
1292
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
850
1293
|
Popover,
|
|
851
1294
|
{
|
|
852
1295
|
open,
|
|
853
1296
|
onOpenChange: setOpen,
|
|
854
|
-
trigger
|
|
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),
|
|
855
1631
|
align,
|
|
856
|
-
className: cn("p-
|
|
857
|
-
|
|
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(
|
|
858
1728
|
"button",
|
|
859
1729
|
{
|
|
1730
|
+
ref: (node) => {
|
|
1731
|
+
refs.current[index] = node;
|
|
1732
|
+
},
|
|
860
1733
|
type: "button",
|
|
861
|
-
role: "
|
|
862
|
-
|
|
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),
|
|
863
1739
|
className: cn(
|
|
864
|
-
"
|
|
865
|
-
|
|
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"
|
|
866
1742
|
),
|
|
867
|
-
|
|
868
|
-
setOpen(false);
|
|
869
|
-
item.onSelect();
|
|
870
|
-
},
|
|
871
|
-
children: item.label
|
|
1743
|
+
children: preset.label
|
|
872
1744
|
},
|
|
873
|
-
|
|
874
|
-
))
|
|
1745
|
+
preset.key
|
|
1746
|
+
))
|
|
875
1747
|
}
|
|
876
1748
|
);
|
|
877
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);
|
|
2164
|
+
function useToast() {
|
|
2165
|
+
const toast = React2.useContext(ToastContext);
|
|
2166
|
+
if (!toast) throw new Error("useToast must be used within ToastProvider");
|
|
2167
|
+
return toast;
|
|
2168
|
+
}
|
|
2169
|
+
var TOAST_TTL_MS = 4e3;
|
|
2170
|
+
var TONE = {
|
|
2171
|
+
success: "border-l-status-success",
|
|
2172
|
+
error: "border-l-status-blocked",
|
|
2173
|
+
info: "border-l-status-info"
|
|
2174
|
+
};
|
|
2175
|
+
function ToastProvider({ children }) {
|
|
2176
|
+
const [toasts, setToasts] = React2.useState([]);
|
|
2177
|
+
const nextId = React2.useRef(0);
|
|
2178
|
+
const toast = React2.useCallback((input) => {
|
|
2179
|
+
const id = nextId.current++;
|
|
2180
|
+
setToasts((prev) => [...prev, { id, ...input }]);
|
|
2181
|
+
setTimeout(() => setToasts((prev) => prev.filter((t) => t.id !== id)), TOAST_TTL_MS);
|
|
2182
|
+
}, []);
|
|
2183
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ToastContext.Provider, { value: toast, children: [
|
|
2184
|
+
children,
|
|
2185
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none fixed bottom-4 right-4 z-[60] flex w-80 flex-col gap-2", children: toasts.map((t) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2186
|
+
"div",
|
|
2187
|
+
{
|
|
2188
|
+
role: "status",
|
|
2189
|
+
className: cn(
|
|
2190
|
+
"pointer-events-auto rounded-lg border border-l-4 bg-card px-4 py-3 text-foreground shadow-md",
|
|
2191
|
+
TONE[t.variant ?? "success"]
|
|
2192
|
+
),
|
|
2193
|
+
children: [
|
|
2194
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: t.title }),
|
|
2195
|
+
t.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 break-all text-xs text-muted-foreground", children: t.description })
|
|
2196
|
+
]
|
|
2197
|
+
},
|
|
2198
|
+
t.id
|
|
2199
|
+
)) })
|
|
2200
|
+
] });
|
|
2201
|
+
}
|
|
2202
|
+
var SIDES = {
|
|
2203
|
+
top: "bottom-full left-1/2 -translate-x-1/2 mb-1.5",
|
|
2204
|
+
right: "left-full top-1/2 -translate-y-1/2 ml-1.5",
|
|
2205
|
+
bottom: "top-full left-1/2 -translate-x-1/2 mt-1.5",
|
|
2206
|
+
left: "right-full top-1/2 -translate-y-1/2 mr-1.5"
|
|
2207
|
+
};
|
|
2208
|
+
function Tooltip2({ label, children, side = "top", className }) {
|
|
2209
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "group/tt relative inline-flex", children: [
|
|
2210
|
+
children,
|
|
2211
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2212
|
+
"span",
|
|
2213
|
+
{
|
|
2214
|
+
role: "tooltip",
|
|
2215
|
+
className: cn(
|
|
2216
|
+
"pointer-events-none absolute z-50 whitespace-nowrap rounded-md bg-foreground px-2 py-1 text-xs font-medium text-background opacity-0 shadow-md transition-opacity group-hover/tt:opacity-100",
|
|
2217
|
+
SIDES[side],
|
|
2218
|
+
className
|
|
2219
|
+
),
|
|
2220
|
+
children: label
|
|
2221
|
+
}
|
|
2222
|
+
)
|
|
2223
|
+
] });
|
|
2224
|
+
}
|
|
878
2225
|
|
|
879
2226
|
exports.AppApiProvider = AppApiProvider;
|
|
880
2227
|
exports.AppContextProvider = AppContextProvider;
|
|
881
2228
|
exports.AppInvokeError = AppInvokeError;
|
|
2229
|
+
exports.AreaChart = AreaChart;
|
|
882
2230
|
exports.Badge = Badge;
|
|
2231
|
+
exports.BarChart = BarChart;
|
|
883
2232
|
exports.Button = Button;
|
|
2233
|
+
exports.COMPARE_LABELS = COMPARE_LABELS;
|
|
884
2234
|
exports.Card = Card;
|
|
885
2235
|
exports.CardContent = CardContent;
|
|
2236
|
+
exports.CardDescription = CardDescription;
|
|
2237
|
+
exports.CardFooter = CardFooter;
|
|
886
2238
|
exports.CardHeader = CardHeader;
|
|
887
2239
|
exports.CardTitle = CardTitle;
|
|
888
2240
|
exports.Checkbox = Checkbox;
|
|
889
2241
|
exports.CopyButton = CopyButton;
|
|
2242
|
+
exports.DataTable = DataTable;
|
|
2243
|
+
exports.DateRangeCompare = DateRangeCompare;
|
|
890
2244
|
exports.DateRangePicker = DateRangePicker;
|
|
891
2245
|
exports.DropdownMenu = DropdownMenu;
|
|
2246
|
+
exports.FunnelChart = FunnelChart;
|
|
892
2247
|
exports.Input = Input;
|
|
2248
|
+
exports.KpiCard = KpiCard;
|
|
2249
|
+
exports.LineChart = LineChart;
|
|
893
2250
|
exports.Link = Link;
|
|
894
2251
|
exports.Modal = Modal;
|
|
2252
|
+
exports.PERIOD_PRESETS = PERIOD_PRESETS;
|
|
895
2253
|
exports.Popover = Popover;
|
|
896
2254
|
exports.Select = Select;
|
|
897
2255
|
exports.SidePanel = SidePanel;
|
|
898
2256
|
exports.Spinner = Spinner;
|
|
2257
|
+
exports.StackedBarChart = StackedBarChart;
|
|
899
2258
|
exports.Switch = Switch;
|
|
900
2259
|
exports.Table = Table;
|
|
901
2260
|
exports.TableBody = TableBody;
|
|
@@ -908,15 +2267,26 @@ exports.TabsContent = TabsContent;
|
|
|
908
2267
|
exports.TabsList = TabsList;
|
|
909
2268
|
exports.TabsTrigger = TabsTrigger;
|
|
910
2269
|
exports.Textarea = Textarea;
|
|
2270
|
+
exports.ToastProvider = ToastProvider;
|
|
2271
|
+
exports.Tooltip = Tooltip2;
|
|
2272
|
+
exports.badgeVariants = badgeVariants;
|
|
2273
|
+
exports.buttonVariants = buttonVariants;
|
|
911
2274
|
exports.cn = cn;
|
|
2275
|
+
exports.comparisonRange = comparisonRange;
|
|
912
2276
|
exports.copyToClipboard = copyToClipboard;
|
|
913
2277
|
exports.downloadBlob = downloadBlob;
|
|
914
2278
|
exports.downloadCsv = downloadCsv;
|
|
2279
|
+
exports.formatPeriod = formatPeriod;
|
|
2280
|
+
exports.formatRange = formatRange;
|
|
2281
|
+
exports.makePeriod = makePeriod;
|
|
2282
|
+
exports.periodFromPreset = periodFromPreset;
|
|
2283
|
+
exports.relativeDelta = relativeDelta;
|
|
915
2284
|
exports.toCsv = toCsv;
|
|
916
2285
|
exports.useAppApi = useAppApi;
|
|
917
2286
|
exports.useAppContext = useAppContext;
|
|
918
2287
|
exports.useAppRun = useAppRun;
|
|
919
2288
|
exports.useAppRuns = useAppRuns;
|
|
920
2289
|
exports.useAppSettings = useAppSettings;
|
|
2290
|
+
exports.useToast = useToast;
|
|
921
2291
|
//# sourceMappingURL=index.cjs.map
|
|
922
2292
|
//# sourceMappingURL=index.cjs.map
|