Skip to content

Commit 929bb19

Browse files
committed
fixes3
1 parent 6b951ae commit 929bb19

File tree

6 files changed

+259
-306
lines changed

6 files changed

+259
-306
lines changed

lib/api/services/zetaChain.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const ZETA_CHAIN_API_RESOURCES = {
1616
'target_chain_id' as const,
1717
'token_symbol' as const,
1818
'hash' as const,
19+
'age' as const, // frontend only
1920
],
2021
paginated: true,
2122
},
Lines changed: 18 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,37 @@
1-
import { Flex, Text } from '@chakra-ui/react';
2-
import { isEqual } from 'es-toolkit';
3-
import type { ChangeEvent } from 'react';
4-
import React from 'react';
1+
import type { AdvancedFilterAge, AdvancedFilterParams } from 'types/api/advancedFilter';
52

6-
import { ADVANCED_FILTER_AGES, type AdvancedFilterAge, type AdvancedFilterParams } from 'types/api/advancedFilter';
7-
8-
import dayjs from 'lib/date/dayjs';
9-
import { Input } from 'toolkit/chakra/input';
10-
import { PopoverCloseTriggerWrapper } from 'toolkit/chakra/popover';
11-
import { ndash } from 'toolkit/utils/htmlEntities';
12-
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
13-
import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
14-
15-
import { getDurationFromAge } from '../lib';
3+
import BaseAgeFilter, { type DateConverter } from './BaseAgeFilter';
164

175
const FILTER_PARAM_FROM = 'age_from';
186
const FILTER_PARAM_TO = 'age_to';
197
const FILTER_PARAM_AGE = 'age';
208

21-
const defaultValue = { age: '', from: '', to: '' } as const;
22-
type AgeFromToValue = { age: AdvancedFilterAge | ''; from: string; to: string };
9+
const dateConverter: DateConverter = {
10+
toFilterValue: (date: string) => date, // Keep as ISO string
11+
fromFilterValue: (value: string | undefined) => value || '',
12+
getCurrentValue: (value: string | undefined) => value || '',
13+
};
2314

2415
type Props = {
25-
value?: AgeFromToValue;
26-
handleFilterChange: (filed: keyof AdvancedFilterParams, value?: string) => void;
16+
value?: { age: AdvancedFilterAge | ''; from: string; to: string };
17+
handleFilterChange: (field: keyof AdvancedFilterParams, value?: string) => void;
2718
columnName: string;
2819
isLoading?: boolean;
2920
onClose?: () => void;
3021
};
3122

32-
const DateInput = ({ value, onChange, placeholder, max }: { value: string; onChange: (value: string) => void; placeholder: string; max: string }) => {
33-
const [ tempValue, setTempValue ] = React.useState(value ? dayjs(value).format('YYYY-MM-DD') : '');
34-
35-
// reset
36-
React.useEffect(() => {
37-
if (!value) {
38-
setTempValue('');
39-
}
40-
}, [ value ]);
41-
42-
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
43-
setTempValue(event.target.value);
44-
onChange(event.target.value);
45-
}, [ onChange ]);
46-
23+
const AgeFilter = (props: Props) => {
4724
return (
48-
<Input
49-
value={ tempValue }
50-
onChange={ handleChange }
51-
placeholder={ placeholder }
52-
type="date"
53-
max={ max }
54-
autoComplete="off"
55-
size="sm"
25+
<BaseAgeFilter<AdvancedFilterParams>
26+
{ ...props }
27+
fieldNames={{
28+
from: FILTER_PARAM_FROM,
29+
to: FILTER_PARAM_TO,
30+
age: FILTER_PARAM_AGE,
31+
}}
32+
dateConverter={ dateConverter }
5633
/>
5734
);
5835
};
5936

