Skip to content

Commit a45f790

Browse files
authored
Merge pull request #163 from PRO-Robotech/feature/dev
search controls and url sync
2 parents 996d4be + eb4d821 commit a45f790

File tree

5 files changed

+101
-30
lines changed

5 files changed

+101
-30
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@prorobotech/openapi-k8s-toolkit",
3-
"version": "0.0.1-alpha.115",
3+
"version": "0.0.1-alpha.116",
44
"description": "ProRobotech OpenAPI k8s tools",
55
"main": "dist/openapi-k8s-toolkit.cjs.js",
66
"module": "dist/openapi-k8s-toolkit.es.js",

src/components/molecules/Search/Search.tsx

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
/* eslint-disable max-lines-per-function */
22
import React, { FC, useState, useEffect } from 'react'
3-
import { Typography, Form, Select, SelectProps, Input } from 'antd'
3+
import { Form, Select, SelectProps, Input } from 'antd'
44
import type { CustomTagProps } from 'rc-select/lib/BaseSelect'
55
import { useLocation, useSearchParams } from 'react-router-dom'
66
import { getKinds } from 'api/bff/search/getKinds'
77
import { getSortedKinds } from 'utils/getSortedKinds'
88
import { TKindIndex } from 'localTypes/bff/search'
99
import { TKindWithVersion } from 'localTypes/search'
1010
import { filterSelectOptions } from 'utils/filterSelectOptions'
11-
import { useDebouncedCallback, getArrayParam, setArrayParam } from './utils'
11+
import { useDebouncedCallback, getArrayParam, setArrayParam, getStringParam, setStringParam } from './utils'
1212
import { Styled } from './styled'
1313

