@pagamio/frontend-commons-lib 0.8.195 → 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.
@@ -1,27 +1,31 @@
1
1
  import type { ChangeEvent } from 'react';
2
2
  import React from 'react';
3
- interface FilterComponentProps {
4
- filters: Array<{
5
- name: string;
6
- placeholder?: string;
7
- type: 'select' | 'multi-select' | 'date';
8
- options?: Array<{
9
- label: string;
10
- value: string;
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, string | string[] | Date | null>;
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: string | string[] | Date | null) => void;
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: ['#b45dae'] }) }));
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;
@@ -48,6 +48,10 @@ export interface FilterConfig {
48
48
  tourUrl?: string;
49
49
  query?: any;
50
50
  multi?: boolean;
51
+ rangeKeys?: {
52
+ start: string;
53
+ end: string;
54
+ };
51
55
  }
52
56
  export interface Visual {
53
57
  type: string;
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { FilterConfig } from './components/types';
3
- type FilterValue = string | string[] | Date | null;
3
+ type FilterValue = string | string[] | Date | null | undefined;
4
4
  interface Config {
5
5
  title: string;
6
6
  summary: string;
@@ -33,6 +33,8 @@ const renderFilterComponent = ({ isLoading, error, filters, filterOptions, selec
33
33
  return (_jsx(Card, { className: "mb-5", children: _jsx(ErrorState, { error: error, onRetry: handleRetry }) }));
34
34
  }
35
35
  const resolveFilterType = (filter) => {
36
+ if (filter.type === 'date-range')
37
+ return 'date-range';
36
38
  if (filter.type === 'date')
37
39
  return 'date';
38
40
  if (filter.type === 'multi-select')
@@ -46,6 +48,7 @@ const renderFilterComponent = ({ isLoading, error, filters, filterOptions, selec
46
48
  placeholder: filter.placeholder ?? `Select ${filter.name}`,
47
49
  type: resolveFilterType(filter),
48
50
  options: filterOptions[filter.name] || filter.options,
51
+ rangeKeys: filter.rangeKeys,
49
52
  })), showClearFilters: true, selectedFilters: selectedFilters, handleFilterChange: handleFilterChange, handleApplyFilters: handleApplyFilters, resetFilters: resetFilters, isNarrow: isNarrow }));
50
53
  };
51
54
  const DashboardWrapper = ({ data, showVisualHeader = true, showEventsTabbedLayout = false, handleFilterChange = () => { }, selectedFilters = {}, tourSelectedFilters = {}, resetFilters = () => { }, handleApplyFilters = () => { }, handleApplyTourFilters = () => { }, handleTourFilterChange = () => { }, }) => {
@@ -128,19 +131,16 @@ const DashboardWrapper = ({ data, showVisualHeader = true, showEventsTabbedLayou
128
131
  const dateRangeValue = typeof selectedFilters?.dateRange === 'string' ? selectedFilters.dateRange.toUpperCase() : '';
129
132
  if (dateRangeValue === 'CUSTOM_RANGE') {
130
133
  baseFilters.push({
131
- name: 'startDate',
132
- label: 'Start Date',
133
- placeholder: 'Select start date',
134
- type: 'date',
135
- options: [],
136
- multi: false,
137
- }, {
138
- name: 'endDate',
139
- label: 'End Date',
140
- placeholder: 'Select end date',
141
- type: 'date',
134
+ name: 'customDateRange',
135
+ label: 'Custom Date Range',
136
+ placeholder: 'Select date range',
137
+ type: 'date-range',
142
138
  options: [],
143
139
  multi: false,
140
+ rangeKeys: {
141
+ start: 'startDate',
142
+ end: 'endDate',
143
+ },
144
144
  });
145
145
  }
146
146
  return baseFilters;
@@ -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: Array<{
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.195",
4
+ "version": "0.8.196",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false