60-
const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props) => {
61-
const [ currentValue, setCurrentValue ] = React.useState<AgeFromToValue>({
62-
age: value.age || '',
63-
from: value.age ? '' : (value.from || ''),
64-
to: value.age ? '' : (value.to || ''),
65-
});
66-
67-
const handleFromChange = React.useCallback((newValue: string) => {
68-
setCurrentValue(prev => ({ age: '', to: prev.to, from: newValue }));
69-
}, []);
70-
71-
const handleToChange = React.useCallback((newValue: string) => {
72-
setCurrentValue(prev => ({ age: '', from: prev.from, to: newValue }));
73-
}, []);
74-
75-
const onPresetChange = React.useCallback((age: AdvancedFilterAge) => {
76-
const from = dayjs((dayjs().valueOf() - getDurationFromAge(age))).toISOString();
77-
handleFilterChange(FILTER_PARAM_FROM, from);
78-
const to = dayjs().toISOString();
79-
handleFilterChange(FILTER_PARAM_TO, to);
80-
handleFilterChange(FILTER_PARAM_AGE, age);
81-
onClose?.();
82-
}, [ handleFilterChange, onClose ]);
83-
84-
const onReset = React.useCallback(() => setCurrentValue(defaultValue), []);
85-
86-
const onFilter = React.useCallback(() => {
87-
if (!currentValue.age && !currentValue.to && !currentValue.from) {
88-
handleFilterChange(FILTER_PARAM_FROM, undefined);
89-
handleFilterChange(FILTER_PARAM_TO, undefined);
90-
handleFilterChange(FILTER_PARAM_AGE, undefined);
91-
return;
92-
}
93-
const from = currentValue.age ?
94-
dayjs((dayjs().valueOf() - getDurationFromAge(currentValue.age))).toISOString() :
95-
dayjs(currentValue.from || undefined).startOf('day').toISOString();
96-
handleFilterChange(FILTER_PARAM_FROM, from);
97-
const to = currentValue.age ? dayjs().toISOString() : dayjs(currentValue.to || undefined).endOf('day').toISOString();
98-
handleFilterChange(FILTER_PARAM_TO, to);
99-
handleFilterChange(FILTER_PARAM_AGE, currentValue.age);
100-
}, [ handleFilterChange, currentValue ]);
101-
102-
return (
103-
<TableColumnFilter
104-
title="Set last duration"
105-
isFilled={ Boolean(currentValue.from || currentValue.to || currentValue.age) }
106-
isTouched={ currentValue.age ? value.age !== currentValue.age : !isEqual(currentValue, value) }
107-
onFilter={ onFilter }
108-
onReset={ onReset }
109-
hasReset
110-
>
111-
<Flex gap={ 3 }>
112-
<PopoverCloseTriggerWrapper>
113-
<TagGroupSelect<AdvancedFilterAge>
114-
items={ ADVANCED_FILTER_AGES.map(val => ({ id: val, title: val })) }
115-
onChange={ onPresetChange }
116-
value={ currentValue.age || undefined }
117-
/>
118-
</PopoverCloseTriggerWrapper>
119-
</Flex>
120-
<Flex mt={ 3 }>
121-
<DateInput
122-
value={ currentValue.age ? '' : currentValue.from }
123-
onChange={ handleFromChange }
124-
placeholder="From"
125-
max={ dayjs().format('YYYY-MM-DD') }
126-
/>
127-
<Text mx={ 3 }>{ ndash }</Text>
128-
<DateInput
129-
value={ currentValue.age ? '' : currentValue.to }
130-
onChange={ handleToChange }
131-
placeholder="To"
132-
max={ dayjs().format('YYYY-MM-DD') }
133-
/>
134-
</Flex>
135-
</TableColumnFilter>
136-
);
137-
};
138-
13937
export default AgeFilter;
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import { Flex, Text } from '@chakra-ui/react';
2+
import { isEqual } from 'es-toolkit';
3+
import type { ChangeEvent } from 'react';
4+
import React from 'react';
5+
6+
import { ADVANCED_FILTER_AGES, type AdvancedFilterAge } from 'types/api/advancedFilter';
7+
8+
import dayjs from 'lib/date/dayjs';
9+
import { Input } from 'toolkit/chakra/input';
10+
import { PopoverCloseTriggerWrapper } from 'toolkit/chakra/popover';
11+
import { ndash } from 'toolkit/utils/htmlEntities';
12+
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
13+
import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
14+
15+
import { getDurationFromAge } from '../lib';
16+
17+
const defaultValue = { age: '', from: '', to: '' } as const;
18+
type AgeFromToValue = { age: AdvancedFilterAge | ''; from: string; to: string };
19+
20+
type DateConverter = {
21+
toFilterValue: (date: string) => string;
22+
fromFilterValue: (value: string | undefined) => string;
23+
getCurrentValue: (value: string | undefined) => string;
24+
};
25+
26+
type Props<T> = {
27+
value?: AgeFromToValue;
28+
handleFilterChange: (field: keyof T, value?: string) => void;
29+
columnName: string;
30+
isLoading?: boolean;
31+
onClose?: () => void;
32+
fieldNames: {
33+
from: keyof T;
34+
to: keyof T;
35+
age: keyof T;
36+
};
37+
dateConverter: DateConverter;
38+
};
39+
40+
const DateInput = ({ value, onChange, placeholder, max }: { value: string; onChange: (value: string) => void; placeholder: string; max: string }) => {
41+
const [ tempValue, setTempValue ] = React.useState(value || '');
42+
43+
// reset
44+
React.useEffect(() => {
45+
if (!value) {
46+
setTempValue('');
47+
}
48+
}, [ value ]);
49+
50+
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
51+
setTempValue(event.target.value);
52+
onChange(event.target.value);
53+
}, [ onChange ]);
54+
55+
return (
56+
<Input
57+
value={ tempValue }
58+
onChange={ handleChange }
59+
placeholder={ placeholder }
60+
type="date"
61+
max={ max }
62+
autoComplete="off"
63+
size="sm"
64+
/>
65+
);
66+
};
67+
68+
function BaseAgeFilter<T>({
69+
value = defaultValue,
70+
handleFilterChange,
71+
onClose,
72+
fieldNames,
73+
dateConverter,
74+
}: Props<T>) {
75+
const getFromValue = () => {
76+
if (value.age) return '';
77+
return value.from ? dateConverter.getCurrentValue(value.from) : '';
78+
};
79+
80+
const getToValue = () => {
81+
if (value.age) return '';
82+
return value.to ? dateConverter.getCurrentValue(value.to) : '';
83+
};
84+
85+
const [ currentValue, setCurrentValue ] = React.useState<AgeFromToValue>({
86+
age: value.age || '',
87+
from: getFromValue(),
88+
to: getToValue(),
89+
});
90+
91+
const handleFromChange = React.useCallback((newValue: string) => {
92+
setCurrentValue(prev => ({ age: '', to: prev.to, from: newValue }));
93+
}, []);
94+
95+
const handleToChange = React.useCallback((newValue: string) => {
96+
setCurrentValue(prev => ({ age: '', from: prev.from, to: newValue }));
97+
}, []);
98+
99+
const onPresetChange = React.useCallback((age: AdvancedFilterAge) => {
100+
const from = dateConverter.toFilterValue(dayjs((dayjs().valueOf() - getDurationFromAge(age))).toISOString());
101+
handleFilterChange(fieldNames.from, from);
102+
const to = dateConverter.toFilterValue(dayjs().toISOString());
103+
handleFilterChange(fieldNames.to, to);
104+
handleFilterChange(fieldNames.age, age);
105+
onClose?.();
106+
}, [ handleFilterChange, onClose, fieldNames, dateConverter ]);
107+
108+
const onReset = React.useCallback(() => {
109+
setCurrentValue(defaultValue);
110+
}, []);
111+
112+
const onFilter = React.useCallback(() => {
113+
if (!currentValue.age && !currentValue.to && !currentValue.from) {
114+
handleFilterChange(fieldNames.from, undefined);
115+
handleFilterChange(fieldNames.to, undefined);
116+
handleFilterChange(fieldNames.age, undefined);
117+
return;
118+
}
119+
120+
if (currentValue.age) {
121+
// Age preset is selected, calculate timestamps
122+
const from = dateConverter.toFilterValue(dayjs((dayjs().valueOf() - getDurationFromAge(currentValue.age))).toISOString());
123+
const to = dateConverter.toFilterValue(dayjs().toISOString());
124+
handleFilterChange(fieldNames.from, from);
125+
handleFilterChange(fieldNames.to, to);
126+
handleFilterChange(fieldNames.age, currentValue.age);
127+
} else {
128+
// Custom date range is selected
129+
const from = currentValue.from ? dateConverter.toFilterValue(dayjs(currentValue.from).startOf('day').toISOString()) : undefined;
130+
const to = currentValue.to ? dateConverter.toFilterValue(dayjs(currentValue.to).endOf('day').toISOString()) : undefined;
131+
handleFilterChange(fieldNames.from, from);
132+
handleFilterChange(fieldNames.to, to);
133+
handleFilterChange(fieldNames.age, undefined);
134+
}
135+
}, [ handleFilterChange, currentValue, fieldNames, dateConverter ]);
136+
137+
// Check if the current values differ from the original values
138+
const isTouched = React.useMemo(() => {
139+
if (currentValue.age) {
140+
return value.age !== currentValue.age;
141+
}
142+
143+
// If both current values are empty and both original values are empty, not touched
144+
if (!currentValue.from && !currentValue.to && !value.from && !value.to) {
145+
return false;
146+
}
147+
148+
// Convert original values to date strings for comparison
149+
const originalValueAsDates = {
150+
from: value.from ? dateConverter.getCurrentValue(value.from) : '',
151+
to: value.to ? dateConverter.getCurrentValue(value.to) : '',
152+
};
153+
154+
// Compare the date strings directly
155+
return !isEqual(currentValue, originalValueAsDates);
156+
}, [ currentValue, value, dateConverter ]);
157+
158+
return (
159+
<TableColumnFilter
160+
title="Set last duration"
161+
isFilled={ Boolean(currentValue.from || currentValue.to || currentValue.age) }
162+
isTouched={ isTouched }
163+
onFilter={ onFilter }
164+
onReset={ onReset }
165+
hasReset
166+
>
167+
<Flex gap={ 3 }>
168+
<PopoverCloseTriggerWrapper>
169+
<TagGroupSelect<AdvancedFilterAge>
170+
items={ ADVANCED_FILTER_AGES.map(val => ({ id: val, title: val })) }
171+
onChange={ onPresetChange }
172+
value={ currentValue.age || undefined }
173+
/>
174+
</PopoverCloseTriggerWrapper>
175+
</Flex>
176+
<Flex mt={ 3 }>
177+
<DateInput
178+
value={ currentValue.age ? '' : currentValue.from }
179+
onChange={ handleFromChange }
180+
placeholder="From"
181+
max={ dayjs().format('YYYY-MM-DD') }
182+
/>
183+
<Text mx={ 3 }>{ ndash }</Text>
184+
<DateInput
185+
value={ currentValue.age ? '' : currentValue.to }
186+
onChange={ handleToChange }
187+
placeholder="To"
188+
max={ dayjs().format('YYYY-MM-DD') }
189+
/>
190+
</Flex>
191+
</TableColumnFilter>
192+
);
193+
}
194+
195+
export default BaseAgeFilter;
196+
export type { Props, DateConverter };

0 commit comments

Comments
 (0)