@@ -0,0 +1,106 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@conusai-kit/ui/primitives"
|
||||
|
||||
// Registers a document: the browser stores the file through the files module, then asks this module to
|
||||
// register it, which takes the version's hash, size and type from the stored file. Then it opens the
|
||||
// document.
|
||||
|
||||
import type { ProjectSlugs } from "@conusai-kit/ui"
|
||||
import { describeError, useUpload, useWrite } from "@conusai-kit/ui/data"
|
||||
import { SelectField, serviceNotice, usePageNavigation } from "@conusai-kit/ui/patterns"
|
||||
import { Alert, AlertDescription, Field, FieldDescription, FieldLabel, Input } from "@conusai-kit/ui/primitives"
|
||||
import { type FormEvent, type ReactElement, useState } from "react"
|
||||
import type { RetentionClassView } from "../contract/index.js"
|
||||
|
||||
export interface RegisterFormLabels {
|
||||
readonly heading: string
|
||||
readonly title: string
|
||||
readonly kind: string
|
||||
readonly kindHint: string
|
||||
readonly retentionClass: string
|
||||
readonly file: string
|
||||
readonly submit: string
|
||||
readonly busy: string
|
||||
readonly noClass: string
|
||||
readonly failed: string
|
||||
/** The text of each business refusal by problem code (`businessRefusalNotices` of `@conusai-kit/business/refusals`). */
|
||||
readonly refusals: Readonly<Record<string, string>>
|
||||
}
|
||||
|
||||
export function RegisterForm({
|
||||
slugs,
|
||||
classes,
|
||||
listHref,
|
||||
labels,
|
||||
}: {
|
||||
slugs: ProjectSlugs
|
||||
classes: readonly RetentionClassView[]
|
||||
listHref: string
|
||||
labels: RegisterFormLabels
|
||||
}): ReactElement {
|
||||
const navigation = usePageNavigation()
|
||||
const upload = useUpload()
|
||||
const register = useWrite<Record<string, unknown>, { documentId: string }>("archive.document.register")
|
||||
const [requestId] = useState(() => crypto.randomUUID())
|
||||
const [state, setState] = useState<{ kind: "idle" | "busy" } | { kind: "failed"; detail: string }>({ kind: "idle" })
|
||||
|
||||
const submit = async (event: FormEvent<HTMLFormElement>): Promise<void> => {
|
||||
event.preventDefault()
|
||||
const form = new FormData(event.currentTarget)
|
||||
const file = form.get("file")
|
||||
if (!(file instanceof File) || file.size === 0) return
|
||||
const kind = String(form.get("kind") ?? "").trim()
|
||||
setState({ kind: "busy" })
|
||||
try {
|
||||
const stored = await upload(slugs, file)
|
||||
const created = await register.mutate({
|
||||
...slugs,
|
||||
title: String(form.get("title") ?? "").trim(),
|
||||
...(kind ? { kind } : {}),
|
||||
retentionClass: String(form.get("retentionClass") ?? ""),
|
||||
fileId: stored.fileId,
|
||||
requestId,
|
||||
})
|
||||
navigation.navigate(`${listHref}/${encodeURIComponent(created.documentId)}`)
|
||||
} catch (error) {
|
||||
setState({ kind: "failed", detail: serviceNotice(error, labels.refusals)?.text ?? describeError(error) })
|
||||
}
|
||||
}
|
||||
|
||||
return classes.length === 0 ? (
|
||||
<p>{labels.noClass}</p>
|
||||
) : (
|
||||
<form onSubmit={submit} aria-busy={state.kind === "busy"}>
|
||||
<h2>{labels.heading}</h2>
|
||||
<Field>
|
||||
<FieldLabel>{labels.title}</FieldLabel>
|
||||
<Input name="title" type="text" required maxLength={200} />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>{labels.kind}</FieldLabel>
|
||||
<Input name="kind" type="text" pattern="[0-9]{1,4}" />
|
||||
<FieldDescription>{labels.kindHint}</FieldDescription>
|
||||
</Field>
|
||||
<SelectField
|
||||
name="retentionClass"
|
||||
label={labels.retentionClass}
|
||||
required
|
||||
defaultValue={classes[0]?.code}
|
||||
options={classes.map((retention) => ({ value: retention.code, label: retention.code }))}
|
||||
/>
|
||||
<Field>
|
||||
<FieldLabel>{labels.file}</FieldLabel>
|
||||
<Input name="file" type="file" required />
|
||||
</Field>
|
||||
<Button type="submit" disabled={state.kind === "busy"}>
|
||||
{state.kind === "busy" ? labels.busy : labels.submit}
|
||||
</Button>
|
||||
{state.kind === "failed" ? (
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>{labels.failed.replace("{detail}", state.detail)}</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user