import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack } from '@mui/material' import { useState, type FormEvent, type ReactNode } from 'react' import { ApiError, type Validation } from './api.ts' import { useValidation } from './useValidation.ts' /** * A dialog that edits one row. The server validates the value as it changes (validatePath); onSave writes it and * the dialog closes when it succeeds. fields renders the inputs from the value, a setter and the problems by field; * below shows extra information from the validation, such as subnet placements. */ export default function RowDialog(props: { title: string initial: T validatePath: string saveLabel: string onSave: (value: T) => Promise onCancel: () => void fields: (value: T, set: (next: T) => void, problems: Record) => ReactNode below?: (result: V | null) => ReactNode }) { const [value, setValue] = useState(props.initial) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) const validation = useValidation(props.validatePath, value) const problems = validation.result?.problems ?? {} const message = validation.result?.message ?? '' const blocked = validation.pending || Object.keys(problems).length > 0 || message !== '' async function submit(event: FormEvent) { event.preventDefault() if (blocked) { return } setSaving(true) setError(null) try { await props.onSave(value) } catch (caught) { const failure = caught as ApiError setError([failure.message, failure.detail].filter(Boolean).join('\n')) setSaving(false) } } return ( reason !== 'backdropClick' && props.onCancel()} maxWidth="sm" fullWidth slotProps={{ paper: { component: 'form', onSubmit: submit } }}> {props.title} {error && {error}} {validation.error && {validation.error}} {message && {message}} {props.fields(value, setValue, problems)} {props.below?.(validation.result)} ) }