Files
dns-autoresolver/web/src/pages/SchedulePage.tsx
T

146 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useId } from "react"
import { Controller, useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
import { Loader2, Save } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Field,
FieldContent,
FieldError,
FieldGroup,
FieldLabel,
FieldSet,
} from "@/components/ui/field"
import { useSchedule, useUpdateSchedule } from "@/hooks/useApi"
import type { Schedule } from "@/api/types"
const scheduleFormSchema = z.object({
intervalSeconds: z
.number({ error: "Укажите интервал в секундах" })
.int("Интервал — целое число секунд")
.min(60, "Интервал не может быть меньше 60 секунд"),
enabled: z.boolean(),
})
type ScheduleForm = z.infer<typeof scheduleFormSchema>
const DEFAULT_SCHEDULE: Schedule = { intervalSeconds: 300, enabled: false }
// Rendered only once the current schedule is loaded, so useForm's
// defaultValues (a one-time snapshot in react-hook-form) are always seeded
// with the real values — no reset()-after-fetch race between the query and
// the form's first render.
function ScheduleFormCard({ initial }: { initial: Schedule }) {
const updateSchedule = useUpdateSchedule()
const intervalFieldId = useId()
const {
control,
handleSubmit,
formState: { errors },
} = useForm<ScheduleForm>({
resolver: zodResolver(scheduleFormSchema),
defaultValues: initial,
})
function onSubmit(values: ScheduleForm) {
updateSchedule.mutate(values)
}
return (
<form
onSubmit={handleSubmit(onSubmit)}
noValidate
className="flex flex-col gap-4 rounded-xl border border-border bg-card/60 p-4"
>
<FieldSet className="gap-3">
<FieldGroup className="gap-3">
<Field className="sm:max-w-56">
<FieldLabel htmlFor={intervalFieldId}>Интервал (секунд)</FieldLabel>
<FieldContent>
<Controller
control={control}
name="intervalSeconds"
render={({ field }) => (
<Input
{...field}
id={intervalFieldId}
type="number"
min={60}
step={1}
className="font-dns"
aria-invalid={!!errors.intervalSeconds}
onChange={(e) => field.onChange(e.target.valueAsNumber)}
/>
)}
/>
<FieldError errors={[errors.intervalSeconds]} />
</FieldContent>
</Field>
<Field>
<Controller
control={control}
name="enabled"
render={({ field }) => (
<Label className="flex items-center gap-2.5 text-sm font-normal">
<Checkbox
aria-label="Включено"
checked={field.value}
onCheckedChange={(v) => field.onChange(v === true)}
/>
Автоматические проверки включены
</Label>
)}
/>
</Field>
</FieldGroup>
</FieldSet>
<div className="flex items-center justify-between gap-3 border-t border-border pt-3">
{updateSchedule.isError && (
<span role="alert" className="font-dns text-xs text-destructive">
{updateSchedule.error.message}
</span>
)}
<Button type="submit" disabled={updateSchedule.isPending} className="ml-auto">
{updateSchedule.isPending ? (
<Loader2 className="size-4 animate-spin" strokeWidth={1.75} />
) : (
<Save className="size-4" strokeWidth={1.75} />
)}
Сохранить расписание
</Button>
</div>
</form>
)
}
export function SchedulePage() {
const schedule = useSchedule()
return (
<div className="mx-auto flex max-w-2xl flex-col gap-6 px-6 py-8">
<header className="flex flex-col gap-1">
<span className="font-dns text-[11px] tracking-wider text-muted-foreground uppercase">
scheduler
</span>
<h1 className="text-xl font-semibold tracking-tight text-foreground">Расписание проверок</h1>
</header>
{schedule.isPending ? (
<div className="flex items-center gap-2 rounded-lg border border-border bg-card px-4 py-8 text-sm text-muted-foreground">
<Loader2 className="size-4 animate-spin" strokeWidth={1.75} />
Загружаю расписание
</div>
) : (
<ScheduleFormCard initial={schedule.data ?? DEFAULT_SCHEDULE} />
)}
</div>
)
}