@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.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { clsx } from 'clsx';
|
|
2
2
|
import { twMerge } from 'tailwind-merge';
|
|
3
|
-
import * as
|
|
3
|
+
import * as React2 from 'react';
|
|
4
4
|
import { createContext, useContext, useState, useRef, useCallback, useEffect } from 'react';
|
|
5
|
+
import { Dialog } from '@base-ui/react/dialog';
|
|
6
|
+
import { Menu } from '@base-ui/react/menu';
|
|
7
|
+
import { Checkbox as Checkbox$1 } from '@base-ui/react/checkbox';
|
|
8
|
+
import { Popover as Popover$1 } from '@base-ui/react/popover';
|
|
9
|
+
import { Select as Select$1 } from '@base-ui/react/select';
|
|
10
|
+
import { Switch as Switch$1 } from '@base-ui/react/switch';
|
|
11
|
+
import { Tabs as Tabs$1 } from '@base-ui/react/tabs';
|
|
5
12
|
import { cva } from 'class-variance-authority';
|
|
6
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
13
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
14
|
+
import { useReactTable, getFilteredRowModel, getSortedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
|
|
15
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
16
|
+
import { fr } from 'date-fns/locale/fr';
|
|
17
|
+
import { DayPicker } from 'react-day-picker';
|
|
18
|
+
import { subYears, subMonths, endOfMonth, startOfMonth, addQuarters, endOfQuarter, startOfQuarter, endOfYear, startOfYear, differenceInCalendarDays, subDays, parse, format, addDays } from 'date-fns';
|
|
19
|
+
import { BarChart as BarChart$1, Bar, LineChart as LineChart$1, Line, AreaChart as AreaChart$1, Area, FunnelChart as FunnelChart$1, Funnel, Cell, LabelList, ResponsiveContainer, Tooltip, Legend, CartesianGrid, XAxis, YAxis } from 'recharts';
|
|
7
20
|
|
|
8
21
|
// src/cn.ts
|
|
9
22
|
function cn(...inputs) {
|
|
@@ -289,6 +302,12 @@ function downloadCsv(rows, filename) {
|
|
|
289
302
|
const blob = new Blob([`${BOM}${toCsv(rows)}`], { type: "text/csv;charset=utf-8" });
|
|
290
303
|
downloadBlob(blob, filename);
|
|
291
304
|
}
|
|
305
|
+
function triggerProps(trigger) {
|
|
306
|
+
if (React2.isValidElement(trigger)) {
|
|
307
|
+
return { render: trigger, nativeButton: trigger.type === "button" || trigger.type === Button };
|
|
308
|
+
}
|
|
309
|
+
return { render: /* @__PURE__ */ jsx("span", { children: trigger }), nativeButton: false };
|
|
310
|
+
}
|
|
292
311
|
var buttonVariants = cva(
|
|
293
312
|
"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",
|
|
294
313
|
{
|
|
@@ -298,7 +317,7 @@ var buttonVariants = cva(
|
|
|
298
317
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
299
318
|
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
300
319
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
301
|
-
destructive: "bg-destructive text-
|
|
320
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
302
321
|
},
|
|
303
322
|
size: {
|
|
304
323
|
default: "h-9 px-4 py-2",
|
|
@@ -310,7 +329,7 @@ var buttonVariants = cva(
|
|
|
310
329
|
defaultVariants: { variant: "default", size: "default" }
|
|
311
330
|
}
|
|
312
331
|
);
|
|
313
|
-
var Button =
|
|
332
|
+
var Button = React2.forwardRef(
|
|
314
333
|
({ className, variant, size, ...props }, ref) => /* @__PURE__ */ jsx("button", { ref, className: cn(buttonVariants({ variant, size, className })), ...props })
|
|
315
334
|
);
|
|
316
335
|
Button.displayName = "Button";
|
|
@@ -352,10 +371,19 @@ function CardHeader({ className, ...props }) {
|
|
|
352
371
|
function CardTitle({ className, ...props }) {
|
|
353
372
|
return /* @__PURE__ */ jsx("h3", { className: cn("text-lg font-semibold leading-none tracking-tight", className), ...props });
|
|
354
373
|
}
|
|
374
|
+
function CardDescription({
|
|
375
|
+
className,
|
|
376
|
+
...props
|
|
377
|
+
}) {
|
|
378
|
+
return /* @__PURE__ */ jsx("p", { className: cn("text-sm text-muted-foreground", className), ...props });
|
|
379
|
+
}
|
|
355
380
|
function CardContent({ className, ...props }) {
|
|
356
381
|
return /* @__PURE__ */ jsx("div", { className: cn("p-6 pt-0", className), ...props });
|
|
357
382
|
}
|
|
358
|
-
|
|
383
|
+
function CardFooter({ className, ...props }) {
|
|
384
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex items-center p-6 pt-0", className), ...props });
|
|
385
|
+
}
|
|
386
|
+
var Input = React2.forwardRef(({ className, type, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
359
387
|
"input",
|
|
360
388
|
{
|
|
361
389
|
type,
|
|
@@ -375,7 +403,13 @@ function TableHeader({
|
|
|
375
403
|
className,
|
|
376
404
|
...props
|
|
377
405
|
}) {
|
|
378
|
-
return /* @__PURE__ */ jsx(
|
|
406
|
+
return /* @__PURE__ */ jsx(
|
|
407
|
+
"thead",
|
|
408
|
+
{
|
|
409
|
+
className: cn("[&_tr]:border-b [&_tr]:hover:bg-transparent", className),
|
|
410
|
+
...props
|
|
411
|
+
}
|
|
412
|
+
);
|
|
379
413
|
}
|
|
380
414
|
function TableBody({ className, ...props }) {
|
|
381
415
|
return /* @__PURE__ */ jsx("tbody", { className: cn("[&_tr:last-child]:border-0", className), ...props });
|
|
@@ -397,7 +431,7 @@ function TableHead({ className, ...props }) {
|
|
|
397
431
|
"th",
|
|
398
432
|
{
|
|
399
433
|
className: cn(
|
|
400
|
-
"h-10 px-4 text-left align-middle text-xs font-medium text-muted-foreground",
|
|
434
|
+
"h-10 px-4 text-left align-middle text-xs font-medium uppercase tracking-wide text-muted-foreground",
|
|
401
435
|
className
|
|
402
436
|
),
|
|
403
437
|
...props
|
|
@@ -407,57 +441,92 @@ function TableHead({ className, ...props }) {
|
|
|
407
441
|
function TableCell({ className, ...props }) {
|
|
408
442
|
return /* @__PURE__ */ jsx("td", { className: cn("p-4 align-middle", className), ...props });
|
|
409
443
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
444
|
+
function Select({
|
|
445
|
+
value,
|
|
446
|
+
defaultValue,
|
|
447
|
+
onValueChange,
|
|
448
|
+
items,
|
|
449
|
+
placeholder = "Choisir\u2026",
|
|
450
|
+
disabled,
|
|
451
|
+
name,
|
|
452
|
+
className,
|
|
453
|
+
...props
|
|
454
|
+
}) {
|
|
455
|
+
return /* @__PURE__ */ jsxs(
|
|
456
|
+
Select$1.Root,
|
|
413
457
|
{
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
children
|
|
458
|
+
items,
|
|
459
|
+
value,
|
|
460
|
+
defaultValue,
|
|
461
|
+
onValueChange: (next) => onValueChange?.(String(next)),
|
|
462
|
+
disabled,
|
|
463
|
+
name,
|
|
464
|
+
children: [
|
|
465
|
+
/* @__PURE__ */ jsxs(
|
|
466
|
+
Select$1.Trigger,
|
|
467
|
+
{
|
|
468
|
+
className: cn(
|
|
469
|
+
"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",
|
|
470
|
+
className
|
|
471
|
+
),
|
|
472
|
+
...props,
|
|
473
|
+
children: [
|
|
474
|
+
/* @__PURE__ */ jsx(Select$1.Value, { placeholder }),
|
|
475
|
+
/* @__PURE__ */ jsx(Select$1.Icon, { "aria-hidden": true, className: "text-xs text-muted-foreground", children: "\u25BE" })
|
|
476
|
+
]
|
|
477
|
+
}
|
|
478
|
+
),
|
|
479
|
+
/* @__PURE__ */ jsx(Select$1.Portal, { children: /* @__PURE__ */ jsx(Select$1.Positioner, { className: "z-50", sideOffset: 4, children: /* @__PURE__ */ jsx(Select$1.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__ */ jsx(
|
|
480
|
+
Select$1.Item,
|
|
481
|
+
{
|
|
482
|
+
value: item.value,
|
|
483
|
+
disabled: item.disabled,
|
|
484
|
+
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",
|
|
485
|
+
children: /* @__PURE__ */ jsx(Select$1.ItemText, { children: item.label })
|
|
486
|
+
},
|
|
487
|
+
item.value
|
|
488
|
+
)) }) }) })
|
|
489
|
+
]
|
|
421
490
|
}
|
|
422
|
-
)
|
|
423
|
-
);
|
|
424
|
-
Select.displayName = "Select";
|
|
425
|
-
var TabsContext = React.createContext(null);
|
|
426
|
-
function useTabsContext() {
|
|
427
|
-
const ctx = React.useContext(TabsContext);
|
|
428
|
-
if (!ctx) throw new Error("Tabs sub-component must be used within <Tabs>");
|
|
429
|
-
return ctx;
|
|
491
|
+
);
|
|
430
492
|
}
|
|
431
493
|
function Tabs({ defaultValue, value, onValueChange, className, children }) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
494
|
+
return /* @__PURE__ */ jsx(
|
|
495
|
+
Tabs$1.Root,
|
|
496
|
+
{
|
|
497
|
+
className,
|
|
498
|
+
defaultValue,
|
|
499
|
+
value,
|
|
500
|
+
onValueChange: (next) => onValueChange?.(String(next)),
|
|
501
|
+
children
|
|
502
|
+
}
|
|
503
|
+
);
|
|
439
504
|
}
|
|
440
505
|
function TabsList({
|
|
441
506
|
className,
|
|
442
507
|
children
|
|
443
508
|
}) {
|
|
444
|
-
return /* @__PURE__ */ jsx(
|
|
509
|
+
return /* @__PURE__ */ jsx(
|
|
510
|
+
Tabs$1.List,
|
|
511
|
+
{
|
|
512
|
+
className: cn("inline-flex items-center gap-1 rounded-lg bg-muted p-1", className),
|
|
513
|
+
children
|
|
514
|
+
}
|
|
515
|
+
);
|
|
445
516
|
}
|
|
446
517
|
function TabsTrigger({
|
|
447
518
|
value,
|
|
448
519
|
className,
|
|
449
520
|
children
|
|
450
521
|
}) {
|
|
451
|
-
const { value: current, setValue } = useTabsContext();
|
|
452
|
-
const active = current === value;
|
|
453
522
|
return /* @__PURE__ */ jsx(
|
|
454
|
-
|
|
523
|
+
Tabs$1.Tab,
|
|
455
524
|
{
|
|
456
|
-
|
|
457
|
-
onClick: () => setValue(value),
|
|
525
|
+
value,
|
|
458
526
|
className: cn(
|
|
459
527
|
"rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
|
|
460
|
-
|
|
528
|
+
"text-muted-foreground hover:text-foreground",
|
|
529
|
+
"data-[active]:bg-card data-[active]:text-foreground data-[active]:shadow-sm",
|
|
461
530
|
className
|
|
462
531
|
),
|
|
463
532
|
children
|
|
@@ -469,38 +538,25 @@ function TabsContent({
|
|
|
469
538
|
className,
|
|
470
539
|
children
|
|
471
540
|
}) {
|
|
472
|
-
|
|
473
|
-
if (current !== value) return null;
|
|
474
|
-
return /* @__PURE__ */ jsx("div", { className, children });
|
|
541
|
+
return /* @__PURE__ */ jsx(Tabs$1.Panel, { value, className, children });
|
|
475
542
|
}
|
|
476
543
|
function Modal({ open, onClose, title, children, className }) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
/* @__PURE__ */ jsx(
|
|
480
|
-
"button",
|
|
481
|
-
{
|
|
482
|
-
type: "button",
|
|
483
|
-
"aria-label": "Close",
|
|
484
|
-
className: "absolute inset-0 cursor-default bg-foreground/40",
|
|
485
|
-
onClick: onClose
|
|
486
|
-
}
|
|
487
|
-
),
|
|
544
|
+
return /* @__PURE__ */ jsx(Dialog.Root, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
|
|
545
|
+
/* @__PURE__ */ jsx(Dialog.Backdrop, { "aria-label": "Close", className: "fixed inset-0 z-50 bg-overlay" }),
|
|
488
546
|
/* @__PURE__ */ jsxs(
|
|
489
|
-
|
|
547
|
+
Dialog.Popup,
|
|
490
548
|
{
|
|
491
|
-
role: "dialog",
|
|
492
|
-
"aria-modal": true,
|
|
493
549
|
className: cn(
|
|
494
|
-
"
|
|
550
|
+
"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",
|
|
495
551
|
className
|
|
496
552
|
),
|
|
497
553
|
children: [
|
|
498
|
-
title && /* @__PURE__ */ jsx(
|
|
554
|
+
title && /* @__PURE__ */ jsx(Dialog.Title, { className: "mb-3 text-base font-semibold", children: title }),
|
|
499
555
|
children
|
|
500
556
|
]
|
|
501
557
|
}
|
|
502
558
|
)
|
|
503
|
-
] });
|
|
559
|
+
] }) });
|
|
504
560
|
}
|
|
505
561
|
var spinnerSizes = { sm: "size-4", md: "size-6", lg: "size-8" };
|
|
506
562
|
function Spinner({ className, size = "md" }) {
|
|
@@ -527,7 +583,7 @@ function Spinner({ className, size = "md" }) {
|
|
|
527
583
|
}
|
|
528
584
|
);
|
|
529
585
|
}
|
|
530
|
-
var Link =
|
|
586
|
+
var Link = React2.forwardRef(
|
|
531
587
|
({ className, variant = "default", ...props }, ref) => /* @__PURE__ */ jsx(
|
|
532
588
|
"a",
|
|
533
589
|
{
|
|
@@ -550,58 +606,22 @@ function Switch({
|
|
|
550
606
|
...props
|
|
551
607
|
}) {
|
|
552
608
|
return /* @__PURE__ */ jsx(
|
|
553
|
-
|
|
609
|
+
Switch$1.Root,
|
|
554
610
|
{
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
"aria-checked": checked,
|
|
611
|
+
checked,
|
|
612
|
+
onCheckedChange: (next) => onCheckedChange?.(next),
|
|
558
613
|
disabled,
|
|
559
|
-
onClick: () => onCheckedChange?.(!checked),
|
|
560
614
|
className: cn(
|
|
561
|
-
|
|
562
|
-
|
|
615
|
+
// `data-[disabled]`, not `disabled:` — the root is a span, so `:disabled`
|
|
616
|
+
// never matches it and the dimming would silently stop happening.
|
|
617
|
+
"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",
|
|
563
618
|
className
|
|
564
619
|
),
|
|
565
620
|
...props,
|
|
566
|
-
children: /* @__PURE__ */ jsx(
|
|
567
|
-
"span",
|
|
568
|
-
{
|
|
569
|
-
className: cn(
|
|
570
|
-
"inline-block size-4 transform rounded-full bg-white shadow transition-transform",
|
|
571
|
-
checked ? "translate-x-4" : "translate-x-0.5"
|
|
572
|
-
)
|
|
573
|
-
}
|
|
574
|
-
)
|
|
621
|
+
children: /* @__PURE__ */ jsx(Switch$1.Thumb, { className: "inline-block size-4 translate-x-0.5 rounded-full bg-white shadow transition-transform data-[checked]:translate-x-4" })
|
|
575
622
|
}
|
|
576
623
|
);
|
|
577
624
|
}
|
|
578
|
-
function DateRangePicker({ value, onChange, className, disabled }) {
|
|
579
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", className), children: [
|
|
580
|
-
/* @__PURE__ */ jsx(
|
|
581
|
-
"input",
|
|
582
|
-
{
|
|
583
|
-
type: "date",
|
|
584
|
-
disabled,
|
|
585
|
-
value: value.from,
|
|
586
|
-
max: value.to || void 0,
|
|
587
|
-
onChange: (e) => onChange({ ...value, from: e.target.value }),
|
|
588
|
-
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"
|
|
589
|
-
}
|
|
590
|
-
),
|
|
591
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "\u2192" }),
|
|
592
|
-
/* @__PURE__ */ jsx(
|
|
593
|
-
"input",
|
|
594
|
-
{
|
|
595
|
-
type: "date",
|
|
596
|
-
disabled,
|
|
597
|
-
value: value.to,
|
|
598
|
-
min: value.from || void 0,
|
|
599
|
-
onChange: (e) => onChange({ ...value, to: e.target.value }),
|
|
600
|
-
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"
|
|
601
|
-
}
|
|
602
|
-
)
|
|
603
|
-
] });
|
|
604
|
-
}
|
|
605
625
|
function IconX({ className }) {
|
|
606
626
|
return /* @__PURE__ */ jsx(
|
|
607
627
|
"svg",
|
|
@@ -655,43 +675,69 @@ function IconCheck({ className }) {
|
|
|
655
675
|
}
|
|
656
676
|
);
|
|
657
677
|
}
|
|
658
|
-
var Textarea =
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
678
|
+
var Textarea = React2.forwardRef(function Textarea2({ className, ...props }, ref) {
|
|
679
|
+
return /* @__PURE__ */ jsx(
|
|
680
|
+
"textarea",
|
|
681
|
+
{
|
|
682
|
+
ref,
|
|
683
|
+
className: cn(
|
|
684
|
+
"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",
|
|
685
|
+
className
|
|
686
|
+
),
|
|
687
|
+
...props
|
|
688
|
+
}
|
|
689
|
+
);
|
|
690
|
+
});
|
|
691
|
+
function Checkbox({
|
|
692
|
+
checked,
|
|
693
|
+
defaultChecked,
|
|
694
|
+
onCheckedChange,
|
|
695
|
+
disabled,
|
|
696
|
+
label,
|
|
697
|
+
name,
|
|
698
|
+
className,
|
|
699
|
+
...props
|
|
700
|
+
}) {
|
|
701
|
+
const labelId = React2.useId();
|
|
702
|
+
const control = React2.useRef(null);
|
|
703
|
+
const box = /* @__PURE__ */ jsx(
|
|
704
|
+
Checkbox$1.Root,
|
|
705
|
+
{
|
|
706
|
+
ref: control,
|
|
707
|
+
"aria-labelledby": label ? labelId : void 0,
|
|
708
|
+
checked,
|
|
709
|
+
defaultChecked,
|
|
710
|
+
onCheckedChange: (next) => onCheckedChange?.(next),
|
|
711
|
+
disabled,
|
|
712
|
+
name,
|
|
713
|
+
className: cn(
|
|
714
|
+
"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",
|
|
715
|
+
className
|
|
716
|
+
),
|
|
717
|
+
...props,
|
|
718
|
+
children: /* @__PURE__ */ jsx(Checkbox$1.Indicator, { className: "text-[10px] leading-none", children: "\u2713" })
|
|
719
|
+
}
|
|
720
|
+
);
|
|
721
|
+
if (!label) return box;
|
|
722
|
+
return (
|
|
723
|
+
// Not a wrapping <label>: Base UI would associate it AND keep its own label,
|
|
724
|
+
// so the text and the control would both answer to the same name and
|
|
725
|
+
// `getByLabelText` would go ambiguous. Pointing `aria-labelledby` at the text
|
|
726
|
+
// names the control once; the click handler keeps the label clickable.
|
|
727
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 text-sm", children: [
|
|
728
|
+
box,
|
|
729
|
+
/* @__PURE__ */ jsx(
|
|
730
|
+
"span",
|
|
731
|
+
{
|
|
732
|
+
id: labelId,
|
|
733
|
+
className: "cursor-pointer",
|
|
734
|
+
onClick: () => control.current?.click(),
|
|
735
|
+
children: label
|
|
736
|
+
}
|
|
737
|
+
)
|
|
738
|
+
] })
|
|
739
|
+
);
|
|
740
|
+
}
|
|
695
741
|
function SidePanel({
|
|
696
742
|
open,
|
|
697
743
|
onClose,
|
|
@@ -700,47 +746,26 @@ function SidePanel({
|
|
|
700
746
|
children,
|
|
701
747
|
className
|
|
702
748
|
}) {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const onKeyDown = (e) => {
|
|
706
|
-
if (e.key === "Escape") onClose();
|
|
707
|
-
};
|
|
708
|
-
window.addEventListener("keydown", onKeyDown);
|
|
709
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
710
|
-
}, [open, onClose]);
|
|
711
|
-
if (!open) return null;
|
|
712
|
-
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50", children: [
|
|
713
|
-
/* @__PURE__ */ jsx(
|
|
714
|
-
"button",
|
|
715
|
-
{
|
|
716
|
-
type: "button",
|
|
717
|
-
"aria-label": "Fermer",
|
|
718
|
-
className: "absolute inset-0 cursor-default bg-foreground/40",
|
|
719
|
-
onClick: onClose
|
|
720
|
-
}
|
|
721
|
-
),
|
|
749
|
+
return /* @__PURE__ */ jsx(Dialog.Root, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
|
|
750
|
+
/* @__PURE__ */ jsx(Dialog.Backdrop, { "aria-label": "Fermer", className: "fixed inset-0 z-50 bg-overlay" }),
|
|
722
751
|
/* @__PURE__ */ jsxs(
|
|
723
|
-
|
|
752
|
+
Dialog.Popup,
|
|
724
753
|
{
|
|
725
|
-
role: "dialog",
|
|
726
|
-
"aria-modal": true,
|
|
727
754
|
className: cn(
|
|
728
|
-
"
|
|
755
|
+
"fixed inset-y-0 right-0 z-50 flex w-full max-w-lg flex-col border-l bg-card shadow-xl",
|
|
729
756
|
className
|
|
730
757
|
),
|
|
731
758
|
children: [
|
|
732
759
|
/* @__PURE__ */ jsxs("header", { className: "flex shrink-0 items-start gap-3 border-b px-5 py-4", children: [
|
|
733
760
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
734
|
-
title && /* @__PURE__ */ jsx(
|
|
735
|
-
description && /* @__PURE__ */ jsx(
|
|
761
|
+
title && /* @__PURE__ */ jsx(Dialog.Title, { className: "text-base font-semibold", children: title }),
|
|
762
|
+
description && /* @__PURE__ */ jsx(Dialog.Description, { className: "mt-0.5 text-xs text-muted-foreground", children: description })
|
|
736
763
|
] }),
|
|
737
764
|
/* @__PURE__ */ jsx(
|
|
738
|
-
|
|
765
|
+
Dialog.Close,
|
|
739
766
|
{
|
|
740
|
-
type: "button",
|
|
741
767
|
"aria-label": "Fermer",
|
|
742
768
|
className: "-mr-1 shrink-0 rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
743
|
-
onClick: onClose,
|
|
744
769
|
children: /* @__PURE__ */ jsx(IconX, { className: "size-4" })
|
|
745
770
|
}
|
|
746
771
|
)
|
|
@@ -749,7 +774,7 @@ function SidePanel({
|
|
|
749
774
|
]
|
|
750
775
|
}
|
|
751
776
|
)
|
|
752
|
-
] });
|
|
777
|
+
] }) });
|
|
753
778
|
}
|
|
754
779
|
var COPY_FEEDBACK_MS = 2e3;
|
|
755
780
|
function CopyButton({
|
|
@@ -760,9 +785,9 @@ function CopyButton({
|
|
|
760
785
|
size = "sm",
|
|
761
786
|
className
|
|
762
787
|
}) {
|
|
763
|
-
const [state, setState] =
|
|
764
|
-
const timeout =
|
|
765
|
-
|
|
788
|
+
const [state, setState] = React2.useState("idle");
|
|
789
|
+
const timeout = React2.useRef(void 0);
|
|
790
|
+
React2.useEffect(() => () => clearTimeout(timeout.current), []);
|
|
766
791
|
async function copy() {
|
|
767
792
|
const ok = await copyToClipboard(value);
|
|
768
793
|
setState(ok ? "copied" : "error");
|
|
@@ -792,69 +817,1391 @@ function Popover({
|
|
|
792
817
|
align = "start",
|
|
793
818
|
className
|
|
794
819
|
}) {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
return /* @__PURE__ */ jsxs(
|
|
812
|
-
/* @__PURE__ */ jsx("
|
|
813
|
-
|
|
814
|
-
|
|
820
|
+
return (
|
|
821
|
+
// The callback is re-wrapped so Base UI's event-details argument stops at the
|
|
822
|
+
// SDK boundary: the engine is an implementation detail, not part of the API.
|
|
823
|
+
/* @__PURE__ */ jsxs(Popover$1.Root, { open, onOpenChange: (next) => onOpenChange(next), children: [
|
|
824
|
+
/* @__PURE__ */ jsx("span", { className: "inline-block", children: /* @__PURE__ */ jsx(Popover$1.Trigger, { ...triggerProps(trigger) }) }),
|
|
825
|
+
/* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(Popover$1.Positioner, { side: "bottom", align, sideOffset: 4, className: "z-50", children: /* @__PURE__ */ jsx(
|
|
826
|
+
Popover$1.Popup,
|
|
827
|
+
{
|
|
828
|
+
className: cn("min-w-48 rounded-md border bg-card p-2 shadow-lg", className),
|
|
829
|
+
children
|
|
830
|
+
}
|
|
831
|
+
) }) })
|
|
832
|
+
] })
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
function DropdownMenu({ trigger, items, align = "end", className }) {
|
|
836
|
+
return /* @__PURE__ */ jsxs(Menu.Root, { children: [
|
|
837
|
+
/* @__PURE__ */ jsx("span", { className: "inline-block", children: /* @__PURE__ */ jsx(Menu.Trigger, { ...triggerProps(trigger) }) }),
|
|
838
|
+
/* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, { side: "bottom", align, sideOffset: 4, className: "z-50", children: /* @__PURE__ */ jsx(
|
|
839
|
+
Menu.Popup,
|
|
815
840
|
{
|
|
816
841
|
className: cn(
|
|
817
|
-
"
|
|
818
|
-
align === "end" ? "right-0" : "left-0",
|
|
842
|
+
"flex min-w-48 flex-col rounded-md border bg-card p-1 shadow-lg",
|
|
819
843
|
className
|
|
820
844
|
),
|
|
821
|
-
children
|
|
845
|
+
children: items.map((item, i) => /* @__PURE__ */ jsx(
|
|
846
|
+
Menu.Item,
|
|
847
|
+
{
|
|
848
|
+
disabled: item.disabled,
|
|
849
|
+
onClick: item.onSelect,
|
|
850
|
+
className: cn(
|
|
851
|
+
"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",
|
|
852
|
+
item.destructive && "text-destructive"
|
|
853
|
+
),
|
|
854
|
+
children: item.label
|
|
855
|
+
},
|
|
856
|
+
i
|
|
857
|
+
))
|
|
822
858
|
}
|
|
823
|
-
)
|
|
859
|
+
) }) })
|
|
824
860
|
] });
|
|
825
861
|
}
|
|
826
|
-
|
|
827
|
-
|
|
862
|
+
var EMPTY_STATE = {
|
|
863
|
+
search: "",
|
|
864
|
+
sort: null,
|
|
865
|
+
filters: {},
|
|
866
|
+
hidden: [],
|
|
867
|
+
selected: []
|
|
868
|
+
};
|
|
869
|
+
var MIN_COLUMN_WIDTH_PX = 64;
|
|
870
|
+
var DEFAULT_VIRTUALIZE_FROM = 100;
|
|
871
|
+
var ROW_HEIGHT_PX = 53;
|
|
872
|
+
var VIRTUAL_VIEWPORT_PX = 640;
|
|
873
|
+
function useDataTableState(controlled, initial, onStateChange) {
|
|
874
|
+
const [internal, setInternal] = React2.useState({ ...EMPTY_STATE, ...initial });
|
|
875
|
+
const state = React2.useMemo(
|
|
876
|
+
() => ({ ...EMPTY_STATE, ...initial, ...internal, ...controlled }),
|
|
877
|
+
[controlled, initial, internal]
|
|
878
|
+
);
|
|
879
|
+
const patch = (next) => {
|
|
880
|
+
const merged = { ...state, ...next };
|
|
881
|
+
setInternal(merged);
|
|
882
|
+
onStateChange?.(merged);
|
|
883
|
+
};
|
|
884
|
+
return [state, patch];
|
|
885
|
+
}
|
|
886
|
+
function DataTable({
|
|
887
|
+
columns,
|
|
888
|
+
rows,
|
|
889
|
+
rowId,
|
|
890
|
+
state: controlled,
|
|
891
|
+
defaultState,
|
|
892
|
+
onStateChange,
|
|
893
|
+
facets = [],
|
|
894
|
+
searchPlaceholder = "Rechercher\u2026",
|
|
895
|
+
selectable = false,
|
|
896
|
+
onRowClick,
|
|
897
|
+
pagination,
|
|
898
|
+
manual = false,
|
|
899
|
+
loading = false,
|
|
900
|
+
error,
|
|
901
|
+
empty = "Aucun r\xE9sultat.",
|
|
902
|
+
virtualizeFrom = DEFAULT_VIRTUALIZE_FROM,
|
|
903
|
+
className
|
|
904
|
+
}) {
|
|
905
|
+
const [state, patch] = useDataTableState(controlled, defaultState, onStateChange);
|
|
906
|
+
const [widths, setWidths] = React2.useState({});
|
|
907
|
+
const hidden = React2.useMemo(() => new Set(state.hidden), [state.hidden]);
|
|
908
|
+
const selected = React2.useMemo(() => new Set(state.selected), [state.selected]);
|
|
909
|
+
const visibleColumns = React2.useMemo(
|
|
910
|
+
() => columns.filter((column) => !hidden.has(column.key)),
|
|
911
|
+
[columns, hidden]
|
|
912
|
+
);
|
|
913
|
+
const columnDefs = React2.useMemo(
|
|
914
|
+
() => visibleColumns.map((column) => ({
|
|
915
|
+
id: column.key,
|
|
916
|
+
accessorFn: (row) => column.value ? column.value(row) : row[column.key],
|
|
917
|
+
enableSorting: column.sortable ?? false,
|
|
918
|
+
size: column.width,
|
|
919
|
+
cell: ({ row }) => column.cell ? column.cell(row.original) : String(row.original[column.key] ?? "")
|
|
920
|
+
})),
|
|
921
|
+
[visibleColumns]
|
|
922
|
+
);
|
|
923
|
+
const sorting = React2.useMemo(
|
|
924
|
+
() => state.sort ? [{ id: state.sort.key, desc: state.sort.direction === "desc" }] : [],
|
|
925
|
+
[state.sort]
|
|
926
|
+
);
|
|
927
|
+
const table = useReactTable({
|
|
928
|
+
data: rows,
|
|
929
|
+
columns: columnDefs,
|
|
930
|
+
state: { sorting, globalFilter: manual ? "" : state.search },
|
|
931
|
+
getRowId: (row) => rowId(row),
|
|
932
|
+
getCoreRowModel: getCoreRowModel(),
|
|
933
|
+
...manual ? { manualSorting: true, manualFiltering: true } : { getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel() }
|
|
934
|
+
});
|
|
935
|
+
const facetedRows = React2.useMemo(() => {
|
|
936
|
+
const active = Object.entries(state.filters).filter(([, filter]) => isApplied(filter));
|
|
937
|
+
if (manual || active.length === 0) return table.getRowModel().rows;
|
|
938
|
+
return table.getRowModel().rows.filter(
|
|
939
|
+
(row) => active.every(([key, filter]) => {
|
|
940
|
+
const column = columns.find((c) => c.key === key);
|
|
941
|
+
const raw = column?.value ? column.value(row.original) : row.original[key];
|
|
942
|
+
return matches(raw, filter);
|
|
943
|
+
})
|
|
944
|
+
);
|
|
945
|
+
}, [columns, manual, state.filters, table.getRowModel().rows]);
|
|
946
|
+
const bodyRef = React2.useRef(null);
|
|
947
|
+
const longEnoughToVirtualise = facetedRows.length > virtualizeFrom;
|
|
948
|
+
const virtualizer = useVirtualizer({
|
|
949
|
+
count: facetedRows.length,
|
|
950
|
+
getScrollElement: () => bodyRef.current,
|
|
951
|
+
estimateSize: () => ROW_HEIGHT_PX,
|
|
952
|
+
overscan: 8,
|
|
953
|
+
enabled: longEnoughToVirtualise
|
|
954
|
+
});
|
|
955
|
+
const virtualItems = virtualizer.getVirtualItems();
|
|
956
|
+
const virtualise = longEnoughToVirtualise && virtualItems.length > 0;
|
|
957
|
+
const toggleSort = (key) => {
|
|
958
|
+
const next = state.sort?.key !== key ? { key, direction: "asc" } : state.sort.direction === "asc" ? { key, direction: "desc" } : null;
|
|
959
|
+
patch({ sort: next });
|
|
960
|
+
};
|
|
961
|
+
const toggleSelected = (id) => {
|
|
962
|
+
const next = new Set(selected);
|
|
963
|
+
if (next.has(id)) next.delete(id);
|
|
964
|
+
else next.add(id);
|
|
965
|
+
patch({ selected: [...next] });
|
|
966
|
+
};
|
|
967
|
+
const allVisibleSelected = facetedRows.length > 0 && facetedRows.every((row) => selected.has(row.id));
|
|
968
|
+
const toggleAll = () => {
|
|
969
|
+
patch({ selected: allVisibleSelected ? [] : facetedRows.map((row) => row.id) });
|
|
970
|
+
};
|
|
971
|
+
const setFilter = (facetKey, filter) => {
|
|
972
|
+
const next = { ...state.filters };
|
|
973
|
+
if (filter) next[facetKey] = filter;
|
|
974
|
+
else delete next[facetKey];
|
|
975
|
+
patch({ filters: next });
|
|
976
|
+
};
|
|
977
|
+
const renderedRows = virtualise ? virtualItems.map((item) => facetedRows[item.index]) : facetedRows;
|
|
978
|
+
const spaceAbove = virtualise ? virtualItems[0]?.start ?? 0 : 0;
|
|
979
|
+
const spaceBelow = virtualise ? virtualizer.getTotalSize() - (virtualItems[virtualItems.length - 1]?.end ?? 0) : 0;
|
|
980
|
+
const columnCount = visibleColumns.length + (selectable ? 1 : 0);
|
|
981
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3", className), children: [
|
|
982
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
983
|
+
/* @__PURE__ */ jsx(
|
|
984
|
+
Input,
|
|
985
|
+
{
|
|
986
|
+
value: state.search,
|
|
987
|
+
onChange: (event) => patch({ search: event.target.value }),
|
|
988
|
+
placeholder: searchPlaceholder,
|
|
989
|
+
"aria-label": searchPlaceholder,
|
|
990
|
+
className: "h-9 max-w-xs"
|
|
991
|
+
}
|
|
992
|
+
),
|
|
993
|
+
facets.map((facet) => /* @__PURE__ */ jsx(
|
|
994
|
+
FacetFilter,
|
|
995
|
+
{
|
|
996
|
+
facet,
|
|
997
|
+
filter: state.filters[facet.key],
|
|
998
|
+
onChange: (filter) => setFilter(facet.key, filter)
|
|
999
|
+
},
|
|
1000
|
+
facet.key
|
|
1001
|
+
)),
|
|
1002
|
+
/* @__PURE__ */ jsx(
|
|
1003
|
+
ColumnPicker,
|
|
1004
|
+
{
|
|
1005
|
+
columns,
|
|
1006
|
+
hidden,
|
|
1007
|
+
onToggle: (key) => patch({
|
|
1008
|
+
hidden: hidden.has(key) ? state.hidden.filter((k) => k !== key) : [...state.hidden, key]
|
|
1009
|
+
})
|
|
1010
|
+
}
|
|
1011
|
+
),
|
|
1012
|
+
selectable && selected.size > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", role: "status", children: [
|
|
1013
|
+
selected.size,
|
|
1014
|
+
" s\xE9lectionn\xE9",
|
|
1015
|
+
selected.size > 1 ? "s" : ""
|
|
1016
|
+
] })
|
|
1017
|
+
] }),
|
|
1018
|
+
/* @__PURE__ */ jsx(
|
|
1019
|
+
"div",
|
|
1020
|
+
{
|
|
1021
|
+
ref: bodyRef,
|
|
1022
|
+
className: cn("overflow-auto rounded-xl border bg-card", virtualise && "max-h-160"),
|
|
1023
|
+
style: virtualise ? { maxHeight: VIRTUAL_VIEWPORT_PX } : void 0,
|
|
1024
|
+
children: /* @__PURE__ */ jsxs(Table, { children: [
|
|
1025
|
+
/* @__PURE__ */ jsx(TableHeader, { className: virtualise ? "sticky top-0 z-10 bg-card" : void 0, children: /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
1026
|
+
selectable && /* @__PURE__ */ jsx(TableHead, { className: "w-10", children: /* @__PURE__ */ jsx(
|
|
1027
|
+
Checkbox,
|
|
1028
|
+
{
|
|
1029
|
+
checked: allVisibleSelected,
|
|
1030
|
+
onCheckedChange: toggleAll,
|
|
1031
|
+
"aria-label": "Tout s\xE9lectionner"
|
|
1032
|
+
}
|
|
1033
|
+
) }),
|
|
1034
|
+
visibleColumns.map((column) => {
|
|
1035
|
+
const label = typeof column.header === "string" ? column.header : column.key;
|
|
1036
|
+
return /* @__PURE__ */ jsxs(
|
|
1037
|
+
TableHead,
|
|
1038
|
+
{
|
|
1039
|
+
style: { width: widths[column.key] ?? column.width },
|
|
1040
|
+
className: cn("relative", column.align === "right" && "text-right"),
|
|
1041
|
+
"aria-sort": !column.sortable ? void 0 : state.sort?.key === column.key ? state.sort.direction === "asc" ? "ascending" : "descending" : "none",
|
|
1042
|
+
children: [
|
|
1043
|
+
column.sortable ? /* @__PURE__ */ jsxs(
|
|
1044
|
+
"button",
|
|
1045
|
+
{
|
|
1046
|
+
type: "button",
|
|
1047
|
+
onClick: () => toggleSort(column.key),
|
|
1048
|
+
className: "inline-flex cursor-pointer items-center gap-1 hover:text-foreground",
|
|
1049
|
+
children: [
|
|
1050
|
+
column.header,
|
|
1051
|
+
/* @__PURE__ */ jsx(
|
|
1052
|
+
SortGlyph,
|
|
1053
|
+
{
|
|
1054
|
+
direction: state.sort?.key === column.key ? state.sort.direction : null
|
|
1055
|
+
}
|
|
1056
|
+
)
|
|
1057
|
+
]
|
|
1058
|
+
}
|
|
1059
|
+
) : column.header,
|
|
1060
|
+
/* @__PURE__ */ jsx(
|
|
1061
|
+
ResizeHandle,
|
|
1062
|
+
{
|
|
1063
|
+
label,
|
|
1064
|
+
onResize: (delta, from) => setWidths((current) => ({
|
|
1065
|
+
...current,
|
|
1066
|
+
[column.key]: Math.max(MIN_COLUMN_WIDTH_PX, from + delta)
|
|
1067
|
+
}))
|
|
1068
|
+
}
|
|
1069
|
+
)
|
|
1070
|
+
]
|
|
1071
|
+
},
|
|
1072
|
+
column.key
|
|
1073
|
+
);
|
|
1074
|
+
})
|
|
1075
|
+
] }) }),
|
|
1076
|
+
/* @__PURE__ */ jsx(TableBody, { children: error ? /* @__PURE__ */ jsx(StatusRow, { colSpan: columnCount, tone: "error", children: error }) : loading ? /* @__PURE__ */ jsx(SkeletonRows, { columns: columnCount }) : facetedRows.length === 0 ? /* @__PURE__ */ jsx(StatusRow, { colSpan: columnCount, children: empty }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1077
|
+
spaceAbove > 0 && /* @__PURE__ */ jsx("tr", { "aria-hidden": true, style: { height: spaceAbove } }),
|
|
1078
|
+
renderedRows.map((row) => /* @__PURE__ */ jsxs(
|
|
1079
|
+
TableRow,
|
|
1080
|
+
{
|
|
1081
|
+
"data-state": selected.has(row.id) ? "selected" : void 0,
|
|
1082
|
+
className: onRowClick ? "cursor-pointer" : "hover:bg-transparent",
|
|
1083
|
+
onClick: onRowClick ? () => onRowClick(row.original) : void 0,
|
|
1084
|
+
children: [
|
|
1085
|
+
selectable && /* @__PURE__ */ jsx(TableCell, { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsx(
|
|
1086
|
+
Checkbox,
|
|
1087
|
+
{
|
|
1088
|
+
checked: selected.has(row.id),
|
|
1089
|
+
onCheckedChange: () => toggleSelected(row.id),
|
|
1090
|
+
"aria-label": `S\xE9lectionner ${row.id}`
|
|
1091
|
+
}
|
|
1092
|
+
) }),
|
|
1093
|
+
row.getVisibleCells().map((cell) => {
|
|
1094
|
+
const column = visibleColumns.find((c) => c.key === cell.column.id);
|
|
1095
|
+
return /* @__PURE__ */ jsx(
|
|
1096
|
+
TableCell,
|
|
1097
|
+
{
|
|
1098
|
+
className: column?.align === "right" ? "text-right" : void 0,
|
|
1099
|
+
children: flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
1100
|
+
},
|
|
1101
|
+
cell.id
|
|
1102
|
+
);
|
|
1103
|
+
})
|
|
1104
|
+
]
|
|
1105
|
+
},
|
|
1106
|
+
row.id
|
|
1107
|
+
)),
|
|
1108
|
+
spaceBelow > 0 && /* @__PURE__ */ jsx("tr", { "aria-hidden": true, style: { height: spaceBelow } })
|
|
1109
|
+
] }) })
|
|
1110
|
+
] })
|
|
1111
|
+
}
|
|
1112
|
+
),
|
|
1113
|
+
pagination && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
1114
|
+
/* @__PURE__ */ jsx(
|
|
1115
|
+
Button,
|
|
1116
|
+
{
|
|
1117
|
+
variant: "outline",
|
|
1118
|
+
size: "sm",
|
|
1119
|
+
disabled: !pagination.hasPrevious,
|
|
1120
|
+
onClick: pagination.onPrevious,
|
|
1121
|
+
children: "Pr\xE9c\xE9dent"
|
|
1122
|
+
}
|
|
1123
|
+
),
|
|
1124
|
+
/* @__PURE__ */ jsx(
|
|
1125
|
+
Button,
|
|
1126
|
+
{
|
|
1127
|
+
variant: "outline",
|
|
1128
|
+
size: "sm",
|
|
1129
|
+
disabled: !pagination.hasNext,
|
|
1130
|
+
onClick: pagination.onNext,
|
|
1131
|
+
children: "Suivant"
|
|
1132
|
+
}
|
|
1133
|
+
)
|
|
1134
|
+
] })
|
|
1135
|
+
] });
|
|
1136
|
+
}
|
|
1137
|
+
function ResizeHandle({
|
|
1138
|
+
label,
|
|
1139
|
+
onResize
|
|
1140
|
+
}) {
|
|
1141
|
+
const start = (event) => {
|
|
1142
|
+
const header = event.currentTarget.parentElement;
|
|
1143
|
+
if (!header) return;
|
|
1144
|
+
const from = header.getBoundingClientRect().width;
|
|
1145
|
+
const originX = event.clientX;
|
|
1146
|
+
const move = (moveEvent) => onResize(moveEvent.clientX - originX, from);
|
|
1147
|
+
const stop = () => {
|
|
1148
|
+
window.removeEventListener("pointermove", move);
|
|
1149
|
+
window.removeEventListener("pointerup", stop);
|
|
1150
|
+
};
|
|
1151
|
+
window.addEventListener("pointermove", move);
|
|
1152
|
+
window.addEventListener("pointerup", stop);
|
|
1153
|
+
event.preventDefault();
|
|
1154
|
+
};
|
|
1155
|
+
return /* @__PURE__ */ jsx(
|
|
1156
|
+
"span",
|
|
1157
|
+
{
|
|
1158
|
+
"aria-hidden": true,
|
|
1159
|
+
"data-resize-handle": label,
|
|
1160
|
+
onPointerDown: start,
|
|
1161
|
+
className: "absolute inset-y-0 right-0 w-1 cursor-col-resize select-none hover:bg-border"
|
|
1162
|
+
}
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
function SortGlyph({ direction }) {
|
|
1166
|
+
return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-[10px] leading-none", children: direction === "asc" ? "\u25B2" : direction === "desc" ? "\u25BC" : "\u2195" });
|
|
1167
|
+
}
|
|
1168
|
+
function StatusRow({
|
|
1169
|
+
colSpan,
|
|
1170
|
+
tone,
|
|
1171
|
+
children
|
|
1172
|
+
}) {
|
|
1173
|
+
return /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(
|
|
1174
|
+
TableCell,
|
|
1175
|
+
{
|
|
1176
|
+
colSpan,
|
|
1177
|
+
className: cn(
|
|
1178
|
+
"text-sm",
|
|
1179
|
+
tone === "error" ? "text-status-blocked" : "text-muted-foreground"
|
|
1180
|
+
),
|
|
1181
|
+
children
|
|
1182
|
+
}
|
|
1183
|
+
) });
|
|
1184
|
+
}
|
|
1185
|
+
function SkeletonRows({ columns }) {
|
|
1186
|
+
return /* @__PURE__ */ jsx(Fragment, { children: Array.from({ length: 5 }, (_, row) => /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: Array.from({ length: columns }, (_2, cell) => /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("span", { className: "block h-3 w-full animate-pulse rounded bg-muted" }) }, cell)) }, row)) });
|
|
1187
|
+
}
|
|
1188
|
+
var VALUELESS = /* @__PURE__ */ new Set(["empty", "nempty"]);
|
|
1189
|
+
var SINGLE_VALUE = /* @__PURE__ */ new Set([
|
|
1190
|
+
"contains",
|
|
1191
|
+
"ncontains",
|
|
1192
|
+
"gt",
|
|
1193
|
+
"lt",
|
|
1194
|
+
"gte",
|
|
1195
|
+
"lte"
|
|
1196
|
+
]);
|
|
1197
|
+
var OPERATOR_LABELS = {
|
|
1198
|
+
eq: "est",
|
|
1199
|
+
neq: "n'est pas",
|
|
1200
|
+
contains: "contient",
|
|
1201
|
+
ncontains: "ne contient pas",
|
|
1202
|
+
gt: "sup\xE9rieur \xE0",
|
|
1203
|
+
lt: "inf\xE9rieur \xE0",
|
|
1204
|
+
gte: "sup\xE9rieur ou \xE9gal \xE0",
|
|
1205
|
+
lte: "inf\xE9rieur ou \xE9gal \xE0",
|
|
1206
|
+
empty: "est vide",
|
|
1207
|
+
nempty: "n'est pas vide"
|
|
1208
|
+
};
|
|
1209
|
+
function isApplied(filter) {
|
|
1210
|
+
if (!filter) return false;
|
|
1211
|
+
return VALUELESS.has(filter.operator) || filter.values.length > 0;
|
|
1212
|
+
}
|
|
1213
|
+
function compare(value, bound) {
|
|
1214
|
+
if (value === "" || bound === "") return null;
|
|
1215
|
+
const boundNumber = Number(bound);
|
|
1216
|
+
if (!Number.isNaN(boundNumber)) {
|
|
1217
|
+
const valueNumber = Number(value);
|
|
1218
|
+
return Number.isNaN(valueNumber) ? null : valueNumber - boundNumber;
|
|
1219
|
+
}
|
|
1220
|
+
const boundDate = Date.parse(bound);
|
|
1221
|
+
if (!Number.isNaN(boundDate)) {
|
|
1222
|
+
const valueDate = Date.parse(value);
|
|
1223
|
+
return Number.isNaN(valueDate) ? null : valueDate - boundDate;
|
|
1224
|
+
}
|
|
1225
|
+
return value < bound ? -1 : value > bound ? 1 : 0;
|
|
1226
|
+
}
|
|
1227
|
+
function matches(raw, filter) {
|
|
1228
|
+
const value = raw == null ? "" : String(raw);
|
|
1229
|
+
const [bound = ""] = filter.values;
|
|
1230
|
+
const ordering = compare(value, bound);
|
|
1231
|
+
switch (filter.operator) {
|
|
1232
|
+
case "eq":
|
|
1233
|
+
return filter.values.includes(value);
|
|
1234
|
+
case "neq":
|
|
1235
|
+
return !filter.values.includes(value);
|
|
1236
|
+
case "contains":
|
|
1237
|
+
return value.toLowerCase().includes(bound.toLowerCase());
|
|
1238
|
+
case "ncontains":
|
|
1239
|
+
return !value.toLowerCase().includes(bound.toLowerCase());
|
|
1240
|
+
case "gt":
|
|
1241
|
+
return ordering !== null && ordering > 0;
|
|
1242
|
+
case "lt":
|
|
1243
|
+
return ordering !== null && ordering < 0;
|
|
1244
|
+
case "gte":
|
|
1245
|
+
return ordering !== null && ordering >= 0;
|
|
1246
|
+
case "lte":
|
|
1247
|
+
return ordering !== null && ordering <= 0;
|
|
1248
|
+
case "empty":
|
|
1249
|
+
return value === "";
|
|
1250
|
+
case "nempty":
|
|
1251
|
+
return value !== "";
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
function FacetFilter({
|
|
1255
|
+
facet,
|
|
1256
|
+
filter,
|
|
1257
|
+
onChange
|
|
1258
|
+
}) {
|
|
1259
|
+
const [open, setOpen] = React2.useState(false);
|
|
1260
|
+
const operators = facet.operators ?? ["eq"];
|
|
1261
|
+
const operator = filter?.operator ?? operators[0] ?? "eq";
|
|
1262
|
+
const values = filter?.values ?? [];
|
|
1263
|
+
const summary = !isApplied(filter) ? facet.label : VALUELESS.has(operator) ? `${facet.label} \xB7 ${OPERATOR_LABELS[operator]}` : `${facet.label} \xB7 ${values.length}`;
|
|
1264
|
+
const setOperator = (next) => {
|
|
1265
|
+
const sameKind = SINGLE_VALUE.has(next) === SINGLE_VALUE.has(operator);
|
|
1266
|
+
onChange({
|
|
1267
|
+
operator: next,
|
|
1268
|
+
values: VALUELESS.has(next) || !sameKind ? [] : SINGLE_VALUE.has(next) ? values.slice(0, 1) : values
|
|
1269
|
+
});
|
|
1270
|
+
};
|
|
1271
|
+
return /* @__PURE__ */ jsx(
|
|
1272
|
+
Popover,
|
|
1273
|
+
{
|
|
1274
|
+
open,
|
|
1275
|
+
onOpenChange: setOpen,
|
|
1276
|
+
trigger: (
|
|
1277
|
+
// Named for what it does: a chip reading just "Nom" tells a screen-reader
|
|
1278
|
+
// user nothing, and collides with the sortable column header of that name.
|
|
1279
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", "aria-label": `Filtrer par ${summary}`, children: summary })
|
|
1280
|
+
),
|
|
1281
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex w-56 flex-col gap-2", children: [
|
|
1282
|
+
operators.length > 1 && /* @__PURE__ */ jsx(
|
|
1283
|
+
Select,
|
|
1284
|
+
{
|
|
1285
|
+
value: operator,
|
|
1286
|
+
"aria-label": `Op\xE9rateur \u2014 ${facet.label}`,
|
|
1287
|
+
onValueChange: (next) => setOperator(next),
|
|
1288
|
+
items: operators.map((candidate) => ({
|
|
1289
|
+
value: candidate,
|
|
1290
|
+
label: OPERATOR_LABELS[candidate]
|
|
1291
|
+
}))
|
|
1292
|
+
}
|
|
1293
|
+
),
|
|
1294
|
+
VALUELESS.has(operator) ? null : SINGLE_VALUE.has(operator) || !facet.options ? /* @__PURE__ */ jsx(
|
|
1295
|
+
Input,
|
|
1296
|
+
{
|
|
1297
|
+
value: values[0] ?? "",
|
|
1298
|
+
"aria-label": `Valeur \u2014 ${facet.label}`,
|
|
1299
|
+
onChange: (event) => onChange({ operator, values: event.target.value ? [event.target.value] : [] })
|
|
1300
|
+
}
|
|
1301
|
+
) : facet.options.map((option) => /* @__PURE__ */ jsx(
|
|
1302
|
+
Checkbox,
|
|
1303
|
+
{
|
|
1304
|
+
checked: values.includes(option.value),
|
|
1305
|
+
onCheckedChange: () => onChange({
|
|
1306
|
+
operator,
|
|
1307
|
+
values: values.includes(option.value) ? values.filter((v) => v !== option.value) : [...values, option.value]
|
|
1308
|
+
}),
|
|
1309
|
+
label: option.label ?? option.value
|
|
1310
|
+
},
|
|
1311
|
+
option.value
|
|
1312
|
+
)),
|
|
1313
|
+
isApplied(filter) && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => onChange(null), children: "Effacer" })
|
|
1314
|
+
] })
|
|
1315
|
+
}
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
function ColumnPicker({
|
|
1319
|
+
columns,
|
|
1320
|
+
hidden,
|
|
1321
|
+
onToggle
|
|
1322
|
+
}) {
|
|
1323
|
+
const [open, setOpen] = React2.useState(false);
|
|
828
1324
|
return /* @__PURE__ */ jsx(
|
|
829
1325
|
Popover,
|
|
830
1326
|
{
|
|
831
1327
|
open,
|
|
832
1328
|
onOpenChange: setOpen,
|
|
833
|
-
|
|
1329
|
+
align: "end",
|
|
1330
|
+
trigger: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: "Colonnes" }),
|
|
1331
|
+
children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: columns.map((column) => /* @__PURE__ */ jsx(
|
|
1332
|
+
Checkbox,
|
|
1333
|
+
{
|
|
1334
|
+
checked: !hidden.has(column.key),
|
|
1335
|
+
onCheckedChange: () => onToggle(column.key),
|
|
1336
|
+
label: typeof column.header === "string" ? column.header : column.key
|
|
1337
|
+
},
|
|
1338
|
+
column.key
|
|
1339
|
+
)) })
|
|
1340
|
+
}
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
var PERIOD_PRESETS = [
|
|
1344
|
+
{ key: "today", label: "Aujourd'hui" },
|
|
1345
|
+
{ key: "yesterday", label: "Hier" },
|
|
1346
|
+
{ key: "last7", label: "7 derniers jours" },
|
|
1347
|
+
{ key: "last30", label: "30 derniers jours" },
|
|
1348
|
+
{ key: "last90", label: "90 derniers jours" },
|
|
1349
|
+
{ key: "thisMonth", label: "Mois en cours" },
|
|
1350
|
+
{ key: "lastMonth", label: "Mois dernier" },
|
|
1351
|
+
{ key: "thisQuarter", label: "Trimestre en cours" },
|
|
1352
|
+
{ key: "last12Months", label: "12 derniers mois" },
|
|
1353
|
+
{ key: "thisYear", label: "Ann\xE9e en cours" },
|
|
1354
|
+
{ key: "all", label: "Tout l'historique" }
|
|
1355
|
+
];
|
|
1356
|
+
var COMPARE_LABELS = {
|
|
1357
|
+
none: "Aucune",
|
|
1358
|
+
previous_period: "P\xE9riode pr\xE9c\xE9dente",
|
|
1359
|
+
previous_year: "Ann\xE9e pr\xE9c\xE9dente",
|
|
1360
|
+
custom: "Personnalis\xE9e"
|
|
1361
|
+
};
|
|
1362
|
+
var DAY_FORMAT = "yyyy-MM-dd";
|
|
1363
|
+
var toDay = (date) => format(date, DAY_FORMAT);
|
|
1364
|
+
var fromDay = (day) => parse(day, DAY_FORMAT, /* @__PURE__ */ new Date());
|
|
1365
|
+
var CALENDAR_UNIT = {
|
|
1366
|
+
thisMonth: "month",
|
|
1367
|
+
lastMonth: "month",
|
|
1368
|
+
thisQuarter: "quarter",
|
|
1369
|
+
thisYear: "year"
|
|
1370
|
+
};
|
|
1371
|
+
function presetRange(preset, today) {
|
|
1372
|
+
const day = toDay(today);
|
|
1373
|
+
switch (preset) {
|
|
1374
|
+
case "today":
|
|
1375
|
+
return { from: day, to: day };
|
|
1376
|
+
case "yesterday": {
|
|
1377
|
+
const yesterday = toDay(subDays(today, 1));
|
|
1378
|
+
return { from: yesterday, to: yesterday };
|
|
1379
|
+
}
|
|
1380
|
+
case "last7":
|
|
1381
|
+
return { from: toDay(subDays(today, 6)), to: day };
|
|
1382
|
+
case "last30":
|
|
1383
|
+
return { from: toDay(subDays(today, 29)), to: day };
|
|
1384
|
+
case "last90":
|
|
1385
|
+
return { from: toDay(subDays(today, 89)), to: day };
|
|
1386
|
+
case "thisMonth":
|
|
1387
|
+
return { from: toDay(startOfMonth(today)), to: toDay(endOfMonth(today)) };
|
|
1388
|
+
case "lastMonth": {
|
|
1389
|
+
const previous = subMonths(today, 1);
|
|
1390
|
+
return { from: toDay(startOfMonth(previous)), to: toDay(endOfMonth(previous)) };
|
|
1391
|
+
}
|
|
1392
|
+
case "thisQuarter":
|
|
1393
|
+
return { from: toDay(startOfQuarter(today)), to: toDay(endOfQuarter(today)) };
|
|
1394
|
+
case "last12Months":
|
|
1395
|
+
return { from: toDay(addDays(subYears(today, 1), 1)), to: day };
|
|
1396
|
+
case "thisYear":
|
|
1397
|
+
return { from: toDay(startOfYear(today)), to: toDay(endOfYear(today)) };
|
|
1398
|
+
case "all":
|
|
1399
|
+
return { to: day };
|
|
1400
|
+
case "custom":
|
|
1401
|
+
return { from: day, to: day };
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function comparisonRange(preset, range, compare2) {
|
|
1405
|
+
if (compare2 === "none" || compare2 === "custom" || range.from === void 0) return void 0;
|
|
1406
|
+
const from = fromDay(range.from);
|
|
1407
|
+
const to = fromDay(range.to);
|
|
1408
|
+
if (compare2 === "previous_year") {
|
|
1409
|
+
return { from: toDay(subYears(from, 1)), to: toDay(subYears(to, 1)) };
|
|
1410
|
+
}
|
|
1411
|
+
const unit = CALENDAR_UNIT[preset];
|
|
1412
|
+
if (unit === "month") {
|
|
1413
|
+
const previous = subMonths(from, 1);
|
|
1414
|
+
return { from: toDay(startOfMonth(previous)), to: toDay(endOfMonth(previous)) };
|
|
1415
|
+
}
|
|
1416
|
+
if (unit === "quarter") {
|
|
1417
|
+
const previous = addQuarters(from, -1);
|
|
1418
|
+
return { from: toDay(startOfQuarter(previous)), to: toDay(endOfQuarter(previous)) };
|
|
1419
|
+
}
|
|
1420
|
+
if (unit === "year") {
|
|
1421
|
+
const previous = subYears(from, 1);
|
|
1422
|
+
return { from: toDay(startOfYear(previous)), to: toDay(endOfYear(previous)) };
|
|
1423
|
+
}
|
|
1424
|
+
const length = differenceInCalendarDays(to, from) + 1;
|
|
1425
|
+
return { from: toDay(subDays(from, length)), to: toDay(subDays(to, length)) };
|
|
1426
|
+
}
|
|
1427
|
+
function makePeriod(preset, range, compare2, customCompareRange) {
|
|
1428
|
+
const resolved = compare2 === "custom" ? customCompareRange : comparisonRange(preset, range, compare2);
|
|
1429
|
+
const effective = resolved === void 0 ? "none" : compare2;
|
|
1430
|
+
return {
|
|
1431
|
+
preset,
|
|
1432
|
+
range,
|
|
1433
|
+
compare: effective,
|
|
1434
|
+
...resolved ? { compareRange: resolved } : {}
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
function periodFromPreset(preset, today, compare2 = "none") {
|
|
1438
|
+
return makePeriod(preset, presetRange(preset, today), compare2);
|
|
1439
|
+
}
|
|
1440
|
+
var MONTHS_ABBR = [
|
|
1441
|
+
"janv.",
|
|
1442
|
+
"f\xE9vr.",
|
|
1443
|
+
"mars",
|
|
1444
|
+
"avr.",
|
|
1445
|
+
"mai",
|
|
1446
|
+
"juin",
|
|
1447
|
+
"juil.",
|
|
1448
|
+
"ao\xFBt",
|
|
1449
|
+
"sept.",
|
|
1450
|
+
"oct.",
|
|
1451
|
+
"nov.",
|
|
1452
|
+
"d\xE9c."
|
|
1453
|
+
];
|
|
1454
|
+
var formatDay = (day) => {
|
|
1455
|
+
const date = fromDay(day);
|
|
1456
|
+
return `${date.getDate()} ${MONTHS_ABBR[date.getMonth()]} ${date.getFullYear()}`;
|
|
1457
|
+
};
|
|
1458
|
+
function formatRange(range) {
|
|
1459
|
+
if (range.from === void 0) return `Jusqu'au ${formatDay(range.to)}`;
|
|
1460
|
+
if (range.from === range.to) return formatDay(range.from);
|
|
1461
|
+
const from = fromDay(range.from);
|
|
1462
|
+
const to = fromDay(range.to);
|
|
1463
|
+
if (from.getFullYear() === to.getFullYear() && from.getMonth() === to.getMonth()) {
|
|
1464
|
+
return `${from.getDate()} \u2013 ${to.getDate()} ${MONTHS_ABBR[to.getMonth()]} ${to.getFullYear()}`;
|
|
1465
|
+
}
|
|
1466
|
+
return `${formatDay(range.from)} \u2013 ${formatDay(range.to)}`;
|
|
1467
|
+
}
|
|
1468
|
+
function formatPeriod(value) {
|
|
1469
|
+
const preset = PERIOD_PRESETS.find((entry) => entry.key === value.preset);
|
|
1470
|
+
if (preset) return preset.label;
|
|
1471
|
+
return isCompleteRange(value.range) ? formatRange(value.range) : "P\xE9riode";
|
|
1472
|
+
}
|
|
1473
|
+
var DAY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
1474
|
+
var isDay = (value) => value !== void 0 && DAY_PATTERN.test(value);
|
|
1475
|
+
function isCompleteRange(range) {
|
|
1476
|
+
return isDay(range.to) && (range.from === void 0 || isDay(range.from));
|
|
1477
|
+
}
|
|
1478
|
+
function spansTwoMonths(range) {
|
|
1479
|
+
if (!isDay(range.from) || !isDay(range.to)) return false;
|
|
1480
|
+
const from = fromDay(range.from);
|
|
1481
|
+
const to = fromDay(range.to);
|
|
1482
|
+
return from.getFullYear() !== to.getFullYear() || from.getMonth() !== to.getMonth();
|
|
1483
|
+
}
|
|
1484
|
+
function orderedRange(from, to) {
|
|
1485
|
+
return from <= to ? { from, to } : { from: to, to: from };
|
|
1486
|
+
}
|
|
1487
|
+
var COMPARE_MODES = [
|
|
1488
|
+
"none",
|
|
1489
|
+
"previous_period",
|
|
1490
|
+
"previous_year",
|
|
1491
|
+
"custom"
|
|
1492
|
+
];
|
|
1493
|
+
var DAY_PICKER_CLASS_NAMES = {
|
|
1494
|
+
// DayPicker renders its Nav as a sibling of the months, before them, so the
|
|
1495
|
+
// arrows are positioned against this box rather than against a caption.
|
|
1496
|
+
months: "relative flex gap-4",
|
|
1497
|
+
month: "space-y-2",
|
|
1498
|
+
month_caption: "flex h-8 items-center justify-center",
|
|
1499
|
+
caption_label: "text-sm font-medium capitalize",
|
|
1500
|
+
nav: "flex items-center justify-between absolute inset-x-0 h-8 px-1 pointer-events-none",
|
|
1501
|
+
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",
|
|
1502
|
+
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",
|
|
1503
|
+
chevron: "h-4 w-4 fill-current",
|
|
1504
|
+
month_grid: "w-full border-collapse",
|
|
1505
|
+
weekdays: "flex",
|
|
1506
|
+
weekday: "w-9 text-xs font-normal text-muted-foreground",
|
|
1507
|
+
week: "flex w-full",
|
|
1508
|
+
day: "h-9 w-9 p-0 text-center text-sm",
|
|
1509
|
+
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",
|
|
1510
|
+
today: "font-semibold text-primary",
|
|
1511
|
+
outside: "text-muted-foreground/50",
|
|
1512
|
+
disabled: "opacity-40",
|
|
1513
|
+
selected: "bg-primary/15",
|
|
1514
|
+
range_start: "rounded-l-md bg-primary [&_button]:bg-primary [&_button]:text-primary-foreground",
|
|
1515
|
+
range_end: "rounded-r-md bg-primary [&_button]:bg-primary [&_button]:text-primary-foreground",
|
|
1516
|
+
range_middle: "bg-primary/15 [&_button]:hover:bg-primary/25"
|
|
1517
|
+
};
|
|
1518
|
+
var COMPARE_MODIFIER_CLASS = "bg-chart-2/25 ring-1 ring-inset ring-chart-2/40";
|
|
1519
|
+
function draftOf(value) {
|
|
1520
|
+
return {
|
|
1521
|
+
preset: value.preset,
|
|
1522
|
+
// Anything that is not a day is "not chosen yet". The picker this replaced
|
|
1523
|
+
// accepted `{ from: '', to: '' }` for exactly that, and its callers still do.
|
|
1524
|
+
from: isDay(value.range.from) ? value.range.from : void 0,
|
|
1525
|
+
to: isDay(value.range.to) ? value.range.to : void 0,
|
|
1526
|
+
compare: value.compare,
|
|
1527
|
+
compareFrom: value.compareRange?.from,
|
|
1528
|
+
compareTo: value.compareRange?.to
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
function rangeOf(draft) {
|
|
1532
|
+
if (!isDay(draft.to)) return null;
|
|
1533
|
+
if (draft.preset === "all") return { to: draft.to };
|
|
1534
|
+
if (!isDay(draft.from)) return null;
|
|
1535
|
+
return { from: draft.from, to: draft.to };
|
|
1536
|
+
}
|
|
1537
|
+
function customCompareOf(draft) {
|
|
1538
|
+
if (!isDay(draft.compareFrom) || !isDay(draft.compareTo)) return void 0;
|
|
1539
|
+
return orderedRange(draft.compareFrom, draft.compareTo);
|
|
1540
|
+
}
|
|
1541
|
+
function previewCompare(draft) {
|
|
1542
|
+
const range = rangeOf(draft);
|
|
1543
|
+
if (!range) return void 0;
|
|
1544
|
+
if (draft.compare === "custom") return customCompareOf(draft);
|
|
1545
|
+
return comparisonRange(draft.preset, range, draft.compare);
|
|
1546
|
+
}
|
|
1547
|
+
function activeCompare(draft) {
|
|
1548
|
+
return draft.preset === "all" ? "none" : draft.compare;
|
|
1549
|
+
}
|
|
1550
|
+
function anchorMonth(range) {
|
|
1551
|
+
if (!range) return void 0;
|
|
1552
|
+
if (isDay(range.from)) return fromDay(range.from);
|
|
1553
|
+
return isDay(range.to) ? fromDay(range.to) : void 0;
|
|
1554
|
+
}
|
|
1555
|
+
function DateRangeCompare({
|
|
1556
|
+
value,
|
|
1557
|
+
onChange,
|
|
1558
|
+
comparable = true,
|
|
1559
|
+
presets = PERIOD_PRESETS,
|
|
1560
|
+
align = "start",
|
|
1561
|
+
disabled,
|
|
1562
|
+
className
|
|
1563
|
+
}) {
|
|
1564
|
+
const [open, setOpen] = React2.useState(false);
|
|
1565
|
+
const [draft, setDraft] = React2.useState(() => draftOf(value));
|
|
1566
|
+
const [month, setMonth] = React2.useState(void 0);
|
|
1567
|
+
const [months, setMonths] = React2.useState(1);
|
|
1568
|
+
const showRange = (range2) => {
|
|
1569
|
+
setMonth(anchorMonth(range2));
|
|
1570
|
+
setMonths(spansTwoMonths(range2) ? 2 : 1);
|
|
1571
|
+
};
|
|
1572
|
+
const openPanel = () => {
|
|
1573
|
+
setDraft(draftOf(value));
|
|
1574
|
+
showRange(value.range);
|
|
1575
|
+
setOpen(true);
|
|
1576
|
+
};
|
|
1577
|
+
const pickPreset = (preset) => {
|
|
1578
|
+
const resolved = periodFromPreset(preset, /* @__PURE__ */ new Date(), activeCompare(draft));
|
|
1579
|
+
setDraft({
|
|
1580
|
+
...draftOf(resolved),
|
|
1581
|
+
// The rail changes the period, not the comparison: the mode the user chose
|
|
1582
|
+
// survives, and `activeCompare` decides whether it can apply here.
|
|
1583
|
+
compare: draft.compare,
|
|
1584
|
+
compareFrom: draft.compareFrom,
|
|
1585
|
+
compareTo: draft.compareTo
|
|
1586
|
+
});
|
|
1587
|
+
showRange(resolved.range);
|
|
1588
|
+
};
|
|
1589
|
+
const pickDays = (selected) => {
|
|
1590
|
+
setDraft({
|
|
1591
|
+
...draft,
|
|
1592
|
+
preset: "custom",
|
|
1593
|
+
from: selected?.from ? toDay(selected.from) : void 0,
|
|
1594
|
+
to: selected?.to ? toDay(selected.to) : void 0
|
|
1595
|
+
});
|
|
1596
|
+
};
|
|
1597
|
+
const range = rangeOf(draft);
|
|
1598
|
+
const compareRange = previewCompare(draft);
|
|
1599
|
+
const apply = () => {
|
|
1600
|
+
if (!range) return;
|
|
1601
|
+
onChange(makePeriod(draft.preset, range, activeCompare(draft), customCompareOf(draft)));
|
|
1602
|
+
setOpen(false);
|
|
1603
|
+
};
|
|
1604
|
+
const selectedDays = isDay(draft.from) ? { from: fromDay(draft.from), to: isDay(draft.to) ? fromDay(draft.to) : void 0 } : void 0;
|
|
1605
|
+
return /* @__PURE__ */ jsx(
|
|
1606
|
+
Popover,
|
|
1607
|
+
{
|
|
1608
|
+
open,
|
|
1609
|
+
onOpenChange: (next) => next ? openPanel() : setOpen(false),
|
|
834
1610
|
align,
|
|
835
|
-
className: cn("p-
|
|
836
|
-
|
|
1611
|
+
className: cn("w-auto p-0", className),
|
|
1612
|
+
trigger: /* @__PURE__ */ jsx(Button, { variant: "outline", disabled, children: formatPeriod(value) }),
|
|
1613
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
|
|
1614
|
+
presets.length > 0 && /* @__PURE__ */ jsx(PresetRail, { presets, selected: draft.preset, onSelect: pickPreset }),
|
|
1615
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
1616
|
+
/* @__PURE__ */ jsx("div", { className: "relative p-3", children: /* @__PURE__ */ jsx(
|
|
1617
|
+
DayPicker,
|
|
1618
|
+
{
|
|
1619
|
+
mode: "range",
|
|
1620
|
+
locale: fr,
|
|
1621
|
+
weekStartsOn: 1,
|
|
1622
|
+
numberOfMonths: months,
|
|
1623
|
+
month,
|
|
1624
|
+
onMonthChange: setMonth,
|
|
1625
|
+
selected: selectedDays,
|
|
1626
|
+
onSelect: pickDays,
|
|
1627
|
+
modifiers: {
|
|
1628
|
+
compare: compareRange?.from ? [{ from: fromDay(compareRange.from), to: fromDay(compareRange.to) }] : []
|
|
1629
|
+
},
|
|
1630
|
+
modifiersClassNames: { compare: COMPARE_MODIFIER_CLASS },
|
|
1631
|
+
classNames: DAY_PICKER_CLASS_NAMES
|
|
1632
|
+
}
|
|
1633
|
+
) }),
|
|
1634
|
+
comparable && /* @__PURE__ */ jsx(
|
|
1635
|
+
CompareSection,
|
|
1636
|
+
{
|
|
1637
|
+
draft,
|
|
1638
|
+
compareRange,
|
|
1639
|
+
onModeChange: (compare2) => setDraft({ ...draft, compare: compare2 }),
|
|
1640
|
+
onCustomChange: (from, to) => setDraft({ ...draft, compareFrom: from, compareTo: to })
|
|
1641
|
+
}
|
|
1642
|
+
),
|
|
1643
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 border-t px-3 py-2", children: [
|
|
1644
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
1645
|
+
range ? formatRange(range) : "Choisissez une date de fin",
|
|
1646
|
+
compareRange && ` vs ${formatRange(compareRange)}`
|
|
1647
|
+
] }),
|
|
1648
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1649
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setOpen(false), children: "Annuler" }),
|
|
1650
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: apply, disabled: !range, children: "Appliquer" })
|
|
1651
|
+
] })
|
|
1652
|
+
] })
|
|
1653
|
+
] })
|
|
1654
|
+
] })
|
|
1655
|
+
}
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
function DateRangePicker({ value, onChange, className, disabled }) {
|
|
1659
|
+
return /* @__PURE__ */ jsx(
|
|
1660
|
+
DateRangeCompare,
|
|
1661
|
+
{
|
|
1662
|
+
value: { preset: "custom", range: value, compare: "none" },
|
|
1663
|
+
onChange: (next) => {
|
|
1664
|
+
if (next.range.from !== void 0) onChange({ from: next.range.from, to: next.range.to });
|
|
1665
|
+
},
|
|
1666
|
+
comparable: false,
|
|
1667
|
+
presets: [],
|
|
1668
|
+
disabled,
|
|
1669
|
+
className
|
|
1670
|
+
}
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
function PresetRail({
|
|
1674
|
+
presets,
|
|
1675
|
+
selected,
|
|
1676
|
+
onSelect
|
|
1677
|
+
}) {
|
|
1678
|
+
const refs = React2.useRef([]);
|
|
1679
|
+
const activeIndex = Math.max(
|
|
1680
|
+
0,
|
|
1681
|
+
presets.findIndex((preset) => preset.key === selected)
|
|
1682
|
+
);
|
|
1683
|
+
const moveTo = (index) => {
|
|
1684
|
+
const next = (index + presets.length) % presets.length;
|
|
1685
|
+
refs.current[next]?.focus();
|
|
1686
|
+
onSelect(presets[next].key);
|
|
1687
|
+
};
|
|
1688
|
+
const onKeyDown = (event, index) => {
|
|
1689
|
+
const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];
|
|
1690
|
+
if (step !== void 0) {
|
|
1691
|
+
event.preventDefault();
|
|
1692
|
+
moveTo(index + step);
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
if (event.key === "Home" || event.key === "End") {
|
|
1696
|
+
event.preventDefault();
|
|
1697
|
+
moveTo(event.key === "Home" ? 0 : presets.length - 1);
|
|
1698
|
+
}
|
|
1699
|
+
};
|
|
1700
|
+
return /* @__PURE__ */ jsx(
|
|
1701
|
+
"div",
|
|
1702
|
+
{
|
|
1703
|
+
role: "radiogroup",
|
|
1704
|
+
"aria-label": "P\xE9riode pr\xE9d\xE9finie",
|
|
1705
|
+
className: "flex w-44 shrink-0 flex-col gap-0.5 border-r p-2",
|
|
1706
|
+
children: presets.map((preset, index) => /* @__PURE__ */ jsx(
|
|
837
1707
|
"button",
|
|
838
1708
|
{
|
|
1709
|
+
ref: (node) => {
|
|
1710
|
+
refs.current[index] = node;
|
|
1711
|
+
},
|
|
839
1712
|
type: "button",
|
|
840
|
-
role: "
|
|
841
|
-
|
|
1713
|
+
role: "radio",
|
|
1714
|
+
"aria-checked": preset.key === selected,
|
|
1715
|
+
tabIndex: index === activeIndex ? 0 : -1,
|
|
1716
|
+
onKeyDown: (event) => onKeyDown(event, index),
|
|
1717
|
+
onClick: () => onSelect(preset.key),
|
|
842
1718
|
className: cn(
|
|
843
|
-
"
|
|
844
|
-
|
|
1719
|
+
"rounded px-3 py-1.5 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1720
|
+
preset.key === selected ? "bg-muted font-medium text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
845
1721
|
),
|
|
846
|
-
|
|
847
|
-
setOpen(false);
|
|
848
|
-
item.onSelect();
|
|
849
|
-
},
|
|
850
|
-
children: item.label
|
|
1722
|
+
children: preset.label
|
|
851
1723
|
},
|
|
852
|
-
|
|
853
|
-
))
|
|
1724
|
+
preset.key
|
|
1725
|
+
))
|
|
1726
|
+
}
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
function CompareSection({
|
|
1730
|
+
draft,
|
|
1731
|
+
compareRange,
|
|
1732
|
+
onModeChange,
|
|
1733
|
+
onCustomChange
|
|
1734
|
+
}) {
|
|
1735
|
+
const derivable = draft.preset !== "all";
|
|
1736
|
+
const active = activeCompare(draft);
|
|
1737
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t px-3 py-2", children: [
|
|
1738
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Comparer \xE0" }),
|
|
1739
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: COMPARE_MODES.map((mode) => /* @__PURE__ */ jsx(
|
|
1740
|
+
"button",
|
|
1741
|
+
{
|
|
1742
|
+
type: "button",
|
|
1743
|
+
"aria-pressed": active === mode,
|
|
1744
|
+
disabled: mode !== "none" && !derivable,
|
|
1745
|
+
onClick: () => onModeChange(mode),
|
|
1746
|
+
className: cn(
|
|
1747
|
+
"rounded-full border px-2.5 py-1 text-xs transition-colors disabled:opacity-40",
|
|
1748
|
+
active === mode ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
1749
|
+
),
|
|
1750
|
+
children: COMPARE_LABELS[mode]
|
|
1751
|
+
},
|
|
1752
|
+
mode
|
|
1753
|
+
)) }),
|
|
1754
|
+
active === "custom" && // Two inputs rather than a second selection mode in the calendar: with
|
|
1755
|
+
// one grid and two ranges to fill, nothing on screen would say which one
|
|
1756
|
+
// a click is about to change.
|
|
1757
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1758
|
+
/* @__PURE__ */ jsx(
|
|
1759
|
+
"input",
|
|
1760
|
+
{
|
|
1761
|
+
type: "date",
|
|
1762
|
+
"aria-label": "D\xE9but de la comparaison",
|
|
1763
|
+
value: draft.compareFrom ?? "",
|
|
1764
|
+
max: draft.compareTo,
|
|
1765
|
+
onChange: (event) => onCustomChange(event.target.value || void 0, draft.compareTo),
|
|
1766
|
+
className: "h-8 rounded-md border border-input bg-transparent px-2 text-sm"
|
|
1767
|
+
}
|
|
1768
|
+
),
|
|
1769
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2192" }),
|
|
1770
|
+
/* @__PURE__ */ jsx(
|
|
1771
|
+
"input",
|
|
1772
|
+
{
|
|
1773
|
+
type: "date",
|
|
1774
|
+
"aria-label": "Fin de la comparaison",
|
|
1775
|
+
value: draft.compareTo ?? "",
|
|
1776
|
+
min: draft.compareFrom,
|
|
1777
|
+
onChange: (event) => onCustomChange(draft.compareFrom, event.target.value || void 0),
|
|
1778
|
+
className: "h-8 rounded-md border border-input bg-transparent px-2 text-sm"
|
|
1779
|
+
}
|
|
1780
|
+
)
|
|
1781
|
+
] }),
|
|
1782
|
+
compareRange && active !== "custom" && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: formatRange(compareRange) })
|
|
1783
|
+
] });
|
|
1784
|
+
}
|
|
1785
|
+
var CHART_COLORS = [
|
|
1786
|
+
"var(--chart-1)",
|
|
1787
|
+
"var(--chart-2)",
|
|
1788
|
+
"var(--chart-3)",
|
|
1789
|
+
"var(--chart-4)",
|
|
1790
|
+
"var(--chart-5)"
|
|
1791
|
+
];
|
|
1792
|
+
var COMPARE_COLOR = CHART_COLORS[1];
|
|
1793
|
+
var colorAt = (index) => CHART_COLORS[index % CHART_COLORS.length];
|
|
1794
|
+
var formatNumber = (value) => new Intl.NumberFormat("fr-FR").format(Math.round(value));
|
|
1795
|
+
var ChartContext = React2.createContext({ formatValue: formatNumber });
|
|
1796
|
+
function ChartContainer({
|
|
1797
|
+
children,
|
|
1798
|
+
height = 320,
|
|
1799
|
+
formatValue = formatNumber,
|
|
1800
|
+
label,
|
|
1801
|
+
className
|
|
1802
|
+
}) {
|
|
1803
|
+
const context = React2.useMemo(() => ({ formatValue }), [formatValue]);
|
|
1804
|
+
return /* @__PURE__ */ jsx(ChartContext.Provider, { value: context, children: /* @__PURE__ */ jsx(
|
|
1805
|
+
"div",
|
|
1806
|
+
{
|
|
1807
|
+
role: "img",
|
|
1808
|
+
"aria-label": label,
|
|
1809
|
+
style: { height },
|
|
1810
|
+
className: cn(
|
|
1811
|
+
"w-full text-xs",
|
|
1812
|
+
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground",
|
|
1813
|
+
"[&_.recharts-cartesian-grid_line]:stroke-border",
|
|
1814
|
+
"[&_.recharts-curve.recharts-tooltip-cursor]:stroke-border",
|
|
1815
|
+
"[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted/40",
|
|
1816
|
+
"[&_.recharts-sector]:outline-none",
|
|
1817
|
+
"[&_.recharts-surface]:outline-none",
|
|
1818
|
+
className
|
|
1819
|
+
),
|
|
1820
|
+
children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children })
|
|
1821
|
+
}
|
|
1822
|
+
) });
|
|
1823
|
+
}
|
|
1824
|
+
function ChartTooltipContent({
|
|
1825
|
+
active,
|
|
1826
|
+
payload,
|
|
1827
|
+
label
|
|
1828
|
+
}) {
|
|
1829
|
+
const { formatValue } = React2.useContext(ChartContext);
|
|
1830
|
+
if (!active || !payload?.length) return null;
|
|
1831
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-card px-2.5 py-1.5 text-xs shadow-md", children: [
|
|
1832
|
+
label !== void 0 && /* @__PURE__ */ jsx("p", { className: "mb-1 font-medium text-foreground", children: label }),
|
|
1833
|
+
/* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-0.5", children: payload.map((entry, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
|
|
1834
|
+
/* @__PURE__ */ jsx(
|
|
1835
|
+
"span",
|
|
1836
|
+
{
|
|
1837
|
+
"aria-hidden": true,
|
|
1838
|
+
className: "h-2 w-2 shrink-0 rounded-[2px]",
|
|
1839
|
+
style: { backgroundColor: entry.color }
|
|
1840
|
+
}
|
|
1841
|
+
),
|
|
1842
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: entry.name }),
|
|
1843
|
+
/* @__PURE__ */ jsx("span", { className: "ml-auto font-medium tabular-nums text-foreground", children: typeof entry.value === "number" ? formatValue(entry.value) : entry.value })
|
|
1844
|
+
] }, `${entry.dataKey}-${index}`)) })
|
|
1845
|
+
] });
|
|
1846
|
+
}
|
|
1847
|
+
var ChartTooltip = /* @__PURE__ */ jsx(
|
|
1848
|
+
Tooltip,
|
|
1849
|
+
{
|
|
1850
|
+
cursor: { strokeWidth: 1 },
|
|
1851
|
+
content: /* @__PURE__ */ jsx(ChartTooltipContent, {}),
|
|
1852
|
+
wrapperStyle: { outline: "none" }
|
|
1853
|
+
}
|
|
1854
|
+
);
|
|
1855
|
+
function ChartLegendContent({ payload }) {
|
|
1856
|
+
if (!payload?.length) return null;
|
|
1857
|
+
return /* @__PURE__ */ jsx("ul", { className: "flex flex-wrap items-center justify-center gap-x-4 gap-y-1 pt-3", children: payload.map((entry, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1.5", children: [
|
|
1858
|
+
/* @__PURE__ */ jsx(
|
|
1859
|
+
"span",
|
|
1860
|
+
{
|
|
1861
|
+
"aria-hidden": true,
|
|
1862
|
+
className: "h-2 w-2 shrink-0 rounded-[2px]",
|
|
1863
|
+
style: { backgroundColor: entry.color }
|
|
1864
|
+
}
|
|
1865
|
+
),
|
|
1866
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: entry.value })
|
|
1867
|
+
] }, `${entry.dataKey}-${index}`)) });
|
|
1868
|
+
}
|
|
1869
|
+
var ChartLegend = /* @__PURE__ */ jsx(Legend, { content: /* @__PURE__ */ jsx(ChartLegendContent, {}) });
|
|
1870
|
+
var COMPARE_LINE_PROPS = {
|
|
1871
|
+
stroke: COMPARE_COLOR,
|
|
1872
|
+
strokeDasharray: "4 4",
|
|
1873
|
+
strokeOpacity: 0.4,
|
|
1874
|
+
strokeWidth: 2,
|
|
1875
|
+
dot: false,
|
|
1876
|
+
activeDot: false,
|
|
1877
|
+
// Not a preference: Recharts animates `strokeDasharray` from `0px 0px`, so an
|
|
1878
|
+
// animated comparison line is drawn solid and only becomes dashed once the
|
|
1879
|
+
// animation ends. Off, it is dashed from the first frame — and a reference
|
|
1880
|
+
// line that draws itself in is pulling the eye towards the thing meant to
|
|
1881
|
+
// recede behind the real series anyway.
|
|
1882
|
+
isAnimationActive: false
|
|
1883
|
+
};
|
|
1884
|
+
var AXIS_PROPS = {
|
|
1885
|
+
tickLine: false,
|
|
1886
|
+
axisLine: false,
|
|
1887
|
+
tickMargin: 8
|
|
1888
|
+
};
|
|
1889
|
+
var MARK_PROPS = { isAnimationActive: false };
|
|
1890
|
+
function CartesianFrame({ x, formatValue }) {
|
|
1891
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1892
|
+
/* @__PURE__ */ jsx(CartesianGrid, { vertical: false, strokeDasharray: "3 3" }),
|
|
1893
|
+
/* @__PURE__ */ jsx(XAxis, { dataKey: x, ...AXIS_PROPS }),
|
|
1894
|
+
/* @__PURE__ */ jsx(YAxis, { ...AXIS_PROPS, width: 56, tickFormatter: formatValue })
|
|
1895
|
+
] });
|
|
1896
|
+
}
|
|
1897
|
+
function accessibleLabel(series, label) {
|
|
1898
|
+
return label ?? series.map((entry) => entry.label).join(", ");
|
|
1899
|
+
}
|
|
1900
|
+
function showsLegend(props) {
|
|
1901
|
+
if (props.hideLegend) return false;
|
|
1902
|
+
return props.series.length > 1 || props.compareSeries !== void 0;
|
|
1903
|
+
}
|
|
1904
|
+
function BarChart(props) {
|
|
1905
|
+
const { data, x, series, compareSeries, formatValue = formatNumber } = props;
|
|
1906
|
+
return /* @__PURE__ */ jsx(
|
|
1907
|
+
ChartContainer,
|
|
1908
|
+
{
|
|
1909
|
+
height: props.height,
|
|
1910
|
+
formatValue,
|
|
1911
|
+
label: accessibleLabel(series, props.label),
|
|
1912
|
+
className: props.className,
|
|
1913
|
+
children: /* @__PURE__ */ jsxs(BarChart$1, { data, children: [
|
|
1914
|
+
/* @__PURE__ */ jsx(CartesianFrame, { x, formatValue }),
|
|
1915
|
+
ChartTooltip,
|
|
1916
|
+
showsLegend(props) && ChartLegend,
|
|
1917
|
+
series.map((entry, index) => /* @__PURE__ */ jsx(
|
|
1918
|
+
Bar,
|
|
1919
|
+
{
|
|
1920
|
+
dataKey: entry.key,
|
|
1921
|
+
name: entry.label,
|
|
1922
|
+
fill: colorAt(entry.colorIndex ?? index),
|
|
1923
|
+
radius: [4, 4, 0, 0],
|
|
1924
|
+
...MARK_PROPS
|
|
1925
|
+
},
|
|
1926
|
+
entry.key
|
|
1927
|
+
)),
|
|
1928
|
+
compareSeries && /* @__PURE__ */ jsx(
|
|
1929
|
+
Bar,
|
|
1930
|
+
{
|
|
1931
|
+
dataKey: compareSeries.key,
|
|
1932
|
+
name: compareSeries.label,
|
|
1933
|
+
fill: COMPARE_LINE_PROPS.stroke,
|
|
1934
|
+
fillOpacity: COMPARE_LINE_PROPS.strokeOpacity,
|
|
1935
|
+
radius: [4, 4, 0, 0],
|
|
1936
|
+
...MARK_PROPS
|
|
1937
|
+
}
|
|
1938
|
+
)
|
|
1939
|
+
] })
|
|
1940
|
+
}
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
function StackedBarChart(props) {
|
|
1944
|
+
const { data, x, series, formatValue = formatNumber } = props;
|
|
1945
|
+
return /* @__PURE__ */ jsx(
|
|
1946
|
+
ChartContainer,
|
|
1947
|
+
{
|
|
1948
|
+
height: props.height,
|
|
1949
|
+
formatValue,
|
|
1950
|
+
label: accessibleLabel(series, props.label),
|
|
1951
|
+
className: props.className,
|
|
1952
|
+
children: /* @__PURE__ */ jsxs(BarChart$1, { data, children: [
|
|
1953
|
+
/* @__PURE__ */ jsx(CartesianFrame, { x, formatValue }),
|
|
1954
|
+
ChartTooltip,
|
|
1955
|
+
showsLegend(props) && ChartLegend,
|
|
1956
|
+
series.map((entry, index) => /* @__PURE__ */ jsx(
|
|
1957
|
+
Bar,
|
|
1958
|
+
{
|
|
1959
|
+
dataKey: entry.key,
|
|
1960
|
+
name: entry.label,
|
|
1961
|
+
stackId: "stack",
|
|
1962
|
+
fill: colorAt(entry.colorIndex ?? index),
|
|
1963
|
+
radius: 0,
|
|
1964
|
+
...MARK_PROPS
|
|
1965
|
+
},
|
|
1966
|
+
entry.key
|
|
1967
|
+
))
|
|
1968
|
+
] })
|
|
1969
|
+
}
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
function LineChart(props) {
|
|
1973
|
+
const { data, x, series, compareSeries, formatValue = formatNumber } = props;
|
|
1974
|
+
return /* @__PURE__ */ jsx(
|
|
1975
|
+
ChartContainer,
|
|
1976
|
+
{
|
|
1977
|
+
height: props.height,
|
|
1978
|
+
formatValue,
|
|
1979
|
+
label: accessibleLabel(series, props.label),
|
|
1980
|
+
className: props.className,
|
|
1981
|
+
children: /* @__PURE__ */ jsxs(LineChart$1, { data, children: [
|
|
1982
|
+
/* @__PURE__ */ jsx(CartesianFrame, { x, formatValue }),
|
|
1983
|
+
ChartTooltip,
|
|
1984
|
+
showsLegend(props) && ChartLegend,
|
|
1985
|
+
series.map((entry, index) => /* @__PURE__ */ jsx(
|
|
1986
|
+
Line,
|
|
1987
|
+
{
|
|
1988
|
+
type: "monotone",
|
|
1989
|
+
dataKey: entry.key,
|
|
1990
|
+
name: entry.label,
|
|
1991
|
+
stroke: colorAt(entry.colorIndex ?? index),
|
|
1992
|
+
strokeWidth: 2,
|
|
1993
|
+
dot: false,
|
|
1994
|
+
...MARK_PROPS
|
|
1995
|
+
},
|
|
1996
|
+
entry.key
|
|
1997
|
+
)),
|
|
1998
|
+
compareSeries && /* @__PURE__ */ jsx(
|
|
1999
|
+
Line,
|
|
2000
|
+
{
|
|
2001
|
+
type: "monotone",
|
|
2002
|
+
dataKey: compareSeries.key,
|
|
2003
|
+
name: compareSeries.label,
|
|
2004
|
+
...COMPARE_LINE_PROPS
|
|
2005
|
+
}
|
|
2006
|
+
)
|
|
2007
|
+
] })
|
|
2008
|
+
}
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
function AreaChart(props) {
|
|
2012
|
+
const { data, x, series, compareSeries, formatValue = formatNumber } = props;
|
|
2013
|
+
return /* @__PURE__ */ jsx(
|
|
2014
|
+
ChartContainer,
|
|
2015
|
+
{
|
|
2016
|
+
height: props.height,
|
|
2017
|
+
formatValue,
|
|
2018
|
+
label: accessibleLabel(series, props.label),
|
|
2019
|
+
className: props.className,
|
|
2020
|
+
children: /* @__PURE__ */ jsxs(AreaChart$1, { data, children: [
|
|
2021
|
+
/* @__PURE__ */ jsx(CartesianFrame, { x, formatValue }),
|
|
2022
|
+
ChartTooltip,
|
|
2023
|
+
showsLegend(props) && ChartLegend,
|
|
2024
|
+
series.map((entry, index) => {
|
|
2025
|
+
const color = colorAt(entry.colorIndex ?? index);
|
|
2026
|
+
return /* @__PURE__ */ jsx(
|
|
2027
|
+
Area,
|
|
2028
|
+
{
|
|
2029
|
+
type: "monotone",
|
|
2030
|
+
dataKey: entry.key,
|
|
2031
|
+
name: entry.label,
|
|
2032
|
+
stroke: color,
|
|
2033
|
+
strokeWidth: 2,
|
|
2034
|
+
fill: color,
|
|
2035
|
+
fillOpacity: 0.15,
|
|
2036
|
+
...MARK_PROPS
|
|
2037
|
+
},
|
|
2038
|
+
entry.key
|
|
2039
|
+
);
|
|
2040
|
+
}),
|
|
2041
|
+
compareSeries && /* @__PURE__ */ jsx(
|
|
2042
|
+
Area,
|
|
2043
|
+
{
|
|
2044
|
+
type: "monotone",
|
|
2045
|
+
dataKey: compareSeries.key,
|
|
2046
|
+
name: compareSeries.label,
|
|
2047
|
+
fill: "none",
|
|
2048
|
+
...COMPARE_LINE_PROPS
|
|
2049
|
+
}
|
|
2050
|
+
)
|
|
2051
|
+
] })
|
|
854
2052
|
}
|
|
855
2053
|
);
|
|
856
2054
|
}
|
|
2055
|
+
function FunnelChart({
|
|
2056
|
+
data,
|
|
2057
|
+
formatValue = formatNumber,
|
|
2058
|
+
height,
|
|
2059
|
+
label,
|
|
2060
|
+
className
|
|
2061
|
+
}) {
|
|
2062
|
+
return /* @__PURE__ */ jsx(
|
|
2063
|
+
ChartContainer,
|
|
2064
|
+
{
|
|
2065
|
+
height,
|
|
2066
|
+
formatValue,
|
|
2067
|
+
label: label ?? data.map((step) => step.label).join(", "),
|
|
2068
|
+
className,
|
|
2069
|
+
children: /* @__PURE__ */ jsxs(FunnelChart$1, { children: [
|
|
2070
|
+
ChartTooltip,
|
|
2071
|
+
/* @__PURE__ */ jsxs(Funnel, { dataKey: "value", nameKey: "label", data, ...MARK_PROPS, children: [
|
|
2072
|
+
data.map((step, index) => /* @__PURE__ */ jsx(Cell, { fill: colorAt(index) }, step.label)),
|
|
2073
|
+
/* @__PURE__ */ jsx(
|
|
2074
|
+
LabelList,
|
|
2075
|
+
{
|
|
2076
|
+
dataKey: "label",
|
|
2077
|
+
position: "right",
|
|
2078
|
+
className: "fill-foreground",
|
|
2079
|
+
stroke: "none",
|
|
2080
|
+
fontSize: 12
|
|
2081
|
+
}
|
|
2082
|
+
)
|
|
2083
|
+
] })
|
|
2084
|
+
] })
|
|
2085
|
+
}
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
function relativeDelta(value, compareValue) {
|
|
2089
|
+
if (compareValue === void 0 || compareValue === 0) return null;
|
|
2090
|
+
return (value - compareValue) / Math.abs(compareValue);
|
|
2091
|
+
}
|
|
2092
|
+
var DELTA_FORMAT = new Intl.NumberFormat("fr-FR", {
|
|
2093
|
+
style: "percent",
|
|
2094
|
+
maximumFractionDigits: 1,
|
|
2095
|
+
signDisplay: "exceptZero"
|
|
2096
|
+
});
|
|
2097
|
+
function KpiCard({
|
|
2098
|
+
label,
|
|
2099
|
+
value,
|
|
2100
|
+
compareValue,
|
|
2101
|
+
formatValue = formatNumber,
|
|
2102
|
+
lowerIsBetter = false,
|
|
2103
|
+
sparkline,
|
|
2104
|
+
className
|
|
2105
|
+
}) {
|
|
2106
|
+
const delta = relativeDelta(value, compareValue);
|
|
2107
|
+
const improved = delta === null ? null : delta > 0 !== lowerIsBetter;
|
|
2108
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(CardContent, { className: "space-y-1 p-4", children: [
|
|
2109
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: label }),
|
|
2110
|
+
/* @__PURE__ */ jsx("p", { className: "text-2xl font-semibold tabular-nums text-foreground", children: formatValue(value) }),
|
|
2111
|
+
delta !== null && /* @__PURE__ */ jsxs(
|
|
2112
|
+
"p",
|
|
2113
|
+
{
|
|
2114
|
+
className: cn(
|
|
2115
|
+
"text-xs font-medium tabular-nums",
|
|
2116
|
+
// Direction is in the sign as well as the colour: a red figure and
|
|
2117
|
+
// a green one differ by more than hue for nobody who cannot see it.
|
|
2118
|
+
improved ? "text-status-success" : "text-status-blocked"
|
|
2119
|
+
),
|
|
2120
|
+
children: [
|
|
2121
|
+
DELTA_FORMAT.format(delta),
|
|
2122
|
+
compareValue !== void 0 && /* @__PURE__ */ jsxs("span", { className: "ml-1 font-normal text-muted-foreground", children: [
|
|
2123
|
+
"vs ",
|
|
2124
|
+
formatValue(compareValue)
|
|
2125
|
+
] })
|
|
2126
|
+
]
|
|
2127
|
+
}
|
|
2128
|
+
),
|
|
2129
|
+
sparkline && sparkline.length > 1 && /* @__PURE__ */ jsx(ChartContainer, { height: 40, label: `Tendance \u2014 ${label}`, className: "pt-1", children: /* @__PURE__ */ jsx(LineChart$1, { data: sparkline.map((point, index) => ({ index, point })), children: /* @__PURE__ */ jsx(
|
|
2130
|
+
Line,
|
|
2131
|
+
{
|
|
2132
|
+
type: "monotone",
|
|
2133
|
+
dataKey: "point",
|
|
2134
|
+
stroke: colorAt(0),
|
|
2135
|
+
strokeWidth: 1.5,
|
|
2136
|
+
dot: false,
|
|
2137
|
+
...MARK_PROPS
|
|
2138
|
+
}
|
|
2139
|
+
) }) })
|
|
2140
|
+
] }) });
|
|
2141
|
+
}
|
|
2142
|
+
var ToastContext = createContext(null);
|
|
2143
|
+
function useToast() {
|
|
2144
|
+
const toast = useContext(ToastContext);
|
|
2145
|
+
if (!toast) throw new Error("useToast must be used within ToastProvider");
|
|
2146
|
+
return toast;
|
|
2147
|
+
}
|
|
2148
|
+
var TOAST_TTL_MS = 4e3;
|
|
2149
|
+
var TONE = {
|
|
2150
|
+
success: "border-l-status-success",
|
|
2151
|
+
error: "border-l-status-blocked",
|
|
2152
|
+
info: "border-l-status-info"
|
|
2153
|
+
};
|
|
2154
|
+
function ToastProvider({ children }) {
|
|
2155
|
+
const [toasts, setToasts] = useState([]);
|
|
2156
|
+
const nextId = useRef(0);
|
|
2157
|
+
const toast = useCallback((input) => {
|
|
2158
|
+
const id = nextId.current++;
|
|
2159
|
+
setToasts((prev) => [...prev, { id, ...input }]);
|
|
2160
|
+
setTimeout(() => setToasts((prev) => prev.filter((t) => t.id !== id)), TOAST_TTL_MS);
|
|
2161
|
+
}, []);
|
|
2162
|
+
return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: toast, children: [
|
|
2163
|
+
children,
|
|
2164
|
+
/* @__PURE__ */ 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__ */ jsxs(
|
|
2165
|
+
"div",
|
|
2166
|
+
{
|
|
2167
|
+
role: "status",
|
|
2168
|
+
className: cn(
|
|
2169
|
+
"pointer-events-auto rounded-lg border border-l-4 bg-card px-4 py-3 text-foreground shadow-md",
|
|
2170
|
+
TONE[t.variant ?? "success"]
|
|
2171
|
+
),
|
|
2172
|
+
children: [
|
|
2173
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t.title }),
|
|
2174
|
+
t.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 break-all text-xs text-muted-foreground", children: t.description })
|
|
2175
|
+
]
|
|
2176
|
+
},
|
|
2177
|
+
t.id
|
|
2178
|
+
)) })
|
|
2179
|
+
] });
|
|
2180
|
+
}
|
|
2181
|
+
var SIDES = {
|
|
2182
|
+
top: "bottom-full left-1/2 -translate-x-1/2 mb-1.5",
|
|
2183
|
+
right: "left-full top-1/2 -translate-y-1/2 ml-1.5",
|
|
2184
|
+
bottom: "top-full left-1/2 -translate-x-1/2 mt-1.5",
|
|
2185
|
+
left: "right-full top-1/2 -translate-y-1/2 mr-1.5"
|
|
2186
|
+
};
|
|
2187
|
+
function Tooltip2({ label, children, side = "top", className }) {
|
|
2188
|
+
return /* @__PURE__ */ jsxs("span", { className: "group/tt relative inline-flex", children: [
|
|
2189
|
+
children,
|
|
2190
|
+
/* @__PURE__ */ jsx(
|
|
2191
|
+
"span",
|
|
2192
|
+
{
|
|
2193
|
+
role: "tooltip",
|
|
2194
|
+
className: cn(
|
|
2195
|
+
"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",
|
|
2196
|
+
SIDES[side],
|
|
2197
|
+
className
|
|
2198
|
+
),
|
|
2199
|
+
children: label
|
|
2200
|
+
}
|
|
2201
|
+
)
|
|
2202
|
+
] });
|
|
2203
|
+
}
|
|
857
2204
|
|
|
858
|
-
export { AppApiProvider, AppContextProvider, AppInvokeError, Badge, Button, Card, CardContent, CardHeader, CardTitle, Checkbox, CopyButton, DateRangePicker, DropdownMenu, Input, Link, Modal, Popover, Select, SidePanel, Spinner, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, cn, copyToClipboard, downloadBlob, downloadCsv, toCsv, useAppApi, useAppContext, useAppRun, useAppRuns, useAppSettings };
|
|
2205
|
+
export { AppApiProvider, AppContextProvider, AppInvokeError, AreaChart, Badge, BarChart, Button, COMPARE_LABELS, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CopyButton, DataTable, DateRangeCompare, DateRangePicker, DropdownMenu, FunnelChart, Input, KpiCard, LineChart, Link, Modal, PERIOD_PRESETS, Popover, Select, SidePanel, Spinner, StackedBarChart, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ToastProvider, Tooltip2 as Tooltip, badgeVariants, buttonVariants, cn, comparisonRange, copyToClipboard, downloadBlob, downloadCsv, formatPeriod, formatRange, makePeriod, periodFromPreset, relativeDelta, toCsv, useAppApi, useAppContext, useAppRun, useAppRuns, useAppSettings, useToast };
|
|
859
2206
|
//# sourceMappingURL=index.js.map
|
|
860
2207
|
//# sourceMappingURL=index.js.map
|