Skip to content

Commit f100b80

Browse files
committed
chore(ui): refactor filter modals across pages for mobile support
1 parent 27bdf92 commit f100b80

27 files changed

Lines changed: 383 additions & 341 deletions

File tree

ui/dashboard/src/@locales/ja/common.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,10 @@
384384
"all": "<b>all:</b> すべての評価結果を返し、SDK キャッシュ全体を更新します。"
385385
}
386386
}
387-
}
388-
},
389-
"load-more": "もっと読み込む"
387+
}
388+
},
389+
"detail": "詳細",
390+
"last-seen": "最終アクセス日時",
391+
"time-stop": "終了日時",
392+
"time-start": "開始日時"
390393
}

ui/dashboard/src/app/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export const Root = memo(() => {
166166
return (
167167
<div className="flex flex-col sm:flex-row w-full h-full">
168168
<div className="flex fixed z-20 top-0 left-0 gap-3 items-center justify-between sm:hidden w-full h-[50px] px-4 bg-primary-400">
169-
<img src={logo} alt="Bucketer" />
169+
<img src={logo} alt="Bucketeer" />
170170
{!showMenu && (
171171
<Button
172172
className="bg-transparent hover:bg-transparent p-0"

ui/dashboard/src/components/modal/dialog.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ const DialogModal = ({
3333
className,
3434
overlayCls
3535
}: ModalProps) => {
36+
// Outside interactions are already blocked via onPointerDownOutside /
37+
// onInteractOutside, so any close reaching here (Escape, Dialog.Close) is allowed
3638
const onOpenChange = useCallback(
3739
(v: boolean) => {
38-
if (v === false && closeOnClickOutside) onClose();
40+
if (v === false) onClose();
3941
},
40-
[closeOnClickOutside]
42+
[onClose]
4143
);
4244

4345
return (

ui/dashboard/src/components/modal/slide.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ const SlideModal = ({
2525
children,
2626
shouldCloseOnOverlayClick = false
2727
}: SliderProps) => {
28+
// Overlay clicks are already blocked via onPointerDownOutside /
29+
// onInteractOutside, so any close reaching here (Escape, Dialog.Close) is allowed
2830
const onOpenChange = useCallback(
2931
(v: boolean) => {
30-
if (v === false && shouldCloseOnOverlayClick) onClose();
32+
if (v === false) onClose();
3133
},
32-
[shouldCloseOnOverlayClick]
34+
[onClose]
3335
);
3436

3537
return (

ui/dashboard/src/hooks/use-options.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ const useOptions = () => {
365365
label: translation('environment'),
366366
value: 'ENVIRONMENT'
367367
},
368+
{
369+
label: translation('last-used-at', 'table'),
370+
value: 'LAST_USED_AT'
371+
},
368372
{
369373
label: translation('created-at', 'table'),
370374
value: 'CREATED_AT'
@@ -413,9 +417,17 @@ const useOptions = () => {
413417
label: translation('role'),
414418
value: 'ORGANIZATION_ROLE'
415419
},
420+
{
421+
label: translation('teams'),
422+
value: 'TEAMS'
423+
},
416424
{
417425
label: translation('last-seen'),
418426
value: 'LAST_SEEN'
427+
},
428+
{
429+
label: translation('state'),
430+
value: 'STATE'
419431
}
420432
],
421433
[language]

ui/dashboard/src/pages/api-keys/api-key-modal/filter-api-key-modal/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useScreen } from 'hooks';
22
import { APIKeysFilters } from 'pages/api-keys/types';
3-
import FilterProjectSlide from 'pages/projects/project-modal/filter-project-modal/slide-filter';
43
import FilterAPIKeyPopup from './popup-filter';
4+
import FilterAPIKeySlide from './slide-filter';
55

66
export type FilterProps = {
77
onSubmit: (v: Partial<APIKeysFilters>) => void;
@@ -33,7 +33,7 @@ const FilterAPIKeyModal = ({
3333
filters={filters}
3434
/>
3535
) : (
36-
<FilterProjectSlide
36+
<FilterAPIKeySlide
3737
isOpen={isOpen}
3838
onClearFilters={onClearFilters}
3939
onClose={onClose}

ui/dashboard/src/pages/api-keys/api-key-modal/filter-api-key-modal/slide-filter.tsx

Lines changed: 125 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ButtonBar } from 'components/button-bar';
88
import Divider from 'components/divider';
99
import Dropdown, { DropdownOption, DropdownValue } from 'components/dropdown';
1010
import Icon from 'components/icon';
11-
import DialogModal from 'components/modal/dialog';
11+
import SlideModal from 'components/modal/slide';
1212
import useFilterAPIKeyLogic from './use-filter-api-key-logic';
1313

1414
export type FilterProps = {
@@ -46,125 +46,136 @@ const FilterAPIKeySlide = ({
4646
isDisabledSubmitButton
4747
} = useFilterAPIKeyLogic(onSubmit, filters);
4848
return (
49-
<DialogModal
50-
className="w-[750px]"
51-
title={t('filters')}
52-
isOpen={isOpen}
53-
onClose={onClose}
54-
>
55-
<div className="flex flex-col w-full items-start p-5 gap-y-4">
56-
{selectedFilters.map((filterOption, filterIndex) => {
57-
const { label, value: filterType } = filterOption;
58-
const isEnvironmentFilter =
59-
filterType === FilterTypes.ENVIRONMENT_IDs;
60-
const valueOptions = getValueOptions(filterOption);
61-
return (
62-
<div
63-
className="flex items-center w-full h-12 gap-x-4"
64-
key={filterIndex}
65-
>
49+
<SlideModal title={t('filters')} isOpen={isOpen} onClose={onClose}>
50+
<div className="w-full h-full min-w-0 flex flex-col justify-between relative">
51+
<div className="flex flex-col min-w-0 w-full items-start p-5 gap-y-4">
52+
{selectedFilters.map((filterOption, filterIndex) => {
53+
const { label, value: filterType } = filterOption;
54+
const isEnvironmentFilter =
55+
filterType === FilterTypes.ENVIRONMENT_IDs;
56+
const valueOptions = getValueOptions(filterOption);
57+
58+
return (
6659
<div
67-
className={cn(
68-
'typo-para-small text-center py-[3px] w-[42px] min-w-[42px] rounded text-accent-pink-500 bg-accent-pink-50',
69-
{
70-
'bg-gray-200 text-gray-600': filterIndex !== 0
71-
}
72-
)}
60+
className="flex min-w-0 items-start w-full h-[100px] gap-x-3"
61+
key={filterIndex}
7362
>
74-
{t(filterIndex === 0 ? `if` : 'and')}
63+
<div className="h-full flex flex-col gap-y-4 items-center justify-center">
64+
<div
65+
className={cn(
66+
'mt-2 typo-para-small text-center py-[3px] w-[42px] min-w-[42px] rounded text-accent-pink-500 bg-accent-pink-50',
67+
{
68+
'bg-gray-200 text-gray-600': filterIndex !== 0
69+
}
70+
)}
71+
>
72+
{t(filterIndex === 0 ? `if` : 'and')}
73+
</div>
74+
<Divider vertical={true} className="border-primary-500" />
75+
</div>
76+
<div className="flex flex-col w-full min-w-0">
77+
<Dropdown
78+
placeholder={t(`select-filter`)}
79+
labelCustom={label}
80+
className="w-full truncate py-2"
81+
options={remainingFilterOptions.map(item => ({
82+
...item,
83+
value: item.value || '',
84+
label: item.label
85+
}))}
86+
value={filterType}
87+
onChange={val =>
88+
handleChangeOption(val as DropdownValue, filterIndex)
89+
}
90+
contentClassName="w-[270px]"
91+
/>
92+
<div className="flex min-w-0 items-center gap-3 mt-3 pl-3">
93+
<p className="typo-para-medium text-gray-600">{t('is')}</p>
94+
<Dropdown
95+
isSearchable={isEnvironmentFilter}
96+
disabled={
97+
(isEnvironmentFilter && isLoadingEnvironments) ||
98+
!filterType
99+
}
100+
loading={isEnvironmentFilter && isLoadingEnvironments}
101+
placeholder={t(`select-value`)}
102+
labelCustom={handleGetLabelFilterValue(filterOption)}
103+
className="w-full truncate py-2"
104+
options={valueOptions as DropdownOption[]}
105+
multiselect={isEnvironmentFilter}
106+
value={
107+
isEnvironmentFilter
108+
? (filterOption.filterValue as string[])
109+
: (filterOption.filterValue as string)
110+
}
111+
onChange={val => {
112+
handleChangeFilterValue(
113+
val as string | number,
114+
filterIndex
115+
);
116+
}}
117+
contentClassName={cn('w-[235px]', {
118+
'pt-0 w-[300px]': isEnvironmentFilter,
119+
'hidden-scroll': valueOptions?.length > 15
120+
})}
121+
/>
122+
<Button
123+
variant={'grey'}
124+
className="px-0 w-fit"
125+
disabled={selectedFilters.length <= 1}
126+
onClick={() =>
127+
setSelectedFilters(
128+
selectedFilters.filter(
129+
(_, index) => filterIndex !== index
130+
)
131+
)
132+
}
133+
>
134+
<Icon icon={IconTrash} size={'sm'} />
135+
</Button>
136+
</div>
137+
</div>
75138
</div>
76-
<Divider vertical={true} className="border-primary-500" />
77-
<Dropdown
78-
placeholder={t(`select-filter`)}
79-
labelCustom={label}
80-
className="w-full truncate"
81-
options={remainingFilterOptions.map(item => ({
82-
...item,
83-
value: item.value || '',
84-
label: item.label
85-
}))}
86-
value={filterType}
87-
onChange={val =>
88-
handleChangeOption(val as DropdownValue, filterIndex)
139+
);
140+
})}
141+
<Button
142+
disabled={isDisabledAddButton}
143+
variant={'text'}
144+
className="h-6 px-0"
145+
onClick={() => {
146+
setSelectedFilters([
147+
...selectedFilters,
148+
{
149+
label: '',
150+
value: undefined,
151+
filterValue: ''
89152
}
90-
contentClassName="w-[270px]"
91-
/>
92-
93-
<p className="typo-para-medium text-gray-600">{t('is')}</p>
94-
95-
<Dropdown
96-
isSearchable={isEnvironmentFilter}
97-
disabled={
98-
(isEnvironmentFilter && isLoadingEnvironments) || !filterType
99-
}
100-
loading={isEnvironmentFilter && isLoadingEnvironments}
101-
placeholder={t(`select-value`)}
102-
labelCustom={handleGetLabelFilterValue(filterOption)}
103-
className="w-full truncate"
104-
options={valueOptions as DropdownOption[]}
105-
multiselect={isEnvironmentFilter}
106-
value={
107-
isEnvironmentFilter
108-
? (filterOption.filterValue as string[])
109-
: (filterOption.filterValue as string)
110-
}
111-
onChange={val => {
112-
handleChangeFilterValue(val as string | number, filterIndex);
113-
}}
114-
contentClassName={cn('w-[235px]', {
115-
'pt-0 w-[300px]': isEnvironmentFilter,
116-
'hidden-scroll': valueOptions?.length > 15
117-
})}
118-
/>
153+
]);
154+
}}
155+
>
156+
<Icon icon={IconPlus} />
157+
{t('add-filter')}
158+
</Button>
159+
</div>
119160

120-
<Button
121-
variant={'grey'}
122-
className="px-0 w-fit"
123-
disabled={selectedFilters.length <= 1}
124-
onClick={() =>
125-
setSelectedFilters(
126-
selectedFilters.filter((_, index) => filterIndex !== index)
127-
)
128-
}
129-
>
130-
<Icon icon={IconTrash} size={'sm'} />
131-
</Button>
132-
</div>
133-
);
134-
})}
135-
<Button
136-
disabled={isDisabledAddButton}
137-
variant={'text'}
138-
className="h-6 px-0"
139-
onClick={() => {
140-
setSelectedFilters([
141-
...selectedFilters,
142-
{
143-
label: '',
144-
value: undefined,
145-
filterValue: ''
146-
}
147-
]);
148-
}}
149-
>
150-
<Icon icon={IconPlus} />
151-
{t('add-filter')}
152-
</Button>
161+
<ButtonBar
162+
className="sticky bottom-0 left-0 bg-white"
163+
secondaryButton={
164+
<Button
165+
disabled={isDisabledSubmitButton}
166+
onClick={onConfirmHandler}
167+
>
168+
{t(`confirm`)}
169+
</Button>
170+
}
171+
primaryButton={
172+
<Button onClick={onClearFilters} variant="secondary">
173+
{t(`clear`)}
174+
</Button>
175+
}
176+
/>
153177
</div>
154-
155-
<ButtonBar
156-
secondaryButton={
157-
<Button disabled={isDisabledSubmitButton} onClick={onConfirmHandler}>
158-
{t(`confirm`)}
159-
</Button>
160-
}
161-
primaryButton={
162-
<Button onClick={onClearFilters} variant="secondary">
163-
{t(`clear`)}
164-
</Button>
165-
}
166-
/>
167-
</DialogModal>
178+
</SlideModal>
168179
);
169180
};
170181

0 commit comments

Comments
 (0)