@pagamio/frontend-commons-lib 0.8.227 → 0.8.229
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/lib/components/ui/Card.js +1 -1
- package/lib/components/ui/DateRangePickerWithPresets.d.ts +57 -0
- package/lib/components/ui/DateRangePickerWithPresets.js +158 -0
- package/lib/components/ui/Popover.js +5 -1
- package/lib/components/ui/StatCard.js +5 -5
- package/lib/components/ui/Tooltip.js +4 -1
- package/lib/components/ui/index.d.ts +1 -0
- package/lib/components/ui/index.js +1 -0
- package/lib/dashboard-visuals/v2/components/DashboardStatCardRow.js +2 -2
- package/lib/dashboard-visuals/v2/components/DashboardWrapperV2.d.ts +11 -1
- package/lib/dashboard-visuals/v2/components/DashboardWrapperV2.js +16 -5
- package/lib/dashboard-visuals/v2/types/index.d.ts +48 -3
- package/lib/styles.css +31 -18
- package/package.json +1 -1
|
@@ -23,7 +23,7 @@ import { cn } from '../../helpers/utils';
|
|
|
23
23
|
* </Card>
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
|
-
const Card = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn('rounded-xl border bg-
|
|
26
|
+
const Card = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn('rounded-xl border bg-white dark:bg-gray-800 text-card-foreground shadow-sm transition-shadow hover:shadow', className), ...props })));
|
|
27
27
|
Card.displayName = 'Card';
|
|
28
28
|
const CardHeader = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn('flex flex-col space-y-1.5 p-6', className), ...props })));
|
|
29
29
|
CardHeader.displayName = 'CardHeader';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { DateRange } from 'react-day-picker';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
export type DatePreset = {
|
|
4
|
+
/** Display label for the preset */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Unique identifier for the preset */
|
|
7
|
+
value: string;
|
|
8
|
+
/** Function to compute the date range for this preset */
|
|
9
|
+
getRange: () => DateRange;
|
|
10
|
+
};
|
|
11
|
+
export interface DateRangePickerWithPresetsProps {
|
|
12
|
+
/** The currently selected date range */
|
|
13
|
+
value?: DateRange;
|
|
14
|
+
/** Callback when date range changes */
|
|
15
|
+
onChange?: (range: DateRange | undefined, presetValue?: string) => void;
|
|
16
|
+
/** Custom preset options (defaults to standard presets) */
|
|
17
|
+
presets?: DatePreset[];
|
|
18
|
+
/** Currently selected preset value */
|
|
19
|
+
selectedPreset?: string;
|
|
20
|
+
/** Placeholder text when no date is selected */
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
/** Minimum selectable date */
|
|
23
|
+
minDate?: Date;
|
|
24
|
+
/** Maximum selectable date */
|
|
25
|
+
maxDate?: Date;
|
|
26
|
+
/** Number of months to display in the calendar */
|
|
27
|
+
numberOfMonths?: number;
|
|
28
|
+
/** Additional CSS classes */
|
|
29
|
+
className?: string;
|
|
30
|
+
/** Whether to show the clear button */
|
|
31
|
+
showClearButton?: boolean;
|
|
32
|
+
/** Format string for displaying dates */
|
|
33
|
+
dateFormat?: string;
|
|
34
|
+
/** Disable the picker */
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare const defaultDatePresets: DatePreset[];
|
|
38
|
+
/**
|
|
39
|
+
* A date range picker with preset options and a dual-month calendar view.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* const [dateRange, setDateRange] = useState<DateRange | undefined>();
|
|
44
|
+
* const [preset, setPreset] = useState('last_7_days');
|
|
45
|
+
*
|
|
46
|
+
* <DateRangePickerWithPresets
|
|
47
|
+
* value={dateRange}
|
|
48
|
+
* selectedPreset={preset}
|
|
49
|
+
* onChange={(range, presetValue) => {
|
|
50
|
+
* setDateRange(range);
|
|
51
|
+
* if (presetValue) setPreset(presetValue);
|
|
52
|
+
* }}
|
|
53
|
+
* />
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare const DateRangePickerWithPresets: React.FC<DateRangePickerWithPresetsProps>;
|
|
57
|
+
export default DateRangePickerWithPresets;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { endOfDay, endOfMonth, format, startOfDay, startOfMonth, subDays, subMonths, } from 'date-fns';
|
|
3
|
+
import { CalendarIcon, X } from 'lucide-react';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { useMemo, useState } from 'react';
|
|
6
|
+
import { cn } from '../../helpers';
|
|
7
|
+
import Button from './Button';
|
|
8
|
+
import Calendar from './Calendar';
|
|
9
|
+
import { Popover, PopoverContent, PopoverTrigger } from './Popover';
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// DEFAULT PRESETS
|
|
12
|
+
// =============================================================================
|
|
13
|
+
export const defaultDatePresets = [
|
|
14
|
+
{
|
|
15
|
+
label: 'Today',
|
|
16
|
+
value: 'today',
|
|
17
|
+
getRange: () => ({
|
|
18
|
+
from: startOfDay(new Date()),
|
|
19
|
+
to: endOfDay(new Date()),
|
|
20
|
+
}),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
label: 'Yesterday',
|
|
24
|
+
value: 'yesterday',
|
|
25
|
+
getRange: () => ({
|
|
26
|
+
from: startOfDay(subDays(new Date(), 1)),
|
|
27
|
+
to: endOfDay(subDays(new Date(), 1)),
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
label: 'Last 7 Days',
|
|
32
|
+
value: 'last_7_days',
|
|
33
|
+
getRange: () => ({
|
|
34
|
+
from: startOfDay(subDays(new Date(), 6)),
|
|
35
|
+
to: endOfDay(new Date()),
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
label: 'Last 30 Days',
|
|
40
|
+
value: 'last_30_days',
|
|
41
|
+
getRange: () => ({
|
|
42
|
+
from: startOfDay(subDays(new Date(), 29)),
|
|
43
|
+
to: endOfDay(new Date()),
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: 'This Month',
|
|
48
|
+
value: 'this_month',
|
|
49
|
+
getRange: () => ({
|
|
50
|
+
from: startOfMonth(new Date()),
|
|
51
|
+
to: endOfDay(new Date()),
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: 'Last Month',
|
|
56
|
+
value: 'last_month',
|
|
57
|
+
getRange: () => ({
|
|
58
|
+
from: startOfMonth(subMonths(new Date(), 1)),
|
|
59
|
+
to: endOfMonth(subMonths(new Date(), 1)),
|
|
60
|
+
}),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
label: 'Custom Range',
|
|
64
|
+
value: 'custom',
|
|
65
|
+
getRange: () => ({
|
|
66
|
+
from: startOfDay(subDays(new Date(), 6)),
|
|
67
|
+
to: endOfDay(new Date()),
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// COMPONENT
|
|
73
|
+
// =============================================================================
|
|
74
|
+
/**
|
|
75
|
+
* A date range picker with preset options and a dual-month calendar view.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* const [dateRange, setDateRange] = useState<DateRange | undefined>();
|
|
80
|
+
* const [preset, setPreset] = useState('last_7_days');
|
|
81
|
+
*
|
|
82
|
+
* <DateRangePickerWithPresets
|
|
83
|
+
* value={dateRange}
|
|
84
|
+
* selectedPreset={preset}
|
|
85
|
+
* onChange={(range, presetValue) => {
|
|
86
|
+
* setDateRange(range);
|
|
87
|
+
* if (presetValue) setPreset(presetValue);
|
|
88
|
+
* }}
|
|
89
|
+
* />
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
const DateRangePickerWithPresets = ({ value, onChange, presets = defaultDatePresets, selectedPreset = 'last_7_days', placeholder = 'Select date range', minDate, maxDate = new Date(), numberOfMonths = 2, className, showClearButton = true, dateFormat = 'MM/dd/yyyy', disabled = false, }) => {
|
|
93
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
94
|
+
const [tempRange, setTempRange] = useState(value);
|
|
95
|
+
const [tempPreset, setTempPreset] = useState(selectedPreset);
|
|
96
|
+
// Initialize temp state when popover opens
|
|
97
|
+
React.useEffect(() => {
|
|
98
|
+
if (isOpen) {
|
|
99
|
+
setTempRange(value);
|
|
100
|
+
setTempPreset(selectedPreset);
|
|
101
|
+
}
|
|
102
|
+
}, [isOpen, value, selectedPreset]);
|
|
103
|
+
// Format the display text
|
|
104
|
+
const displayText = useMemo(() => {
|
|
105
|
+
if (!value?.from)
|
|
106
|
+
return placeholder;
|
|
107
|
+
if (value.to) {
|
|
108
|
+
return `${format(value.from, dateFormat)} - ${format(value.to, dateFormat)}`;
|
|
109
|
+
}
|
|
110
|
+
return format(value.from, dateFormat);
|
|
111
|
+
}, [value, dateFormat, placeholder]);
|
|
112
|
+
// Handle preset selection
|
|
113
|
+
const handlePresetSelect = (preset) => {
|
|
114
|
+
const range = preset.getRange();
|
|
115
|
+
setTempRange(range);
|
|
116
|
+
setTempPreset(preset.value);
|
|
117
|
+
// For non-custom presets, immediately apply and close
|
|
118
|
+
if (preset.value !== 'custom') {
|
|
119
|
+
onChange?.(range, preset.value);
|
|
120
|
+
setIsOpen(false);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
// Handle calendar selection
|
|
124
|
+
const handleCalendarSelect = (range) => {
|
|
125
|
+
setTempRange(range);
|
|
126
|
+
setTempPreset('custom'); // Switch to custom when manually selecting
|
|
127
|
+
};
|
|
128
|
+
// Handle apply
|
|
129
|
+
const handleApply = () => {
|
|
130
|
+
onChange?.(tempRange, tempPreset);
|
|
131
|
+
setIsOpen(false);
|
|
132
|
+
};
|
|
133
|
+
// Handle cancel
|
|
134
|
+
const handleCancel = () => {
|
|
135
|
+
setTempRange(value);
|
|
136
|
+
setTempPreset(selectedPreset);
|
|
137
|
+
setIsOpen(false);
|
|
138
|
+
};
|
|
139
|
+
// Handle clear from trigger button
|
|
140
|
+
const handleClear = (e) => {
|
|
141
|
+
e.stopPropagation();
|
|
142
|
+
onChange?.(undefined, undefined);
|
|
143
|
+
};
|
|
144
|
+
return (_jsxs(Popover, { open: isOpen, onOpenChange: setIsOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", disabled: disabled, className: cn('w-auto justify-start text-left font-normal', !value && 'text-muted-foreground', className), children: [_jsx(CalendarIcon, { className: "mr-2 h-4 w-4" }), _jsx("span", { children: displayText }), showClearButton && value?.from && (_jsx(X, { className: "ml-2 h-4 w-4 opacity-50 hover:opacity-100 cursor-pointer", onClick: handleClear }))] }) }), _jsx(PopoverContent, { className: "w-auto p-0 bg-background border border-border shadow-lg", align: "start", sideOffset: 8, children: _jsxs("div", { className: "flex", children: [_jsx("div", { className: "border-r border-border p-2 min-w-[140px] bg-background", children: _jsx("div", { className: "flex flex-col gap-1", children: presets.map((preset) => (_jsx("button", { type: "button", onClick: () => handlePresetSelect(preset), className: cn('text-left px-3 py-2 text-sm rounded-md transition-colors', 'hover:bg-accent hover:text-accent-foreground', tempPreset === preset.value &&
|
|
145
|
+
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground'), children: preset.label }, preset.value))) }) }), tempPreset === 'custom' && (_jsxs("div", { className: "p-3 bg-background", children: [_jsx(Calendar, { mode: "range", defaultMonth: tempRange?.from, selected: tempRange, onSelect: handleCalendarSelect, numberOfMonths: numberOfMonths, disabled: (date) => {
|
|
146
|
+
if (minDate && date < minDate)
|
|
147
|
+
return true;
|
|
148
|
+
if (maxDate && date > maxDate)
|
|
149
|
+
return true;
|
|
150
|
+
return false;
|
|
151
|
+
}, initialFocus: true }), _jsxs("div", { className: "flex items-center justify-between border-t border-border pt-3 mt-3", children: [_jsx("div", { className: "text-sm text-muted-foreground", children: tempRange?.from && tempRange?.to
|
|
152
|
+
? `${format(tempRange.from, dateFormat)} - ${format(tempRange.to, dateFormat)}`
|
|
153
|
+
: tempRange?.from
|
|
154
|
+
? format(tempRange.from, dateFormat)
|
|
155
|
+
: 'Select a range' }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: handleCancel, children: "Cancel" }), _jsx(Button, { size: "sm", onClick: handleApply, disabled: !tempRange?.from, children: "Apply" })] })] })] }))] }) })] }));
|
|
156
|
+
};
|
|
157
|
+
DateRangePickerWithPresets.displayName = 'DateRangePickerWithPresets';
|
|
158
|
+
export default DateRangePickerWithPresets;
|
|
@@ -5,6 +5,10 @@ import { cn } from '../../helpers';
|
|
|
5
5
|
const Popover = PopoverPrimitive.Root;
|
|
6
6
|
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
7
7
|
const PopoverAnchor = PopoverPrimitive.Anchor;
|
|
8
|
-
const PopoverContent = React.forwardRef(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { ref: ref, align: align, sideOffset: sideOffset, className: cn('z-50 w-72 rounded-md border
|
|
8
|
+
const PopoverContent = React.forwardRef(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { ref: ref, align: align, sideOffset: sideOffset, className: cn('z-50 w-72 rounded-md border p-4 shadow-md outline-none', 'data-[state=open]:animate-in data-[state=closed]:animate-out', 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95', 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2', 'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', className), style: {
|
|
9
|
+
backgroundColor: 'var(--popover, hsl(0 0% 100%))',
|
|
10
|
+
color: 'var(--popover-foreground, hsl(240 10% 3.9%))',
|
|
11
|
+
borderColor: 'var(--border, hsl(240 5.9% 90%))',
|
|
12
|
+
}, ...props }) })));
|
|
9
13
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
|
10
14
|
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
|
@@ -27,7 +27,7 @@ import { Skeleton } from './Skeleton';
|
|
|
27
27
|
// =============================================================================
|
|
28
28
|
// VARIANTS
|
|
29
29
|
// =============================================================================
|
|
30
|
-
const statCardVariants = cva('relative overflow-hidden rounded-xl transition-all duration-200 hover:shadow-
|
|
30
|
+
const statCardVariants = cva('relative overflow-hidden rounded-xl transition-all duration-200 hover:shadow-sm', {
|
|
31
31
|
variants: {
|
|
32
32
|
variant: {
|
|
33
33
|
// Solid colored variants (top row in DreamPOS)
|
|
@@ -37,10 +37,10 @@ const statCardVariants = cva('relative overflow-hidden rounded-xl transition-all
|
|
|
37
37
|
dark: 'bg-gradient-to-r from-gray-800 to-gray-900 text-white',
|
|
38
38
|
primary: 'bg-gradient-to-r from-primary to-primary/90 text-white',
|
|
39
39
|
// Light variants (bottom row in DreamPOS)
|
|
40
|
-
light: 'bg-
|
|
41
|
-
'light-orange': 'bg-
|
|
42
|
-
'light-teal': 'bg-
|
|
43
|
-
'light-green': 'bg-
|
|
40
|
+
light: 'bg-white dark:bg-gray-800 border border-border text-foreground',
|
|
41
|
+
'light-orange': 'bg-white dark:bg-gray-800 border border-orange-100 dark:border-orange-800 text-foreground',
|
|
42
|
+
'light-teal': 'bg-white dark:bg-gray-800 border border-teal-100 dark:border-teal-800 text-foreground',
|
|
43
|
+
'light-green': 'bg-white dark:bg-gray-800 border border-emerald-100 dark:border-emerald-800 text-foreground',
|
|
44
44
|
},
|
|
45
45
|
size: {
|
|
46
46
|
sm: 'p-3',
|
|
@@ -6,6 +6,9 @@ import { cn } from '../../helpers/utils';
|
|
|
6
6
|
const TooltipProvider = TooltipPrimitive.Provider;
|
|
7
7
|
const Tooltip = TooltipPrimitive.Root;
|
|
8
8
|
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
9
|
-
const TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (_jsx(TooltipPrimitive.Portal, { children: _jsx(TooltipPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn('z-50 overflow-hidden rounded-md
|
|
9
|
+
const TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (_jsx(TooltipPrimitive.Portal, { children: _jsx(TooltipPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn('z-50 overflow-hidden rounded-md px-3 py-1.5 text-xs shadow-md', 'animate-in fade-in-0 zoom-in-95', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95', 'data-[side=bottom]:slide-in-from-top-2', 'data-[side=left]:slide-in-from-right-2', 'data-[side=right]:slide-in-from-left-2', 'data-[side=top]:slide-in-from-bottom-2', className), style: {
|
|
10
|
+
backgroundColor: 'var(--primary, hsl(305 38% 54%))',
|
|
11
|
+
color: 'var(--primary-foreground, hsl(0 0% 100%))',
|
|
12
|
+
}, ...props }) })));
|
|
10
13
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
11
14
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
@@ -23,6 +23,7 @@ export { default as DatePicker } from './DatePicker';
|
|
|
23
23
|
export { default as DateFormat } from './DateFormat';
|
|
24
24
|
export { default as DateRangeModal } from './DateRangeModal';
|
|
25
25
|
export { default as RangeDatePicker } from './RangeDatePicker';
|
|
26
|
+
export { default as DateRangePickerWithPresets, defaultDatePresets, type DatePreset, type DateRangePickerWithPresetsProps, } from './DateRangePickerWithPresets';
|
|
26
27
|
export { default as DetailsCard, type DataItem as DetailsCardDataItem } from './DetailsCard';
|
|
27
28
|
export { default as DetailsPage, type DetailsPageProps } from './DetailsPage';
|
|
28
29
|
export { default as FilterComponent } from './FilterComponent';
|
|
@@ -25,6 +25,7 @@ export { default as DatePicker } from './DatePicker';
|
|
|
25
25
|
export { default as DateFormat } from './DateFormat';
|
|
26
26
|
export { default as DateRangeModal } from './DateRangeModal';
|
|
27
27
|
export { default as RangeDatePicker } from './RangeDatePicker';
|
|
28
|
+
export { default as DateRangePickerWithPresets, defaultDatePresets, } from './DateRangePickerWithPresets';
|
|
28
29
|
export { default as DetailsCard } from './DetailsCard';
|
|
29
30
|
export { default as DetailsPage } from './DetailsPage';
|
|
30
31
|
export { default as FilterComponent } from './FilterComponent';
|
|
@@ -64,10 +64,10 @@ const getLgColSpanClass = (span = 3) => {
|
|
|
64
64
|
};
|
|
65
65
|
const DashboardStatCard = ({ config, loading }) => {
|
|
66
66
|
if (loading || config.loading) {
|
|
67
|
-
return _jsx(StatCardSkeleton, { variant: config.variant });
|
|
67
|
+
return (_jsx(StatCardSkeleton, { variant: config.variant, className: config.className, containerClassName: config.containerClassName }));
|
|
68
68
|
}
|
|
69
69
|
const displayValue = formatValue(config.value, config.rawValue, config.format, config.currency);
|
|
70
|
-
return (_jsx(StatCard, { variant: config.variant, title: config.title, value: displayValue, icon: config.icon, change: config.change, comparisonLabel: config.comparisonLabel, footer: config.footer }));
|
|
70
|
+
return (_jsx(StatCard, { variant: config.variant, title: config.title, value: displayValue, icon: config.icon, change: config.change, comparisonLabel: config.comparisonLabel, footer: config.footer, className: config.className, containerClassName: config.containerClassName }));
|
|
71
71
|
};
|
|
72
72
|
const DashboardStatCardRow = ({ config, loading }) => {
|
|
73
73
|
const gapClass = config.gap ? `gap-${config.gap}` : 'gap-4';
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { DateRange } from 'react-day-picker';
|
|
1
2
|
import React from 'react';
|
|
2
|
-
import { type DashboardWrapperV2Props } from '../types';
|
|
3
|
+
import { type DashboardFilterConfig, type DashboardWrapperV2Props, type DateRangeValue } from '../types';
|
|
3
4
|
interface DashboardHeaderProps {
|
|
4
5
|
title: string;
|
|
5
6
|
subtitle?: string;
|
|
7
|
+
/** Show legacy date filter select (deprecated, use useDateRangePicker) */
|
|
6
8
|
showDateFilter?: boolean;
|
|
7
9
|
dateFilterOptions?: {
|
|
8
10
|
label: string;
|
|
@@ -10,6 +12,14 @@ interface DashboardHeaderProps {
|
|
|
10
12
|
}[];
|
|
11
13
|
dateFilter: string;
|
|
12
14
|
onDateFilterChange: (value: string) => void;
|
|
15
|
+
/** Use enhanced date range picker with presets */
|
|
16
|
+
useDateRangePicker?: boolean;
|
|
17
|
+
dateRange?: DateRangeValue;
|
|
18
|
+
onDateRangeChange?: (range: DateRange | undefined, preset?: string) => void;
|
|
19
|
+
/** Additional filters */
|
|
20
|
+
filters?: DashboardFilterConfig[];
|
|
21
|
+
filterValues?: Record<string, string | string[]>;
|
|
22
|
+
onFilterChange?: (filterId: string, value: string | string[]) => void;
|
|
13
23
|
showRefresh?: boolean;
|
|
14
24
|
onRefresh?: () => void;
|
|
15
25
|
loading?: boolean;
|
|
@@ -7,6 +7,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
*
|
|
8
8
|
* Features:
|
|
9
9
|
* - Configurable header with greeting, date filter, and refresh
|
|
10
|
+
* - Enhanced date range picker with presets and calendar
|
|
11
|
+
* - Additional filter support (dropdowns)
|
|
10
12
|
* - Multiple section types (stat cards, charts, custom)
|
|
11
13
|
* - Automatic loading states
|
|
12
14
|
* - Empty state handling
|
|
@@ -18,7 +20,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
18
20
|
* header={{
|
|
19
21
|
* title: (name) => `Good Morning, ${name}`,
|
|
20
22
|
* subtitle: "Here's what's happening with your store today.",
|
|
21
|
-
*
|
|
23
|
+
* useDateRangePicker: true,
|
|
22
24
|
* showRefresh: true,
|
|
23
25
|
* }}
|
|
24
26
|
* userName="John Doe"
|
|
@@ -38,12 +40,21 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
38
40
|
*/
|
|
39
41
|
import { IconBuildingStore, IconRefresh } from '@tabler/icons-react';
|
|
40
42
|
import { useCallback, useState } from 'react';
|
|
43
|
+
import DateRangePickerWithPresets from '../../../components/ui/DateRangePickerWithPresets';
|
|
44
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../components/ui/Select';
|
|
41
45
|
import { cn } from '../../../helpers/utils';
|
|
42
46
|
import { DEFAULT_DATE_FILTER_OPTIONS, } from '../types';
|
|
43
47
|
import { DashboardChartRow } from './DashboardChartRow';
|
|
44
48
|
import { DashboardStatCardRow } from './DashboardStatCardRow';
|
|
45
|
-
const DashboardHeader = ({ title, subtitle, showDateFilter =
|
|
46
|
-
|
|
49
|
+
const DashboardHeader = ({ title, subtitle, showDateFilter = false, dateFilterOptions = DEFAULT_DATE_FILTER_OPTIONS, dateFilter, onDateFilterChange, useDateRangePicker = false, dateRange, onDateRangeChange, filters, filterValues, onFilterChange, showRefresh = true, onRefresh, loading = false, actions, className, }) => {
|
|
50
|
+
// Convert DateRangeValue to DateRange for the picker
|
|
51
|
+
const pickerValue = dateRange ? { from: dateRange.from, to: dateRange.to } : undefined;
|
|
52
|
+
return (_jsxs("div", { className: cn('mb-6 flex flex-wrap items-center justify-between gap-4', className), children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold text-foreground", children: title }), subtitle && _jsx("p", { className: "text-muted-foreground", children: subtitle })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [filters?.map((filter) => (_jsxs(Select, { value: filterValues?.[filter.id] || 'all', onValueChange: (value) => onFilterChange?.(filter.id, value === 'all' ? '' : value), children: [_jsx(SelectTrigger, { className: "w-[180px] bg-card border-border", children: _jsx(SelectValue, { placeholder: filter.placeholder ?? `All ${filter.label}` }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: filter.placeholder ?? `All ${filter.label}` }), filter.options.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value)))] })] }, filter.id))), useDateRangePicker && (_jsx(DateRangePickerWithPresets, { value: pickerValue, selectedPreset: dateFilter, onChange: (range, preset) => {
|
|
53
|
+
onDateRangeChange?.(range, preset);
|
|
54
|
+
if (preset) {
|
|
55
|
+
onDateFilterChange(preset);
|
|
56
|
+
}
|
|
57
|
+
}, maxDate: new Date() })), showDateFilter && !useDateRangePicker && (_jsxs(Select, { value: dateFilter, onValueChange: onDateFilterChange, children: [_jsx(SelectTrigger, { className: "w-[180px] bg-card border-border", children: _jsx(SelectValue, { placeholder: "Select period" }) }), _jsx(SelectContent, { children: dateFilterOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] })), showRefresh && onRefresh && (_jsxs("button", { onClick: onRefresh, disabled: loading, className: "flex items-center gap-2 rounded-lg border border-border bg-card px-4 py-2 text-sm text-foreground hover:bg-muted transition-colors disabled:opacity-50", children: [_jsx(IconRefresh, { className: cn('h-4 w-4', loading && 'animate-spin') }), "Refresh"] })), actions] })] }));
|
|
47
58
|
};
|
|
48
59
|
const DashboardEmptyState = ({ icon, title, description, action }) => {
|
|
49
60
|
return (_jsx("div", { className: "p-6", children: _jsxs("div", { className: "rounded-xl border border-border bg-card p-8 text-center", children: [icon || _jsx(IconBuildingStore, { className: "mx-auto h-12 w-12 text-muted-foreground" }), _jsx("h3", { className: "mt-4 text-lg font-semibold text-foreground", children: title }), description && _jsx("p", { className: "mt-2 text-muted-foreground", children: description }), action && _jsx("div", { className: "mt-4", children: action })] }) }));
|
|
@@ -66,7 +77,7 @@ const SectionRenderer = ({ section, loading }) => {
|
|
|
66
77
|
// =============================================================================
|
|
67
78
|
// MAIN COMPONENT
|
|
68
79
|
// =============================================================================
|
|
69
|
-
const DashboardWrapperV2 = ({ header, sections, userName = 'User', businessUnitId, dateFilter: controlledDateFilter, onDateFilterChange, onRefresh, loading = false, emptyState, className, contentClassName, }) => {
|
|
80
|
+
const DashboardWrapperV2 = ({ header, sections, userName = 'User', businessUnitId, dateFilter: controlledDateFilter, onDateFilterChange, dateRange, onDateRangeChange, filterValues, onFilterChange, onRefresh, loading = false, emptyState, className, contentClassName, }) => {
|
|
70
81
|
// Internal date filter state (uncontrolled mode)
|
|
71
82
|
const [internalDateFilter, setInternalDateFilter] = useState(header?.defaultDateFilter || 'last_7_days');
|
|
72
83
|
// Use controlled or uncontrolled date filter
|
|
@@ -92,7 +103,7 @@ const DashboardWrapperV2 = ({ header, sections, userName = 'User', businessUnitI
|
|
|
92
103
|
if (!businessUnitId && emptyState) {
|
|
93
104
|
return (_jsx(DashboardEmptyState, { icon: emptyState.icon, title: emptyState.title, description: emptyState.description, action: emptyState.action }));
|
|
94
105
|
}
|
|
95
|
-
return (_jsxs("div", { className: cn('min-h-screen bg-background p-6', className), children: [header && (_jsx(DashboardHeader, { title: resolveTitle(), subtitle: header.subtitle, showDateFilter: header.showDateFilter, dateFilterOptions: header.dateFilterOptions || DEFAULT_DATE_FILTER_OPTIONS, dateFilter: dateFilter, onDateFilterChange: handleDateFilterChange, showRefresh: header.showRefresh, onRefresh: onRefresh, loading: loading, actions: header.actions, className: header.className })), _jsx("div", { className: contentClassName, children: sections.map((section) => (_jsx(SectionRenderer, { section: section, loading: loading }, section.type === 'stat-cards'
|
|
106
|
+
return (_jsxs("div", { className: cn('min-h-screen bg-background p-6', className), children: [header && (_jsx(DashboardHeader, { title: resolveTitle(), subtitle: header.subtitle, showDateFilter: header.showDateFilter, dateFilterOptions: header.dateFilterOptions || DEFAULT_DATE_FILTER_OPTIONS, dateFilter: dateFilter, onDateFilterChange: handleDateFilterChange, useDateRangePicker: header.useDateRangePicker, dateRange: dateRange, onDateRangeChange: onDateRangeChange, filters: header.filters, filterValues: filterValues, onFilterChange: onFilterChange, showRefresh: header.showRefresh, onRefresh: onRefresh, loading: loading, actions: header.actions, className: header.className })), _jsx("div", { className: contentClassName, children: sections.map((section) => (_jsx(SectionRenderer, { section: section, loading: loading }, section.type === 'stat-cards'
|
|
96
107
|
? section.config.id
|
|
97
108
|
: section.type === 'charts'
|
|
98
109
|
? section.config.id
|
|
@@ -66,6 +66,10 @@ export interface StatCardConfig {
|
|
|
66
66
|
format?: 'currency' | 'number' | 'percentage';
|
|
67
67
|
/** Currency code for currency format */
|
|
68
68
|
currency?: string;
|
|
69
|
+
/** Custom className for card container */
|
|
70
|
+
className?: string;
|
|
71
|
+
/** Custom container className */
|
|
72
|
+
containerClassName?: string;
|
|
69
73
|
}
|
|
70
74
|
/**
|
|
71
75
|
* Configuration for a row of stat cards
|
|
@@ -245,6 +249,35 @@ export type DashboardSection = {
|
|
|
245
249
|
type: 'custom';
|
|
246
250
|
config: CustomSectionConfig;
|
|
247
251
|
};
|
|
252
|
+
/**
|
|
253
|
+
* Date range for custom range picker
|
|
254
|
+
*/
|
|
255
|
+
export interface DateRangeValue {
|
|
256
|
+
from?: Date;
|
|
257
|
+
to?: Date;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Filter option for dropdowns
|
|
261
|
+
*/
|
|
262
|
+
export interface FilterOption {
|
|
263
|
+
label: string;
|
|
264
|
+
value: string;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Dashboard filter configuration
|
|
268
|
+
*/
|
|
269
|
+
export interface DashboardFilterConfig {
|
|
270
|
+
/** Filter ID */
|
|
271
|
+
id: string;
|
|
272
|
+
/** Filter label */
|
|
273
|
+
label: string;
|
|
274
|
+
/** Filter options */
|
|
275
|
+
options: FilterOption[];
|
|
276
|
+
/** Placeholder text */
|
|
277
|
+
placeholder?: string;
|
|
278
|
+
/** Allow multiple selection */
|
|
279
|
+
multiple?: boolean;
|
|
280
|
+
}
|
|
248
281
|
/**
|
|
249
282
|
* Dashboard header configuration
|
|
250
283
|
*/
|
|
@@ -253,12 +286,16 @@ export interface DashboardHeaderConfig {
|
|
|
253
286
|
title: string | ((userName: string) => string);
|
|
254
287
|
/** Subtitle text */
|
|
255
288
|
subtitle?: string;
|
|
256
|
-
/** Show date filter dropdown */
|
|
289
|
+
/** Show date filter dropdown (legacy simple select) */
|
|
257
290
|
showDateFilter?: boolean;
|
|
258
291
|
/** Date filter options */
|
|
259
292
|
dateFilterOptions?: DateFilterOption[];
|
|
260
293
|
/** Default date filter value */
|
|
261
294
|
defaultDateFilter?: string;
|
|
295
|
+
/** Use enhanced date range picker with presets and calendar */
|
|
296
|
+
useDateRangePicker?: boolean;
|
|
297
|
+
/** Additional filter configurations */
|
|
298
|
+
filters?: DashboardFilterConfig[];
|
|
262
299
|
/** Show refresh button */
|
|
263
300
|
showRefresh?: boolean;
|
|
264
301
|
/** Custom header actions */
|
|
@@ -291,10 +328,18 @@ export interface DashboardWrapperV2Props {
|
|
|
291
328
|
userName?: string;
|
|
292
329
|
/** Business unit ID for API calls */
|
|
293
330
|
businessUnitId?: string;
|
|
294
|
-
/** Current date filter value (controlled) */
|
|
331
|
+
/** Current date filter value (controlled) - preset name like 'last_7_days' */
|
|
295
332
|
dateFilter?: string;
|
|
296
|
-
/** Date filter change handler */
|
|
333
|
+
/** Date filter change handler - receives preset name */
|
|
297
334
|
onDateFilterChange?: (value: string) => void;
|
|
335
|
+
/** Current date range value (controlled) - for custom ranges */
|
|
336
|
+
dateRange?: DateRangeValue;
|
|
337
|
+
/** Date range change handler - receives the actual dates */
|
|
338
|
+
onDateRangeChange?: (range: DateRangeValue | undefined, preset?: string) => void;
|
|
339
|
+
/** Current filter values (controlled) */
|
|
340
|
+
filterValues?: Record<string, string | string[]>;
|
|
341
|
+
/** Filter change handler */
|
|
342
|
+
onFilterChange?: (filterId: string, value: string | string[]) => void;
|
|
298
343
|
/** Refresh handler */
|
|
299
344
|
onRefresh?: () => void;
|
|
300
345
|
/** Global loading state */
|
package/lib/styles.css
CHANGED
|
@@ -1766,6 +1766,9 @@ input[type="range"]::-ms-fill-lower {
|
|
|
1766
1766
|
.w-\[16\%\] {
|
|
1767
1767
|
width: 16%;
|
|
1768
1768
|
}
|
|
1769
|
+
.w-\[180px\] {
|
|
1770
|
+
width: 180px;
|
|
1771
|
+
}
|
|
1769
1772
|
.w-\[1px\] {
|
|
1770
1773
|
width: 1px;
|
|
1771
1774
|
}
|
|
@@ -1813,6 +1816,9 @@ input[type="range"]::-ms-fill-lower {
|
|
|
1813
1816
|
.min-w-\[120px\] {
|
|
1814
1817
|
min-width: 120px;
|
|
1815
1818
|
}
|
|
1819
|
+
.min-w-\[140px\] {
|
|
1820
|
+
min-width: 140px;
|
|
1821
|
+
}
|
|
1816
1822
|
.min-w-\[150px\] {
|
|
1817
1823
|
min-width: 150px;
|
|
1818
1824
|
}
|
|
@@ -2638,10 +2644,6 @@ input[type="range"]::-ms-fill-lower {
|
|
|
2638
2644
|
--tw-bg-opacity: 1;
|
|
2639
2645
|
background-color: rgb(209 250 229 / var(--tw-bg-opacity, 1));
|
|
2640
2646
|
}
|
|
2641
|
-
.bg-emerald-50 {
|
|
2642
|
-
--tw-bg-opacity: 1;
|
|
2643
|
-
background-color: rgb(236 253 245 / var(--tw-bg-opacity, 1));
|
|
2644
|
-
}
|
|
2645
2647
|
.bg-gray-100 {
|
|
2646
2648
|
--tw-bg-opacity: 1;
|
|
2647
2649
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
|
|
@@ -2740,10 +2742,6 @@ input[type="range"]::-ms-fill-lower {
|
|
|
2740
2742
|
--tw-bg-opacity: 1;
|
|
2741
2743
|
background-color: rgb(254 236 220 / var(--tw-bg-opacity, 1));
|
|
2742
2744
|
}
|
|
2743
|
-
.bg-orange-50 {
|
|
2744
|
-
--tw-bg-opacity: 1;
|
|
2745
|
-
background-color: rgb(255 248 241 / var(--tw-bg-opacity, 1));
|
|
2746
|
-
}
|
|
2747
2745
|
.bg-pink-100 {
|
|
2748
2746
|
--tw-bg-opacity: 1;
|
|
2749
2747
|
background-color: rgb(252 232 243 / var(--tw-bg-opacity, 1));
|
|
@@ -2809,10 +2807,6 @@ input[type="range"]::-ms-fill-lower {
|
|
|
2809
2807
|
--tw-bg-opacity: 1;
|
|
2810
2808
|
background-color: rgb(213 245 246 / var(--tw-bg-opacity, 1));
|
|
2811
2809
|
}
|
|
2812
|
-
.bg-teal-50 {
|
|
2813
|
-
--tw-bg-opacity: 1;
|
|
2814
|
-
background-color: rgb(237 250 250 / var(--tw-bg-opacity, 1));
|
|
2815
|
-
}
|
|
2816
2810
|
.bg-teal-600 {
|
|
2817
2811
|
--tw-bg-opacity: 1;
|
|
2818
2812
|
background-color: rgb(4 116 129 / var(--tw-bg-opacity, 1));
|
|
@@ -3864,6 +3858,11 @@ input[type="range"]::-ms-fill-lower {
|
|
|
3864
3858
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
3865
3859
|
transition-duration: 150ms;
|
|
3866
3860
|
}
|
|
3861
|
+
.transition-shadow {
|
|
3862
|
+
transition-property: box-shadow;
|
|
3863
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
3864
|
+
transition-duration: 150ms;
|
|
3865
|
+
}
|
|
3867
3866
|
.transition-transform {
|
|
3868
3867
|
transition-property: transform;
|
|
3869
3868
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
@@ -4323,9 +4322,14 @@ input[type="range"]::-ms-fill-lower {
|
|
|
4323
4322
|
.hover\:opacity-100:hover {
|
|
4324
4323
|
opacity: 1;
|
|
4325
4324
|
}
|
|
4326
|
-
.hover\:shadow
|
|
4327
|
-
--tw-shadow: 0
|
|
4328
|
-
--tw-shadow-colored: 0
|
|
4325
|
+
.hover\:shadow:hover {
|
|
4326
|
+
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
4327
|
+
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
|
4328
|
+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
4329
|
+
}
|
|
4330
|
+
.hover\:shadow-sm:hover {
|
|
4331
|
+
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
4332
|
+
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
|
|
4329
4333
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
4330
4334
|
}
|
|
4331
4335
|
.focus\:z-10:focus {
|
|
@@ -4533,9 +4537,6 @@ input[type="range"]::-ms-fill-lower {
|
|
|
4533
4537
|
--tw-ring-opacity: 1;
|
|
4534
4538
|
--tw-ring-color: rgb(214 31 105 / var(--tw-ring-opacity, 1));
|
|
4535
4539
|
}
|
|
4536
|
-
.focus\:ring-primary:focus {
|
|
4537
|
-
--tw-ring-color: hsl(var(--primary));
|
|
4538
|
-
}
|
|
4539
4540
|
.focus\:ring-purple-200:focus {
|
|
4540
4541
|
--tw-ring-opacity: 1;
|
|
4541
4542
|
--tw-ring-color: rgb(220 215 254 / var(--tw-ring-opacity, 1));
|
|
@@ -5090,6 +5091,10 @@ input[type="range"]::-ms-fill-lower {
|
|
|
5090
5091
|
--tw-border-opacity: 1;
|
|
5091
5092
|
border-color: rgb(8 145 178 / var(--tw-border-opacity, 1));
|
|
5092
5093
|
}
|
|
5094
|
+
.dark\:border-emerald-800:is(.dark *) {
|
|
5095
|
+
--tw-border-opacity: 1;
|
|
5096
|
+
border-color: rgb(6 95 70 / var(--tw-border-opacity, 1));
|
|
5097
|
+
}
|
|
5093
5098
|
.dark\:border-gray-500:is(.dark *) {
|
|
5094
5099
|
--tw-border-opacity: 1;
|
|
5095
5100
|
border-color: rgb(107 114 128 / var(--tw-border-opacity, 1));
|
|
@@ -5130,6 +5135,10 @@ input[type="range"]::-ms-fill-lower {
|
|
|
5130
5135
|
--tw-border-opacity: 1;
|
|
5131
5136
|
border-color: rgb(101 163 13 / var(--tw-border-opacity, 1));
|
|
5132
5137
|
}
|
|
5138
|
+
.dark\:border-orange-800:is(.dark *) {
|
|
5139
|
+
--tw-border-opacity: 1;
|
|
5140
|
+
border-color: rgb(138 44 13 / var(--tw-border-opacity, 1));
|
|
5141
|
+
}
|
|
5133
5142
|
.dark\:border-pink-600:is(.dark *) {
|
|
5134
5143
|
--tw-border-opacity: 1;
|
|
5135
5144
|
border-color: rgb(214 31 105 / var(--tw-border-opacity, 1));
|
|
@@ -5150,6 +5159,10 @@ input[type="range"]::-ms-fill-lower {
|
|
|
5150
5159
|
--tw-border-opacity: 1;
|
|
5151
5160
|
border-color: rgb(4 116 129 / var(--tw-border-opacity, 1));
|
|
5152
5161
|
}
|
|
5162
|
+
.dark\:border-teal-800:is(.dark *) {
|
|
5163
|
+
--tw-border-opacity: 1;
|
|
5164
|
+
border-color: rgb(5 80 92 / var(--tw-border-opacity, 1));
|
|
5165
|
+
}
|
|
5153
5166
|
.dark\:border-white:is(.dark *) {
|
|
5154
5167
|
--tw-border-opacity: 1;
|
|
5155
5168
|
border-color: rgb(255 255 255 / var(--tw-border-opacity, 1));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagamio/frontend-commons-lib",
|
|
3
3
|
"description": "Pagamio library for Frontend reusable components like the form engine and table container",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.229",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"provenance": false
|