rename web/ to admin/, along with the web-named build and cli identifiers
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQueryClient, type QueryClient, type UseMutationOptions } from "@tanstack/react-query";
|
||||
|
||||
/** Which form is open: none, the create form, or the edit form for one row. */
|
||||
export type CrudFormState<Entity> = { mode: "create" } | { mode: "edit"; entity: Entity };
|
||||
|
||||
/** The row a confirmation dialog is open for, and the message it shows. */
|
||||
export interface PendingDelete<Entity> {
|
||||
entity: Entity;
|
||||
message: string;
|
||||
}
|
||||
|
||||
type MutationFactory<Variables> = (queryClient: QueryClient) => UseMutationOptions<unknown, Error, Variables>;
|
||||
|
||||
export interface CrudFormSpec<Entity extends { id: number }, Input> {
|
||||
create: MutationFactory<Input>;
|
||||
update: MutationFactory<{ id: number; input: Input }>;
|
||||
remove: MutationFactory<number>;
|
||||
/** The message the confirmation dialog shows before a delete. */
|
||||
confirmDelete: (entity: Entity) => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The create/edit/delete plumbing shared by the local DNS tabs (ruling 10):
|
||||
* three mutations, the open-form state, the pending-delete state and the
|
||||
* handlers. The field JSX, the table and the confirmation dialog stay in the
|
||||
* caller: `onDelete` opens the confirmation instead of deciding anything, so
|
||||
* the hook never reaches for `window.confirm` (ruling 5).
|
||||
*/
|
||||
export function useCrudForm<Entity extends { id: number }, Input>(spec: CrudFormSpec<Entity, Input>) {
|
||||
const queryClient = useQueryClient();
|
||||
const create = useMutation(spec.create(queryClient));
|
||||
const update = useMutation(spec.update(queryClient));
|
||||
const remove = useMutation(spec.remove(queryClient));
|
||||
const [form, setForm] = useState<CrudFormState<Entity> | null>(null);
|
||||
const [pendingDelete, setPendingDelete] = useState<PendingDelete<Entity> | null>(null);
|
||||
|
||||
function openForm(next: CrudFormState<Entity>) {
|
||||
create.reset();
|
||||
update.reset();
|
||||
setForm(next);
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
setForm(null);
|
||||
}
|
||||
|
||||
function onSubmit(input: Input) {
|
||||
if (form === null) return;
|
||||
if (form.mode === "create") {
|
||||
create.mutate(input, { onSuccess: () => setForm(null) });
|
||||
} else {
|
||||
update.mutate({ id: form.entity.id, input }, { onSuccess: () => setForm(null) });
|
||||
}
|
||||
}
|
||||
|
||||
function onDelete(entity: Entity) {
|
||||
setPendingDelete({ entity, message: spec.confirmDelete(entity) });
|
||||
}
|
||||
|
||||
function confirmPendingDelete() {
|
||||
if (pendingDelete === null) return;
|
||||
remove.mutate(pendingDelete.entity.id);
|
||||
setPendingDelete(null);
|
||||
}
|
||||
|
||||
function cancelPendingDelete() {
|
||||
setPendingDelete(null);
|
||||
}
|
||||
|
||||
return {
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
form,
|
||||
openForm,
|
||||
closeForm,
|
||||
onSubmit,
|
||||
onDelete,
|
||||
pendingDelete,
|
||||
confirmPendingDelete,
|
||||
cancelPendingDelete,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user