Skip to content

Commit 3912108

Browse files
committed
feat: add route search form with 9 filter fields
- Created SearchForm component with name, id, host, path, description, plugin, labels, version, status filters - Implemented hybrid filtering (backend for name, client-side for others) - Added client-side filtering utilities in clientSideFilter.ts - Integrated search form into routes list page - Extended pageSearch schema to support new filter parameters - Added translations for en, es, de, zh locales - Fixed clsx import warning in Editor.tsx This enables users to quickly find target routes when managing hundreds of routes in a cluster. Closes #3205
1 parent 75707c6 commit 3912108

File tree

10 files changed

+605
-6
lines changed

10 files changed

+605
-6
lines changed

src/components/form/Editor.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717
import { InputWrapper, type InputWrapperProps, Skeleton } from '@mantine/core';
1818
import { Editor } from '@monaco-editor/react';
19-
import clsx from 'clsx';
19+
import classNames from 'clsx';
2020
import { useEffect, useMemo, useRef, useState } from 'react';
2121
import {
2222
type FieldValues,
@@ -132,7 +132,7 @@ export const FormItemEditor = <T extends FieldValues>(
132132
)}
133133
<Editor
134134
wrapperProps={{
135-
className: clsx(
135+
className: classNames(
136136
'editor-wrapper',
137137
restField.disabled && 'editor-wrapper--disabled'
138138
),

src/components/form/SearchForm.tsx

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import { Button, Col, Form, Input, Row, Select, Space } from 'antd';
18+
import { useEffect, useMemo } from 'react';
19+
import { useTranslation } from 'react-i18next';
20+
21+
export type SearchFormValues = {
22+
name?: string;
23+
id?: string;
24+
host?: string;
25+
path?: string;
26+
description?: string;
27+
plugin?: string;
28+
labels?: string[];
29+
version?: string;
30+
status?: string;
31+
};
32+
33+
type Option = {
34+
label: string;
35+
value: string;
36+
};
37+
38+
type SearchFormProps = {
39+
onSearch?: (values: SearchFormValues) => void;
40+
onReset?: (values: SearchFormValues) => void;
41+
labelOptions?: Option[];
42+
versionOptions?: Option[];
43+
statusOptions?: Option[];
44+
initialValues?: Partial<SearchFormValues>;
45+
};
46+
47+
export const SearchForm = (props: SearchFormProps) => {
48+
const {
49+
onSearch,
50+
onReset,
51+
labelOptions,
52+
versionOptions,
53+
statusOptions,
54+
initialValues,
55+
} = props;
56+
57+
const { t } = useTranslation('common');
58+
const [form] = Form.useForm<SearchFormValues>();
59+
60+
const defaultStatusOptions = useMemo<Option[]>(
61+
() => [
62+
{
63+
label: t('form.searchForm.status.all'),
64+
value: 'UnPublished/Published',
65+
},
66+
{
67+
label: t('form.searchForm.status.published'),
68+
value: 'Published',
69+
},
70+
{
71+
label: t('form.searchForm.status.unpublished'),
72+
value: 'UnPublished',
73+
},
74+
],
75+
[t]
76+
);
77+
78+
const mergedStatusOptions = useMemo(
79+
() => statusOptions ?? defaultStatusOptions,
80+
[defaultStatusOptions, statusOptions]
81+
);
82+
const resolvedInitialValues = useMemo(() => {
83+
const defaultStatus = mergedStatusOptions[0]?.value ?? undefined;
84+
return {
85+
status: defaultStatus,
86+
...initialValues,
87+
} satisfies SearchFormValues;
88+
}, [initialValues, mergedStatusOptions]);
89+
90+
useEffect(() => {
91+
form.setFieldsValue(resolvedInitialValues);
92+
}, [form, resolvedInitialValues]);
93+
94+
const handleFinish = (values: SearchFormValues) => {
95+
onSearch?.(values);
96+
};
97+
98+
const handleReset = async () => {
99+
form.resetFields();
100+
form.setFieldsValue(resolvedInitialValues);
101+
const values = form.getFieldsValue();
102+
onReset?.(values);
103+
onSearch?.(values);
104+
};
105+
106+
return (
107+
<Form
108+
form={form}
109+
layout="vertical"
110+
autoComplete="off"
111+
onFinish={handleFinish}
112+
style={{
113+
padding: '20px',
114+
background: '#fafafa',
115+
borderRadius: '8px',
116+
border: '1px solid #e8e8e8',
117+
}}
118+
>
119+
<Row gutter={[16, 0]}>
120+
{/* First column - spans 2 rows */}
121+
<Col xs={24} sm={12} md={6}>
122+
<Form.Item<SearchFormValues>
123+
name="name"
124+
label={t('form.searchForm.fields.name')}
125+
style={{ marginBottom: '16px' }}
126+
>
127+
<Input
128+
placeholder={t('form.searchForm.placeholders.name')}
129+
allowClear
130+
/>
131+
</Form.Item>
132+
<Form.Item<SearchFormValues>
133+
name="id"
134+
label={t('form.searchForm.fields.id')}
135+
style={{ marginBottom: '16px' }}
136+
>
137+
<Input
138+
placeholder={t('form.searchForm.placeholders.id')}
139+
allowClear
140+
/>
141+
</Form.Item>
142+
</Col>
143+
144+
{/* Second column - first row */}
145+
<Col xs={24} sm={12} md={6}>
146+
<Form.Item<SearchFormValues>
147+
name="host"
148+
label={t('form.searchForm.fields.host')}
149+
style={{ marginBottom: '16px' }}
150+
>
151+
<Input
152+
placeholder={t('form.searchForm.placeholders.host')}
153+
allowClear
154+
/>
155+
</Form.Item>
156+
{/* Second column - second row */}
157+
<Form.Item<SearchFormValues>
158+
name="plugin"
159+
label={t('form.searchForm.fields.plugin')}
160+
style={{ marginBottom: '16px' }}
161+
>
162+
<Input
163+
placeholder={t('form.searchForm.placeholders.plugin')}
164+
allowClear
165+
/>
166+
</Form.Item>
167+
</Col>
168+
169+
{/* Third column - first row */}
170+
<Col xs={24} sm={12} md={6}>
171+
<Form.Item<SearchFormValues>
172+
name="path"
173+
label={t('form.searchForm.fields.path')}
174+
style={{ marginBottom: '16px' }}
175+
>
176+
<Input
177+
placeholder={t('form.searchForm.placeholders.path')}
178+
allowClear
179+
/>
180+
</Form.Item>
181+
{/* Third column - second row */}
182+
<Form.Item<SearchFormValues>
183+
name="labels"
184+
label={t('form.searchForm.fields.labels')}
185+
style={{ marginBottom: '16px' }}
186+
>
187+
<Select
188+
mode="multiple"
189+
placeholder={t('form.searchForm.placeholders.labels')}
190+
allowClear
191+
options={labelOptions}
192+
/>
193+
</Form.Item>
194+
</Col>
195+
196+
{/* Fourth column - first row */}
197+
<Col xs={24} sm={12} md={6}>
198+
<Form.Item<SearchFormValues>
199+
name="description"
200+
label={t('form.searchForm.fields.description')}
201+
style={{ marginBottom: '16px' }}
202+
>
203+
<Input
204+
placeholder={t('form.searchForm.placeholders.description')}
205+
allowClear
206+
/>
207+
</Form.Item>
208+
{/* Fourth column - second row */}
209+
<Form.Item<SearchFormValues>
210+
name="version"
211+
label={t('form.searchForm.fields.version')}
212+
style={{ marginBottom: '16px' }}
213+
>
214+
<Select
215+
placeholder={t('form.searchForm.placeholders.version')}
216+
allowClear
217+
options={versionOptions}
218+
/>
219+
</Form.Item>
220+
</Col>
221+
</Row>
222+
223+
{/* Third row - Status and Actions */}
224+
<Row gutter={[16, 0]} align="bottom" style={{ marginTop: '8px' }}>
225+
<Col xs={24} sm={12} md={6}>
226+
<Form.Item<SearchFormValues>
227+
name="status"
228+
label={t('form.searchForm.fields.status')}
229+
style={{ marginBottom: 0 }}
230+
>
231+
<Select
232+
placeholder={t('form.searchForm.placeholders.status')}
233+
allowClear
234+
options={mergedStatusOptions}
235+
/>
236+
</Form.Item>
237+
</Col>
238+
<Col xs={24} sm={12} md={18}>
239+
<div
240+
style={{
241+
display: 'flex',
242+
justifyContent: 'flex-end',
243+
alignItems: 'center',
244+
height: '32px',
245+
}}
246+
>
247+
<Space size="middle">
248+
<Button onClick={handleReset} size="middle">
249+
{t('form.searchForm.actions.reset')}
250+
</Button>
251+
<Button type="primary" htmlType="submit" size="middle">
252+
{t('form.searchForm.actions.search')}
253+
</Button>
254+
</Space>
255+
</div>
256+
</Col>
257+
</Row>
258+
</Form>
259+
);
260+
};
261+
262+
export default SearchForm;

src/config/i18n.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import { initReactI18next } from 'react-i18next';
1919

2020
import de_common from '@/locales/de/common.json';
2121
import en_common from '@/locales/en/common.json';
22-
import zh_common from '@/locales/zh/common.json';
2322
import es_common from '@/locales/es/common.json';
23+
import zh_common from '@/locales/zh/common.json';
2424

2525
export const resources = {
2626
en: {

src/locales/de/common.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,39 @@
8888
"vars": "Variablen"
8989
},
9090
"search": "Suche",
91+
"searchForm": {
92+
"fields": {
93+
"name": "Name",
94+
"id": "ID",
95+
"host": "Host",
96+
"path": "Pfad",
97+
"description": "Beschreibung",
98+
"plugin": "Plugin",
99+
"labels": "Labels",
100+
"version": "Version",
101+
"status": "Status"
102+
},
103+
"placeholders": {
104+
"name": "Name",
105+
"id": "ID",
106+
"host": "Host",
107+
"path": "Pfad",
108+
"description": "Beschreibung",
109+
"plugin": "Plugin",
110+
"labels": "Labels auswählen",
111+
"version": "Version auswählen",
112+
"status": "Status auswählen"
113+
},
114+
"status": {
115+
"all": "Unveröffentlicht/Veröffentlicht",
116+
"published": "Veröffentlicht",
117+
"unpublished": "Unveröffentlicht"
118+
},
119+
"actions": {
120+
"reset": "Zurücksetzen",
121+
"search": "Suchen"
122+
}
123+
},
91124
"secrets": {
92125
"aws": {
93126
"access_key_id": "Access Key ID",

src/locales/en/common.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,39 @@
8888
"vars": "Vars"
8989
},
9090
"search": "Search",
91+
"searchForm": {
92+
"fields": {
93+
"name": "Name",
94+
"id": "ID",
95+
"host": "Host",
96+
"path": "Path",
97+
"description": "Description",
98+
"plugin": "Plugin",
99+
"labels": "Labels",
100+
"version": "Version",
101+
"status": "Status"
102+
},
103+
"placeholders": {
104+
"name": "Name",
105+
"id": "ID",
106+
"host": "Host",
107+
"path": "Path",
108+
"description": "Description",
109+
"plugin": "Plugin",
110+
"labels": "Select labels",
111+
"version": "Select version",
112+
"status": "Select status"
113+
},
114+
"status": {
115+
"all": "UnPublished/Published",
116+
"published": "Published",
117+
"unpublished": "UnPublished"
118+
},
119+
"actions": {
120+
"reset": "Reset",
121+
"search": "Search"
122+
}
123+
},
91124
"secrets": {
92125
"aws": {
93126
"access_key_id": "Access Key ID",

0 commit comments

Comments
 (0)