feature: sk-react-toolkit holds the server helpers, web components and Vite font plugin shared by manage-ui and sql-query-webui.
Release / release (push) Failing after 1m18s
Release / release (push) Failing after 1m18s
This commit is contained in:
commit
4505b37abc
30 files changed
+4826
No files matched your search
@@ -0,0 +1,65 @@
|
||||
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<T, V extends Validation = Validation>(props: {
|
||||
title: string
|
||||
initial: T
|
||||
validatePath: string
|
||||
saveLabel: string
|
||||
onSave: (value: T) => Promise<void>
|
||||
onCancel: () => void
|
||||
fields: (value: T, set: (next: T) => void, problems: Record<string, string>) => ReactNode
|
||||
below?: (result: V | null) => ReactNode
|
||||
}) {
|
||||
const [value, setValue] = useState<T>(props.initial)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const validation = useValidation<V>(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 (
|
||||
<Dialog open onClose={(_event, reason) => reason !== 'backdropClick' && props.onCancel()} maxWidth="sm" fullWidth slotProps={{ paper: { component: 'form', onSubmit: submit } }}>
|
||||
<DialogTitle>{props.title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack sx={{ gap: 2, pt: 1 }}>
|
||||
{error && <Alert severity="error" sx={{ whiteSpace: 'pre-line' }}>{error}</Alert>}
|
||||
{validation.error && <Alert severity="error">{validation.error}</Alert>}
|
||||
{message && <Alert severity="error" sx={{ whiteSpace: 'pre-line' }}>{message}</Alert>}
|
||||
{props.fields(value, setValue, problems)}
|
||||
{props.below?.(validation.result)}
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={props.onCancel}>Cancel</Button>
|
||||
<Button type="submit" variant="contained" disabled={saving || blocked}>
|
||||
{props.saveLabel}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user