fix(ui): каскад страна и локация в форме ноды
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 5s
quality / changes (push) Successful in 4s
quality / api (push) Skipped
quality / docker-check (push) Skipped
quality / web (push) Successful in 45s
CD / quality (push) Successful in 56s
CD / publish (push) Successful in 1m25s
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 5s
quality / changes (push) Successful in 4s
quality / api (push) Skipped
quality / docker-check (push) Skipped
quality / web (push) Successful in 45s
CD / quality (push) Successful in 56s
CD / publish (push) Successful in 1m25s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
/** ISO country codes from fleet locations seed → RU labels for selects. */
|
||||
const COUNTRY_LABELS: Record<string, string> = {
|
||||
RU: 'Россия',
|
||||
DE: 'Германия',
|
||||
NL: 'Нидерланды',
|
||||
FI: 'Финляндия',
|
||||
FR: 'Франция',
|
||||
}
|
||||
|
||||
export function countrySelectLabel(code: string): string {
|
||||
const name = COUNTRY_LABELS[code]
|
||||
return name ? `${code} — ${name}` : code
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import { Button } from '@cdnmanager/ui/components/button'
|
||||
import { Input } from '@cdnmanager/ui/components/input'
|
||||
import { Label } from '@cdnmanager/ui/components/label'
|
||||
import { SelectField } from '@/components/select-field'
|
||||
import { countrySelectLabel } from '@/lib/country-labels'
|
||||
import { queryClient } from '@/lib/query-client'
|
||||
import {
|
||||
createNode,
|
||||
@@ -80,6 +81,7 @@ function NodesPage() {
|
||||
const [editing, setEditing] = useState<Node | null>(null)
|
||||
const [deleteId, setDeleteId] = useState<string | null>(null)
|
||||
const [preview, setPreview] = useState('')
|
||||
const [countryCode, setCountryCode] = useState('')
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@@ -102,6 +104,30 @@ function NodesPage() {
|
||||
const watchIndex = form.watch('indexNum')
|
||||
const watchProvider = form.watch('providerTag')
|
||||
|
||||
const countryOptions = useMemo(() => {
|
||||
const codes = new Set<string>()
|
||||
for (const loc of locations) {
|
||||
if (loc.country) codes.add(loc.country)
|
||||
}
|
||||
return [...codes]
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map((code) => ({ value: code, label: countrySelectLabel(code) }))
|
||||
}, [locations])
|
||||
|
||||
const locationOptions = useMemo(() => {
|
||||
if (!countryCode) return []
|
||||
return locations
|
||||
.filter((l) => l.country === countryCode)
|
||||
.map((l) => ({
|
||||
value: l.id,
|
||||
label: `${l.code} — ${l.name}`,
|
||||
}))
|
||||
}, [locations, countryCode])
|
||||
|
||||
function countryForLocationId(locationId: string): string {
|
||||
return locations.find((l) => l.id === locationId)?.country ?? ''
|
||||
}
|
||||
|
||||
async function refreshPreview() {
|
||||
if (!watchZone || !watchLoc || !watchRole) return
|
||||
try {
|
||||
@@ -148,6 +174,7 @@ function NodesPage() {
|
||||
toast.success(editing ? 'Нода обновлена' : 'Нода создана')
|
||||
setSheetOpen(false)
|
||||
setEditing(null)
|
||||
setCountryCode('')
|
||||
form.reset()
|
||||
void qc.invalidateQueries({ queryKey: ['fleet'] })
|
||||
},
|
||||
@@ -267,6 +294,7 @@ function NodesPage() {
|
||||
onClick={() => {
|
||||
const n = row.original
|
||||
setEditing(n)
|
||||
setCountryCode(countryForLocationId(n.locationId))
|
||||
form.reset({
|
||||
zoneId: n.zoneId,
|
||||
locationId: n.locationId,
|
||||
@@ -306,9 +334,11 @@ function NodesPage() {
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setEditing(null)
|
||||
const firstLoc = locations[0]
|
||||
setCountryCode(firstLoc?.country ?? '')
|
||||
form.reset({
|
||||
zoneId: zones[0]?.id ?? '',
|
||||
locationId: locations[0]?.id ?? '',
|
||||
locationId: firstLoc?.id ?? '',
|
||||
role: 'gw',
|
||||
indexNum: 1,
|
||||
ipv4: '',
|
||||
@@ -396,20 +426,41 @@ function NodesPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Локация</Label>
|
||||
<SelectField
|
||||
value={form.watch('locationId')}
|
||||
onValueChange={(v) => {
|
||||
form.setValue('locationId', v ?? '')
|
||||
void refreshPreview()
|
||||
}}
|
||||
placeholder="Локация"
|
||||
options={locations.map((l) => ({
|
||||
value: l.id,
|
||||
label: `${l.code} — ${l.name}`,
|
||||
}))}
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Страна</Label>
|
||||
<SelectField
|
||||
value={countryCode || null}
|
||||
onValueChange={(v) => {
|
||||
const next = v ?? ''
|
||||
setCountryCode(next)
|
||||
const currentLoc = locations.find((l) => l.id === form.getValues('locationId'))
|
||||
if (!next || !currentLoc || currentLoc.country !== next) {
|
||||
form.setValue('locationId', '')
|
||||
setPreview('')
|
||||
}
|
||||
void refreshPreview()
|
||||
}}
|
||||
placeholder="Страна"
|
||||
options={countryOptions}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Локация</Label>
|
||||
<SelectField
|
||||
value={form.watch('locationId') || null}
|
||||
disabled={!countryCode}
|
||||
onValueChange={(v) => {
|
||||
const id = v ?? ''
|
||||
form.setValue('locationId', id)
|
||||
const loc = locations.find((l) => l.id === id)
|
||||
if (loc?.country) setCountryCode(loc.country)
|
||||
void refreshPreview()
|
||||
}}
|
||||
placeholder={countryCode ? 'Локация' : 'Сначала страна'}
|
||||
options={locationOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
|
||||
Reference in New Issue
Block a user