🐛 fix: Simplify initialValues assignment and update node submission logic in NodesPage

This commit is contained in:
web 2025-09-15 21:39:13 -07:00
parent 8700cf6a91
commit 05d6c8947c
2 changed files with 102 additions and 68 deletions

View File

@ -74,6 +74,20 @@ export default function NodeForm(props: {
const Scheme = useMemo(() => buildSchema(t), [t]); const Scheme = useMemo(() => buildSchema(t), [t]);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [autoFilledFields, setAutoFilledFields] = useState<Set<string>>(new Set());
const addAutoFilledField = (fieldName: string) => {
setAutoFilledFields((prev) => new Set(prev).add(fieldName));
};
const removeAutoFilledField = (fieldName: string) => {
setAutoFilledFields((prev) => {
const newSet = new Set(prev);
newSet.delete(fieldName);
return newSet;
});
};
const form = useForm<NodeFormValues>({ const form = useForm<NodeFormValues>({
resolver: zodResolver(Scheme), resolver: zodResolver(Scheme),
defaultValues: { defaultValues: {
@ -112,12 +126,14 @@ export default function NodeForm(props: {
const currentServer = useMemo(() => servers?.find((s) => s.id === serverId), [servers, serverId]); const currentServer = useMemo(() => servers?.find((s) => s.id === serverId), [servers, serverId]);
const availableProtocols = useMemo(() => { const availableProtocols = useMemo(() => {
return (currentServer?.protocols || []) if (!currentServer?.protocols) return [];
return (currentServer.protocols as Array<{ type: ProtocolName; port?: number }>)
.filter((p) => p.type)
.map((p) => ({ .map((p) => ({
protocol: (p as any).type as ProtocolName, protocol: p.type,
port: (p as any).port as number | undefined, port: p.port,
})) }));
.filter((p) => !!p.protocol);
}, [currentServer]); }, [currentServer]);
useEffect(() => { useEffect(() => {
@ -139,60 +155,96 @@ export default function NodeForm(props: {
const id = nextId ?? undefined; const id = nextId ?? undefined;
form.setValue('server_id', id); form.setValue('server_id', id);
const sel = servers.find((s) => s.id === id); if (!id) {
const dirty = form.formState.dirtyFields as Record<string, any>; setAutoFilledFields(new Set());
return;
}
const selectedServer = servers.find((s) => s.id === id);
if (!selectedServer) return;
const currentValues = form.getValues(); const currentValues = form.getValues();
const fieldsToFill: string[] = [];
if (!dirty.name) { if (!currentValues.name || autoFilledFields.has('name')) {
form.setValue('name', (sel?.name as string) || '', { shouldDirty: false }); form.setValue('name', selectedServer.name as string, { shouldDirty: false });
fieldsToFill.push('name');
} }
if ( if (!currentValues.address || autoFilledFields.has('address')) {
!dirty.address && form.setValue('address', selectedServer.address as string, { shouldDirty: false });
(!currentValues.address || currentValues.address === (sel?.address as string)) fieldsToFill.push('address');
) {
form.setValue('address', (sel?.address as string) || '', { shouldDirty: false });
} }
const allowed = (sel?.protocols || []) const protocols =
.map((p) => (p as any).type as ProtocolName) (selectedServer.protocols as Array<{ type: ProtocolName; port?: number }>) || [];
.filter(Boolean); const firstProtocol = protocols[0];
const currentProtocol = form.getValues('protocol') as ProtocolName; if (firstProtocol && (!currentValues.protocol || autoFilledFields.has('protocol'))) {
form.setValue('protocol', firstProtocol.type, { shouldDirty: false });
fieldsToFill.push('protocol');
if (!allowed.includes(currentProtocol)) { if (!currentValues.port || currentValues.port === 0 || autoFilledFields.has('port')) {
const firstProtocol = allowed[0] || ''; const port = firstProtocol.port || 0;
form.setValue('protocol', firstProtocol as any); form.setValue('port', port, { shouldDirty: false });
fieldsToFill.push('port');
}
}
if (firstProtocol) { setAutoFilledFields(new Set(fieldsToFill));
handleProtocolChange(firstProtocol); }
const handleManualFieldChange = (fieldName: keyof NodeFormValues, value: any) => {
form.setValue(fieldName, value);
removeAutoFilledField(fieldName);
};
function handleProtocolChange(nextProto?: ProtocolName | null) {
const protocol = (nextProto || '') as ProtocolName | '';
form.setValue('protocol', protocol);
if (!protocol || !currentServer) {
removeAutoFilledField('protocol');
return;
}
const currentValues = form.getValues();
const isPortAutoFilled = autoFilledFields.has('port');
removeAutoFilledField('protocol');
if (!currentValues.port || currentValues.port === 0 || isPortAutoFilled) {
const protocolData = (
currentServer.protocols as Array<{ type: ProtocolName; port?: number }>
)?.find((p) => p.type === protocol);
if (protocolData) {
const port = protocolData.port || 0;
form.setValue('port', port, { shouldDirty: false });
addAutoFilledField('port');
} }
} }
} }
function handleProtocolChange(nextProto?: ProtocolName | null) { async function handleSubmit(values: NodeFormValues) {
const p = (nextProto || '') as ProtocolName | ''; const result = await onSubmit(values);
form.setValue('protocol', p); if (result) {
if (!p || !currentServer) return; setOpen(false);
setAutoFilledFields(new Set());
const dirty = form.formState.dirtyFields as Record<string, any>;
const currentValues = form.getValues();
if (!dirty.port) {
const hit = (currentServer.protocols as any[]).find((x) => (x as any).type === p);
const port = (hit as any)?.port as number | undefined;
const newPort = typeof port === 'number' && port > 0 ? port : 0;
if (!currentValues.port || currentValues.port === 0 || currentValues.port === newPort) {
form.setValue('port', newPort, { shouldDirty: false });
}
} }
} }
return ( return (
<Sheet open={open} onOpenChange={setOpen}> <Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild> <SheetTrigger asChild>
<Button onClick={() => form.reset()}>{trigger}</Button> <Button
onClick={() => {
form.reset();
setAutoFilledFields(new Set());
}}
>
{trigger}
</Button>
</SheetTrigger> </SheetTrigger>
<SheetContent className='w-[560px] max-w-full'> <SheetContent className='w-[560px] max-w-full'>
@ -253,7 +305,7 @@ export default function NodeForm(props: {
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
{...field} {...field}
onValueChange={(v) => form.setValue(field.name, v as string)} onValueChange={(v) => handleManualFieldChange('name', v as string)}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -270,7 +322,7 @@ export default function NodeForm(props: {
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
{...field} {...field}
onValueChange={(v) => form.setValue(field.name, v as string)} onValueChange={(v) => handleManualFieldChange('address', v as string)}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -291,7 +343,7 @@ export default function NodeForm(props: {
min={1} min={1}
max={65535} max={65535}
placeholder='1-65535' placeholder='1-65535'
onValueChange={(v) => form.setValue(field.name, Number(v))} onValueChange={(v) => handleManualFieldChange('port', Number(v))}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -327,7 +379,7 @@ export default function NodeForm(props: {
</Button> </Button>
<Button <Button
disabled={loading} disabled={loading}
onClick={form.handleSubmit(onSubmit, (errors) => { onClick={form.handleSubmit(handleSubmit, (errors) => {
const key = Object.keys(errors)[0] as keyof typeof errors; const key = Object.keys(errors)[0] as keyof typeof errors;
if (key) toast.error(String(errors[key]?.message)); if (key) toast.error(String(errors[key]?.message));
return false; return false;

View File

@ -152,26 +152,13 @@ export default function NodesPage() {
trigger={t('edit')} trigger={t('edit')}
title={t('drawerEditTitle')} title={t('drawerEditTitle')}
loading={loading} loading={loading}
initialValues={{ initialValues={row}
name: row.name,
server_id: row.server_id,
protocol: row.protocol as any,
address: row.address as any,
port: row.port as any,
tags: (row.tags as any) || [],
}}
onSubmit={async (values) => { onSubmit={async (values) => {
setLoading(true); setLoading(true);
try { try {
const body: API.UpdateNodeRequest = { const body: API.UpdateNodeRequest = {
id: row.id, ...row,
name: values.name, ...values,
server_id: Number(values.server_id!),
protocol: values.protocol,
address: values.address,
port: Number(values.port!),
tags: values.tags || [],
enabled: row.enabled,
} as any; } as any;
await updateNode(body); await updateNode(body);
toast.success(t('updated')); toast.success(t('updated'));
@ -201,16 +188,11 @@ export default function NodesPage() {
key='copy' key='copy'
variant='outline' variant='outline'
onClick={async () => { onClick={async () => {
const { id, enabled, created_at, updated_at, ...rest } = row as any; const { id, enabled, created_at, updated_at, sort, ...rest } = row as any;
await createNode({ await createNode({
name: rest.name, ...rest,
server_id: rest.server_id,
protocol: rest.protocol,
address: rest.address,
port: rest.port,
tags: rest.tags || [],
enabled: false, enabled: false,
} as any); });
toast.success(t('copied')); toast.success(t('copied'));
ref.current?.refresh(); ref.current?.refresh();
}} }}