1414
type TSearchProps = {
@@ -23,8 +23,12 @@ export const Search: FC<TSearchProps> = ({ cluster }) => {
2323
const FIELD_NAME = 'kinds'
2424
const FIELD_NAME_STRING = 'name'
2525
const FIELD_NAME_MULTIPLE = 'labels'
26+
2627
const TYPE_SELECTOR = 'TYPE_SELECTOR'
28+
2729
const QUERY_KEY = 'kinds' // the query param name
30+
const NAME_QUERY_KEY = 'name'
31+
const LABELS_QUERY_KEY = 'labels'
2832

2933
const watchedKinds = Form.useWatch<string[] | undefined>(FIELD_NAME, form)
3034
const watchedName = Form.useWatch<string | undefined>(FIELD_NAME_STRING, form)
@@ -46,14 +50,40 @@ export const Search: FC<TSearchProps> = ({ cluster }) => {
4650

4751
// Apply current values from search params on mount / when URL changes
4852
useEffect(() => {
49-
const fromUrl = getArrayParam(searchParams, QUERY_KEY)
50-
const current = form.getFieldValue(FIELD_NAME)
53+
const fromKinds = getArrayParam(searchParams, QUERY_KEY)
54+
const currentKinds = form.getFieldValue(FIELD_NAME)
55+
const kindsDiffer =
56+
(fromKinds.length || 0) !== (currentKinds?.length || 0) || fromKinds.some((v, i) => v !== currentKinds?.[i])
5157

52-
// Only update the form if URL differs from form (prevents loops)
53-
const differ = (fromUrl.length || 0) !== (current?.length || 0) || fromUrl.some((v, i) => v !== current?.[i])
58+
// name
59+
const fromName = getStringParam(searchParams, NAME_QUERY_KEY)
60+
const currentName = form.getFieldValue(FIELD_NAME_STRING) as string | undefined
61+
const nameDiffer = (fromName || '') !== (currentName || '')
62+
63+
// labels
64+
const fromLabels = getArrayParam(searchParams, LABELS_QUERY_KEY)
65+
const currentLabels = form.getFieldValue(FIELD_NAME_MULTIPLE) as string[] | undefined
66+
const labelsDiffer =
67+
(fromLabels.length || 0) !== (currentLabels?.length || 0) || fromLabels.some((v, i) => v !== currentLabels?.[i])
68+
69+
// decide type from params
70+
const currentType = form.getFieldValue(TYPE_SELECTOR)
71+
let inferredType: string | undefined
72+
if (fromName) {
73+
inferredType = 'name'
74+
} else if (fromLabels.length > 0) {
75+
inferredType = 'labels'
76+
}
77+
const typeDiffer = inferredType !== currentType
5478

55-
if (differ) {
56-
form.setFieldsValue({ [FIELD_NAME]: fromUrl })
79+
// Only update the form if URL differs from form (prevents loops)
80+
if (kindsDiffer || nameDiffer || labelsDiffer) {
81+
form.setFieldsValue({
82+
[FIELD_NAME]: kindsDiffer ? fromKinds : currentKinds,
83+
[FIELD_NAME_STRING]: nameDiffer ? fromName : currentName,
84+
[FIELD_NAME_MULTIPLE]: labelsDiffer ? fromLabels : currentLabels,
85+
[TYPE_SELECTOR]: typeDiffer ? inferredType : currentType,
86+
})
5787
}
5888
// eslint-disable-next-line react-hooks/exhaustive-deps
5989
}, [location.search]) // react to back/forward, external URL edits
@@ -64,13 +94,49 @@ export const Search: FC<TSearchProps> = ({ cluster }) => {
6494
setSearchParams(next, { replace: true }) // replace to keep history cleaner
6595
}, 250)
6696

97+
const debouncedPushName = useDebouncedCallback((value: string) => {
98+
const next = setStringParam(searchParams, NAME_QUERY_KEY, value)
99+
setSearchParams(next, { replace: true })
100+
}, 250)
101+
102+
const debouncedPushLabels = useDebouncedCallback((values: string[]) => {
103+
const next = setArrayParam(searchParams, LABELS_QUERY_KEY, values)
104+
setSearchParams(next, { replace: true })
105+
}, 250)
106+
67107
useEffect(() => {
68108
debouncedPush(watchedKinds || [])
69109
// eslint-disable-next-line react-hooks/exhaustive-deps
70110
}, [watchedKinds])
71111

72-
// eslint-disable-next-line no-console
73-
console.log(kindWithVersion)
112+
useEffect(() => {
113+
debouncedPushName((watchedName || '').trim())
114+
// eslint-disable-next-line react-hooks/exhaustive-deps
115+
}, [watchedName])
116+
117+
useEffect(() => {
118+
debouncedPushLabels(watchedMultiple || [])
119+
// eslint-disable-next-line react-hooks/exhaustive-deps
120+
}, [watchedMultiple])
121+
122+
useEffect(() => {
123+
if (watchedTypedSelector === 'name') {
124+
// Clear labels when switching to "name"
125+
const cur = form.getFieldValue(FIELD_NAME_MULTIPLE) as string[] | undefined
126+
if (cur?.length) {
127+
form.setFieldsValue({ [FIELD_NAME_MULTIPLE]: [] })
128+
}
129+
} else if (watchedTypedSelector === 'labels') {
130+
// Clear name when switching to "labels"
131+
const cur = (form.getFieldValue(FIELD_NAME_STRING) as string | undefined) ?? ''
132+
if (cur) {
133+
form.setFieldsValue({ [FIELD_NAME_STRING]: '' })
134+
}
135+
}
136+
// Optional: if undefined (e.g., initial), choose a default behavior:
137+
// else { form.setFieldsValue({ [FIELD_NAME_STRING]: '', [FIELD_NAME_MULTIPLE]: [] }) }
138+
// eslint-disable-next-line react-hooks/exhaustive-deps
139+
}, [watchedTypedSelector])
74140

75141
const options: SelectProps['options'] =
76142
kindWithVersion?.map(({ kind, notUnique, group, version }) => ({
@@ -101,7 +167,7 @@ export const Search: FC<TSearchProps> = ({ cluster }) => {
101167
)
102168

103169
return (
104-
<Styled.CatContainer>
170+
<div>
105171
<Form form={form} layout="vertical">
106172
<Styled.FormContainer>
107173
<Form.Item name={FIELD_NAME} label="Kinds">
@@ -113,6 +179,7 @@ export const Search: FC<TSearchProps> = ({ cluster }) => {
113179
allowClear
114180
showSearch
115181
tagRender={tagRender}
182+
maxTagCount="responsive"
116183
/>
117184
</Form.Item>
118185
<Form.Item name={TYPE_SELECTOR} label="Type">
@@ -163,13 +230,12 @@ export const Search: FC<TSearchProps> = ({ cluster }) => {
163230
/>
164231
</Form.Item>
165232
</Styled.HideableContainer>
166-
{/* Example of "watching" the value for display or side-effects */}
167-
<div>Current: {(watchedKinds || []).join(', ')}</div>
168-
<div>Current name: {watchedName}</div>
169-
<div>Current labels: {(watchedMultiple || []).join(', ')}</div>
170233
</Styled.FormContainer>
171234
</Form>
172-
<Typography.Title>To Be Done</Typography.Title>
173-
</Styled.CatContainer>
235+
{/* Example of "watching" the value for display or side-effects */}
236+
<div>Current: {(watchedKinds || []).join(', ')}</div>
237+
<div>Current name: {watchedName}</div>
238+
<div>Current labels: {(watchedMultiple || []).join(', ')}</div>
239+
</div>
174240
)
175241
}

src/components/molecules/Search/styled.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
import { Tag } from 'antd'
22
import styled from 'styled-components'
33

4-
export const CatContainer = styled.div`
5-
display: flex;
6-
justify-content: center;
7-
align-items: center;
8-
flex-flow: column;
9-
margin: auto;
10-
`
11-
124
const SelectTag = styled(Tag)`
135
margin-inline-end: 4px;
146
padding: 4px 6px;
@@ -25,7 +17,9 @@ const SelectTagSpan = styled.span`
2517
`
2618

2719
const FormContainer = styled.div`
28-
width: 450px;
20+
display: grid;
21+
grid-template-columns: 300px 100px 1fr;
22+
gap: 16px;
2923
`
3024

3125
type THideableContainerProps = {
@@ -37,7 +31,6 @@ const HideableContainer = styled.div<THideableContainerProps>`
3731
`
3832

3933
export const Styled = {
40-
CatContainer,
4134
SelectTag,
4235
SelectTagSpan,
4336
FormContainer,

src/components/molecules/Search/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,15 @@ export const setArrayParam = (sp: URLSearchParams, key: string, values: string[]
2828
}
2929
return next
3030
}
31+
32+
export const getStringParam = (sp: URLSearchParams, key: string): string => {
33+
return sp.get(key) ?? ''
34+
}
35+
36+
export const setStringParam = (sp: URLSearchParams, key: string, value: string | undefined | null) => {
37+
const next = new URLSearchParams(sp) // preserve other params
38+
const v = (value ?? '').trim()
39+
if (!v) next.delete(key)
40+
else next.set(key, v)
41+
return next
42+
}

0 commit comments

Comments
 (0)