diff --git a/apps/web/src/lib/country-labels.ts b/apps/web/src/lib/country-labels.ts new file mode 100644 index 0000000..1610628 --- /dev/null +++ b/apps/web/src/lib/country-labels.ts @@ -0,0 +1,13 @@ +/** ISO country codes from fleet locations seed → RU labels for selects. */ +const COUNTRY_LABELS: Record = { + RU: 'Россия', + DE: 'Германия', + NL: 'Нидерланды', + FI: 'Финляндия', + FR: 'Франция', +} + +export function countrySelectLabel(code: string): string { + const name = COUNTRY_LABELS[code] + return name ? `${code} — ${name}` : code +} diff --git a/apps/web/src/routes/_auth/nodes.tsx b/apps/web/src/routes/_auth/nodes.tsx index 97deb7b..ad7b736 100644 --- a/apps/web/src/routes/_auth/nodes.tsx +++ b/apps/web/src/routes/_auth/nodes.tsx @@ -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(null) const [deleteId, setDeleteId] = useState(null) const [preview, setPreview] = useState('') + const [countryCode, setCountryCode] = useState('') const form = useForm({ 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() + 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() { ) : null} -
- - { - form.setValue('locationId', v ?? '') - void refreshPreview() - }} - placeholder="Локация" - options={locations.map((l) => ({ - value: l.id, - label: `${l.code} — ${l.name}`, - }))} - /> +
+
+ + { + 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} + /> +
+
+ + { + 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} + /> +