@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.
@@ -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-card text-card-foreground shadow', className), ...props })));
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 bg-popover p-4 text-popover-foreground 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), ...props }) })));
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-lg', {
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-card border border-border text-foreground',
41
- 'light-orange': 'bg-orange-50 border border-orange-100 text-foreground',
42
- 'light-teal': 'bg-teal-50 border border-teal-100 text-foreground',
43
- 'light-green': 'bg-emerald-50 border border-emerald-100 text-foreground',
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 bg-primary px-3 py-1.5 text-xs text-primary-foreground', '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), ...props }) })));
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
- * showDateFilter: true,
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 = true, dateFilterOptions = DEFAULT_DATE_FILTER_OPTIONS, dateFilter, onDateFilterChange, showRefresh = true, onRefresh, loading = false, actions, className, }) => {
46
- return (_jsxs("div", { className: cn('mb-6 flex items-center justify-between', 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 items-center gap-3", children: [showDateFilter && (_jsx("select", { value: dateFilter, onChange: (e) => onDateFilterChange(e.target.value), className: "rounded-lg border border-border bg-card px-4 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary", children: dateFilterOptions.map((option) => (_jsx("option", { 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] })] }));
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-lg:hover {
4327
- --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
4328
- --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
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.227",
4
+ "version": "0.8.229",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false