mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
Three real behavior deltas flagged in review, each closing a gap
against the "no behavior change" claim:
- use-vendor-portal-session.ts: add retry:false and
meta:{suppressErrorToast:true} so an invalid/expired token fails
fast with a single request and no duplicate global toast (this was
also the root cause of the failing e2e test - the toast and the
inline error message both had role="alert", tripping a strict-mode
locator match)
- task-template-schema.ts: trim the name before validating so a
whitespace-only name is rejected, matching the old manual
form.name.trim() check
- task-template-detail-form.tsx: block Enter-triggered implicit
submission on the Template Name / Description inputs, since the
original page had no <form> element and Enter did nothing
- task-template-items-field.tsx: surface a visible error when a
loaded item has empty text, since the schema already blocked save
in that case but gave no way to see why; use-task-template-editor.ts
now eagerly validates after loading a template so this reflects
reality immediately instead of only after an unrelated edit
Also extract every user-facing string in the task-templates feature
into TASK_TEMPLATE_COPY (task-template-constants.ts) instead of
inline literals scattered across 5 files.
6 new regression tests cover all of the above; full suite (36 tests
across the 2 refactored areas) still green.
119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
import { useState } from "react";
|
|
import { useForm, useWatch, type UseFormReturn } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import type { UseQueryResult } from "@tanstack/react-query";
|
|
import {
|
|
EMPTY_TASK_TEMPLATE_FORM,
|
|
NEW_TEMPLATE_ID,
|
|
} from "@/domain/settings/task-templates/constants/task-template-constants";
|
|
import { mapTaskTemplateToFormValues } from "@/domain/settings/task-templates/mappers/task-template-mapper";
|
|
import { taskTemplateSchema } from "@/domain/settings/task-templates/schemas/task-template-schema";
|
|
import type {
|
|
SelectedTaskTemplateId,
|
|
TaskTemplate,
|
|
TaskTemplateFormValues,
|
|
} from "@/domain/settings/task-templates/types/task-template";
|
|
import {
|
|
useCreateTaskTemplate,
|
|
useDeleteTaskTemplate,
|
|
useUpdateTaskTemplate,
|
|
} from "@/domain/settings/task-templates/use-cases/use-task-template-mutations";
|
|
import { useTaskTemplates } from "@/domain/settings/task-templates/use-cases/use-task-templates";
|
|
|
|
export interface UseTaskTemplateEditorResult {
|
|
templatesQuery: UseQueryResult<TaskTemplate[], Error>;
|
|
templates: TaskTemplate[];
|
|
selectedId: SelectedTaskTemplateId;
|
|
isNew: boolean;
|
|
isSaving: boolean;
|
|
isDeleting: boolean;
|
|
form: UseFormReturn<TaskTemplateFormValues>;
|
|
deleteConfirmOpen: boolean;
|
|
templateName: string;
|
|
selectTemplate: (template: TaskTemplate) => void;
|
|
startNewTemplate: () => void;
|
|
submitForm: () => void;
|
|
requestDelete: () => void;
|
|
cancelDelete: () => void;
|
|
confirmDelete: () => void;
|
|
}
|
|
|
|
export function useTaskTemplateEditor(): UseTaskTemplateEditorResult {
|
|
const templatesQuery = useTaskTemplates();
|
|
const createTemplate = useCreateTaskTemplate();
|
|
const updateTemplate = useUpdateTaskTemplate();
|
|
const deleteTemplate = useDeleteTaskTemplate();
|
|
|
|
const [selectedId, setSelectedId] = useState<SelectedTaskTemplateId>(null);
|
|
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
|
|
|
const form = useForm<TaskTemplateFormValues>({
|
|
resolver: zodResolver(taskTemplateSchema),
|
|
defaultValues: EMPTY_TASK_TEMPLATE_FORM,
|
|
mode: "onChange",
|
|
});
|
|
|
|
const isNew = selectedId === NEW_TEMPLATE_ID;
|
|
const templateName = useWatch({ control: form.control, name: "name" });
|
|
|
|
const selectTemplate = (template: TaskTemplate) => {
|
|
setSelectedId(template.id);
|
|
form.reset(mapTaskTemplateToFormValues(template));
|
|
void form.trigger();
|
|
};
|
|
|
|
const startNewTemplate = () => {
|
|
setSelectedId(NEW_TEMPLATE_ID);
|
|
form.reset(EMPTY_TASK_TEMPLATE_FORM);
|
|
};
|
|
|
|
const onSubmit = (values: TaskTemplateFormValues) => {
|
|
const normalized: TaskTemplateFormValues = {
|
|
...values,
|
|
items: values.items.map((item, index) => ({ ...item, sortOrder: index })),
|
|
};
|
|
if (isNew) {
|
|
createTemplate.mutate(normalized, {
|
|
onSuccess: (result) => setSelectedId(result.id),
|
|
});
|
|
return;
|
|
}
|
|
if (selectedId !== null) {
|
|
updateTemplate.mutate({ id: selectedId, values: normalized });
|
|
}
|
|
};
|
|
|
|
const submitForm = form.handleSubmit(onSubmit);
|
|
|
|
const requestDelete = () => setDeleteConfirmOpen(true);
|
|
const cancelDelete = () => setDeleteConfirmOpen(false);
|
|
|
|
const confirmDelete = () => {
|
|
if (selectedId === null || isNew) return;
|
|
deleteTemplate.mutate(selectedId, {
|
|
onSuccess: () => {
|
|
setSelectedId(null);
|
|
form.reset(EMPTY_TASK_TEMPLATE_FORM);
|
|
setDeleteConfirmOpen(false);
|
|
},
|
|
});
|
|
};
|
|
|
|
return {
|
|
templatesQuery,
|
|
templates: templatesQuery.data ?? [],
|
|
selectedId,
|
|
isNew,
|
|
isSaving: createTemplate.isPending || updateTemplate.isPending,
|
|
isDeleting: deleteTemplate.isPending,
|
|
form,
|
|
deleteConfirmOpen,
|
|
templateName,
|
|
selectTemplate,
|
|
startNewTemplate,
|
|
submitForm,
|
|
requestDelete,
|
|
cancelDelete,
|
|
confirmDelete,
|
|
};
|
|
}
|