@pagamio/frontend-commons-lib 0.8.194 → 0.8.196
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/FilterComponent.d.ts +17 -13
- package/lib/components/ui/FilterComponent.js +49 -1
- package/lib/components/ui/RangeDatePicker.d.ts +0 -23
- package/lib/components/ui/RangeDatePicker.js +26 -2
- package/lib/dashboard-visuals/components/types.d.ts +4 -0
- package/lib/dashboard-visuals/index.d.ts +1 -1
- package/lib/dashboard-visuals/index.js +34 -3
- package/lib/pagamio-table/data-table/TableToolbar.js +1 -0
- package/lib/pagamio-table/data-table/types.d.ts +6 -2
- package/lib/styles.css +8 -0
- package/package.json +1 -1
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
import type { ChangeEvent } from 'react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}>;
|
|
12
|
-
dateValue?: Date | null;
|
|
13
|
-
onDateChange?: (date: Date) => void;
|
|
3
|
+
type FilterValue = string | string[] | Date | null | undefined;
|
|
4
|
+
interface FilterDefinition {
|
|
5
|
+
name: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
type?: 'select' | 'multi-select' | 'date' | 'date-range';
|
|
8
|
+
options?: Array<{
|
|
9
|
+
label: string;
|
|
10
|
+
value: string;
|
|
14
11
|
}>;
|
|
12
|
+
rangeKeys?: {
|
|
13
|
+
start: string;
|
|
14
|
+
end: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
interface FilterComponentProps {
|
|
18
|
+
filters: FilterDefinition[];
|
|
15
19
|
searctInputPlaceHolder?: string;
|
|
16
20
|
showApplyFilterButton?: boolean;
|
|
17
21
|
showClearFilters: boolean;
|
|
18
22
|
showApplyFilters?: boolean;
|
|
19
|
-
selectedFilters: Record<string,
|
|
23
|
+
selectedFilters: Record<string, FilterValue>;
|
|
20
24
|
showSearch?: boolean;
|
|
21
25
|
searchQuery?: string;
|
|
22
26
|
isNarrow: boolean;
|
|
23
27
|
children?: React.ReactNode;
|
|
24
|
-
handleFilterChange: (name: string, value:
|
|
28
|
+
handleFilterChange: (name: string, value: FilterValue) => void;
|
|
25
29
|
handleApplyFilters: () => void;
|
|
26
30
|
resetFilters: () => void;
|
|
27
31
|
onSearch?: (e: ChangeEvent<HTMLInputElement>) => void;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Input, MultiSelect, Select } from '@mantine/core';
|
|
3
|
-
import { IconSearch } from '@tabler/icons-react';
|
|
3
|
+
import { IconCalendar, IconSearch } from '@tabler/icons-react';
|
|
4
|
+
import { format } from 'date-fns';
|
|
5
|
+
import React from 'react';
|
|
4
6
|
import { isDefaultFilterValue } from '../../shared/utils/filterUtils';
|
|
5
7
|
import Button from './Button';
|
|
6
8
|
import DatePicker from './DatePicker';
|
|
7
9
|
import FilterWrapper from './FilterWrapper';
|
|
10
|
+
import { Popover, PopoverContent, PopoverTrigger } from './Popover';
|
|
11
|
+
import RangeDatePicker from './RangeDatePicker';
|
|
8
12
|
const sharedStyles = {
|
|
9
13
|
input: {
|
|
10
14
|
borderRadius: '6px',
|
|
@@ -33,8 +37,19 @@ const commonProps = {
|
|
|
33
37
|
style: { width: 240 },
|
|
34
38
|
};
|
|
35
39
|
const FilterComponent = ({ filters, selectedFilters, showApplyFilterButton = true, showClearFilters, searctInputPlaceHolder = 'Search...', showApplyFilters = true, showSearch = false, searchQuery = '', children, isNarrow, handleFilterChange, handleApplyFilters, resetFilters, onSearch = () => { }, }) => {
|
|
40
|
+
const [activeRangePicker, setActiveRangePicker] = React.useState(null);
|
|
36
41
|
const hasSelectedActiveFilters = Object.entries(selectedFilters).some(([key, v]) => !isDefaultFilterValue(v, key));
|
|
37
42
|
const shouldShowActionButtons = hasSelectedActiveFilters || (showSearch && searchQuery.length > 0);
|
|
43
|
+
const parseDateValue = (value) => {
|
|
44
|
+
if (!value)
|
|
45
|
+
return null;
|
|
46
|
+
if (value instanceof Date)
|
|
47
|
+
return value;
|
|
48
|
+
if (Array.isArray(value))
|
|
49
|
+
return null;
|
|
50
|
+
const parsed = new Date(value);
|
|
51
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
52
|
+
};
|
|
38
53
|
// Keyboard handler for search input
|
|
39
54
|
const handleSearchKeyDown = (e) => {
|
|
40
55
|
if (e.key === 'Enter') {
|
|
@@ -59,6 +74,39 @@ const FilterComponent = ({ filters, selectedFilters, showApplyFilterButton = tru
|
|
|
59
74
|
const { name, type, options } = filter;
|
|
60
75
|
const value = selectedFilters[name];
|
|
61
76
|
const renderFilterInput = () => {
|
|
77
|
+
if (type === 'date-range') {
|
|
78
|
+
const rangeKeys = filter.rangeKeys;
|
|
79
|
+
if (!rangeKeys) {
|
|
80
|
+
console.warn('Date range filter requires rangeKeys', filter);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const startDateValue = rangeKeys ? selectedFilters[rangeKeys.start] : undefined;
|
|
84
|
+
const endDateValue = rangeKeys ? selectedFilters[rangeKeys.end] : undefined;
|
|
85
|
+
const startDate = parseDateValue(startDateValue);
|
|
86
|
+
const endDate = parseDateValue(endDateValue);
|
|
87
|
+
const displayValue = startDate && endDate
|
|
88
|
+
? `${format(startDate, 'dd MMM yyyy')} - ${format(endDate, 'dd MMM yyyy')}`
|
|
89
|
+
: (filter.placeholder ?? 'Select date range');
|
|
90
|
+
const applyRange = (start, end) => {
|
|
91
|
+
if (!rangeKeys)
|
|
92
|
+
return;
|
|
93
|
+
handleFilterChange(rangeKeys.start, start);
|
|
94
|
+
handleFilterChange(rangeKeys.end, end);
|
|
95
|
+
};
|
|
96
|
+
const rangeSelection = {
|
|
97
|
+
startDate: startDate ?? new Date(),
|
|
98
|
+
endDate: endDate ?? startDate ?? new Date(),
|
|
99
|
+
key: name,
|
|
100
|
+
};
|
|
101
|
+
return (_jsxs(Popover, { open: activeRangePicker === name, onOpenChange: (open) => setActiveRangePicker(open ? name : null), children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "flex w-full items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 text-left text-sm text-gray-700 shadow-sm transition hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(IconCalendar, { size: 16, className: "text-gray-500" }), _jsx("span", { className: !startDate || !endDate ? 'text-gray-400' : '', children: displayValue })] }) }) }), _jsx(PopoverContent, { align: "start", className: "w-auto border border-gray-200 bg-white p-0 shadow-2xl", children: _jsxs("div", { className: "p-4", children: [_jsx(RangeDatePicker, { dateRange: rangeSelection, rangeKey: name, onChange: (updatedRange) => {
|
|
102
|
+
if (updatedRange.startDate) {
|
|
103
|
+
applyRange(updatedRange.startDate, updatedRange.endDate ?? updatedRange.startDate);
|
|
104
|
+
}
|
|
105
|
+
}, months: 2, showPreview: false }), _jsxs("div", { className: "flex justify-end gap-2 pt-3", children: [_jsx(Button, { variant: "outline-primary", type: "button", onClick: () => {
|
|
106
|
+
applyRange(null, null);
|
|
107
|
+
setActiveRangePicker(null);
|
|
108
|
+
}, className: "h-9 px-3", children: "Clear" }), _jsx(Button, { variant: "primary", type: "button", onClick: () => setActiveRangePicker(null), className: "h-9 px-4", children: "Done" })] })] }) })] }));
|
|
109
|
+
}
|
|
62
110
|
if (type === 'date') {
|
|
63
111
|
return (_jsx(DatePicker, { value: value || null, onChange: (date) => handleFilterChange(name, date), placeholder: filter.placeholder ?? `Select ${name}` }));
|
|
64
112
|
}
|
|
@@ -51,28 +51,5 @@ export interface RangeDatePickerProps {
|
|
|
51
51
|
*/
|
|
52
52
|
rangeKey?: string;
|
|
53
53
|
}
|
|
54
|
-
/**
|
|
55
|
-
* A flexible date range picker component using react-date-range
|
|
56
|
-
*
|
|
57
|
-
* @see For more customization options, refer to:
|
|
58
|
-
* - https://hypeserver.github.io/react-date-range/
|
|
59
|
-
* - https://www.npmjs.com/package/react-date-range
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```tsx
|
|
63
|
-
* const [selectedRange, setSelectedRange] = useState({
|
|
64
|
-
* startDate: startOfDay(new Date()),
|
|
65
|
-
* endDate: endOfDay(addDays(new Date(), 3)),
|
|
66
|
-
* });
|
|
67
|
-
*
|
|
68
|
-
* return (
|
|
69
|
-
* <RangeDatePicker
|
|
70
|
-
* dateRange={selectedRange}
|
|
71
|
-
* onChange={setSelectedRange}
|
|
72
|
-
* showPreview={true}
|
|
73
|
-
* />
|
|
74
|
-
* )
|
|
75
|
-
* ```
|
|
76
|
-
*/
|
|
77
54
|
declare const RangeDatePicker: React.FC<RangeDatePickerProps>;
|
|
78
55
|
export default RangeDatePicker;
|
|
@@ -4,7 +4,7 @@ import { DateRangePicker } from 'react-date-range';
|
|
|
4
4
|
import 'react-date-range/dist/styles.css';
|
|
5
5
|
// main style file
|
|
6
6
|
import 'react-date-range/dist/theme/default.css';
|
|
7
|
-
import { useEffect, useState } from 'react';
|
|
7
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
8
8
|
import { cn } from '../../helpers';
|
|
9
9
|
/**
|
|
10
10
|
* A flexible date range picker component using react-date-range
|
|
@@ -29,6 +29,25 @@ import { cn } from '../../helpers';
|
|
|
29
29
|
* )
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
+
const FALLBACK_PRIMARY = '#b45dae';
|
|
33
|
+
const getPrimaryColorFromTheme = () => {
|
|
34
|
+
if (typeof window === 'undefined') {
|
|
35
|
+
return FALLBACK_PRIMARY;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const probe = document.createElement('span');
|
|
39
|
+
probe.style.display = 'none';
|
|
40
|
+
probe.className = 'bg-primary-500';
|
|
41
|
+
document.body.appendChild(probe);
|
|
42
|
+
const computedColor = window.getComputedStyle(probe).backgroundColor;
|
|
43
|
+
document.body.removeChild(probe);
|
|
44
|
+
return computedColor || FALLBACK_PRIMARY;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.warn('Unable to infer primary color from theme; falling back.', error);
|
|
48
|
+
return FALLBACK_PRIMARY;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
32
51
|
const RangeDatePicker = ({ dateRange, onChange, minDate, maxDate, className, months = 2, direction = 'horizontal', showPreview = true, rangeKey = 'selection', }) => {
|
|
33
52
|
// Default range: today to 7 days from now
|
|
34
53
|
const defaultRange = {
|
|
@@ -37,6 +56,8 @@ const RangeDatePicker = ({ dateRange, onChange, minDate, maxDate, className, mon
|
|
|
37
56
|
key: rangeKey,
|
|
38
57
|
};
|
|
39
58
|
const [range, setRange] = useState(dateRange ?? defaultRange);
|
|
59
|
+
const [primaryColor, setPrimaryColor] = useState(FALLBACK_PRIMARY);
|
|
60
|
+
const memoizedRangeColors = useMemo(() => [primaryColor], [primaryColor]);
|
|
40
61
|
// Update internal state when dateRange prop changes
|
|
41
62
|
useEffect(() => {
|
|
42
63
|
if (dateRange) {
|
|
@@ -46,6 +67,9 @@ const RangeDatePicker = ({ dateRange, onChange, minDate, maxDate, className, mon
|
|
|
46
67
|
});
|
|
47
68
|
}
|
|
48
69
|
}, [dateRange, rangeKey]);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
setPrimaryColor(getPrimaryColorFromTheme());
|
|
72
|
+
}, []);
|
|
49
73
|
// Handle date range changes
|
|
50
74
|
const handleRangeChange = (rangesByKey) => {
|
|
51
75
|
const newRange = rangesByKey[rangeKey];
|
|
@@ -63,6 +87,6 @@ const RangeDatePicker = ({ dateRange, onChange, minDate, maxDate, className, mon
|
|
|
63
87
|
}
|
|
64
88
|
}
|
|
65
89
|
};
|
|
66
|
-
return (_jsx("div", { className: cn('range-date-picker', className), children: _jsx(DateRangePicker, { ranges: [range], onChange: handleRangeChange, moveRangeOnFirstSelection: false, months: months, direction: direction, showPreview: showPreview, showDateDisplay: true, minDate: minDate, maxDate: maxDate, rangeColors:
|
|
90
|
+
return (_jsx("div", { className: cn('range-date-picker', className), children: _jsx(DateRangePicker, { ranges: [range], onChange: handleRangeChange, moveRangeOnFirstSelection: false, months: months, direction: direction, showPreview: showPreview, showDateDisplay: true, minDate: minDate, maxDate: maxDate, rangeColors: memoizedRangeColors }) }));
|
|
67
91
|
};
|
|
68
92
|
export default RangeDatePicker;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Grid } from '@mantine/core';
|
|
3
3
|
import { Card } from 'flowbite-react';
|
|
4
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
5
5
|
import { useApi } from '../api';
|
|
6
6
|
import { FilterComponent, Tab } from '../components';
|
|
7
7
|
import { useMediaQueries } from '../shared';
|
|
@@ -32,11 +32,23 @@ const renderFilterComponent = ({ isLoading, error, filters, filterOptions, selec
|
|
|
32
32
|
if (error) {
|
|
33
33
|
return (_jsx(Card, { className: "mb-5", children: _jsx(ErrorState, { error: error, onRetry: handleRetry }) }));
|
|
34
34
|
}
|
|
35
|
+
const resolveFilterType = (filter) => {
|
|
36
|
+
if (filter.type === 'date-range')
|
|
37
|
+
return 'date-range';
|
|
38
|
+
if (filter.type === 'date')
|
|
39
|
+
return 'date';
|
|
40
|
+
if (filter.type === 'multi-select')
|
|
41
|
+
return 'multi-select';
|
|
42
|
+
if (filter.type === 'select')
|
|
43
|
+
return 'select';
|
|
44
|
+
return filter.multi ? 'multi-select' : 'select';
|
|
45
|
+
};
|
|
35
46
|
return (_jsx(FilterComponent, { filters: filters.map((filter) => ({
|
|
36
47
|
name: filter.name,
|
|
37
48
|
placeholder: filter.placeholder ?? `Select ${filter.name}`,
|
|
38
|
-
type: filter
|
|
49
|
+
type: resolveFilterType(filter),
|
|
39
50
|
options: filterOptions[filter.name] || filter.options,
|
|
51
|
+
rangeKeys: filter.rangeKeys,
|
|
40
52
|
})), showClearFilters: true, selectedFilters: selectedFilters, handleFilterChange: handleFilterChange, handleApplyFilters: handleApplyFilters, resetFilters: resetFilters, isNarrow: isNarrow }));
|
|
41
53
|
};
|
|
42
54
|
const DashboardWrapper = ({ data, showVisualHeader = true, showEventsTabbedLayout = false, handleFilterChange = () => { }, selectedFilters = {}, tourSelectedFilters = {}, resetFilters = () => { }, handleApplyFilters = () => { }, handleApplyTourFilters = () => { }, handleTourFilterChange = () => { }, }) => {
|
|
@@ -114,11 +126,30 @@ const DashboardWrapper = ({ data, showVisualHeader = true, showEventsTabbedLayou
|
|
|
114
126
|
isNarrow: isSm,
|
|
115
127
|
});
|
|
116
128
|
};
|
|
129
|
+
const dashboardFilters = useMemo(() => {
|
|
130
|
+
const baseFilters = [...config.filters];
|
|
131
|
+
const dateRangeValue = typeof selectedFilters?.dateRange === 'string' ? selectedFilters.dateRange.toUpperCase() : '';
|
|
132
|
+
if (dateRangeValue === 'CUSTOM_RANGE') {
|
|
133
|
+
baseFilters.push({
|
|
134
|
+
name: 'customDateRange',
|
|
135
|
+
label: 'Custom Date Range',
|
|
136
|
+
placeholder: 'Select date range',
|
|
137
|
+
type: 'date-range',
|
|
138
|
+
options: [],
|
|
139
|
+
multi: false,
|
|
140
|
+
rangeKeys: {
|
|
141
|
+
start: 'startDate',
|
|
142
|
+
end: 'endDate',
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return baseFilters;
|
|
147
|
+
}, [config.filters, selectedFilters?.dateRange]);
|
|
117
148
|
const renderContent = () => {
|
|
118
149
|
return renderFilterComponent({
|
|
119
150
|
isLoading: loading,
|
|
120
151
|
error,
|
|
121
|
-
filters:
|
|
152
|
+
filters: dashboardFilters,
|
|
122
153
|
filterOptions,
|
|
123
154
|
selectedFilters,
|
|
124
155
|
handleFilterChange,
|
|
@@ -11,6 +11,7 @@ const TableToolbar = ({ searchable, searchQuery, onSearch, filters = [], applied
|
|
|
11
11
|
type: filterObj.type || 'select',
|
|
12
12
|
options: filterObj.options,
|
|
13
13
|
placeholder: filterObj.placeholder,
|
|
14
|
+
rangeKeys: filterObj.rangeKeys,
|
|
14
15
|
})), searctInputPlaceHolder: searctInputPlaceHolder, showClearFilters: showClearFilters, showApplyFilterButton: showApplyFilterButton, selectedFilters: appliedFilters, handleFilterChange: (name, value) => {
|
|
15
16
|
if (value instanceof Date) {
|
|
16
17
|
handleFilter(name, value.toISOString());
|
|
@@ -154,13 +154,17 @@ export interface BaseEntity {
|
|
|
154
154
|
export interface BaseFilter {
|
|
155
155
|
columnKey: string;
|
|
156
156
|
label?: string;
|
|
157
|
-
type?: 'select' | 'multi-select' | 'date' | 'range' | 'boolean';
|
|
158
|
-
options
|
|
157
|
+
type?: 'select' | 'multi-select' | 'date' | 'date-range' | 'range' | 'boolean';
|
|
158
|
+
options?: Array<{
|
|
159
159
|
label: string;
|
|
160
160
|
value: string;
|
|
161
161
|
}>;
|
|
162
162
|
defaultValue?: string | string[];
|
|
163
163
|
placeholder?: string;
|
|
164
|
+
rangeKeys?: {
|
|
165
|
+
start: string;
|
|
166
|
+
end: string;
|
|
167
|
+
};
|
|
164
168
|
}
|
|
165
169
|
export interface SortConfig {
|
|
166
170
|
sortBy?: string;
|
package/lib/styles.css
CHANGED
|
@@ -2998,6 +2998,9 @@ input[type="range"]::-ms-fill-lower {
|
|
|
2998
2998
|
.pt-2 {
|
|
2999
2999
|
padding-top: 0.5rem;
|
|
3000
3000
|
}
|
|
3001
|
+
.pt-3 {
|
|
3002
|
+
padding-top: 0.75rem;
|
|
3003
|
+
}
|
|
3001
3004
|
.pt-4 {
|
|
3002
3005
|
padding-top: 1rem;
|
|
3003
3006
|
}
|
|
@@ -3432,6 +3435,11 @@ input[type="range"]::-ms-fill-lower {
|
|
|
3432
3435
|
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
|
3433
3436
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
3434
3437
|
}
|
|
3438
|
+
.shadow-2xl {
|
|
3439
|
+
--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
|
3440
|
+
--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);
|
|
3441
|
+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
3442
|
+
}
|
|
3435
3443
|
.shadow-lg {
|
|
3436
3444
|
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
3437
3445
|
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
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.196",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"provenance": false
|