flowstate/app/(app)/tasks/useTaskForm.ts
Chneemann a60851ac07
All checks were successful
Deploy Flowstate to VPS / deploy (push) Successful in 1m27s
refactor(lib): consolidate services, types, utils, and actions into lib folder and update import paths
2026-08-23 16:44:18 +02:00

129 lines
4.2 KiB
TypeScript

/**
* @file app/(app)/tasks/useTaskForm.ts
* @description Custom hook managing state, validation, and submission logic for task creation and editing.
*/
import { useState, useTransition } from "react";
import { useRouter } from "next/navigation";
import { TaskPriority, TaskStatus, DbTask } from "@/lib/types/task";
import { taskSchema } from "@/lib/schemas/task.schema";
/**
* Custom React hook that encapsulates form state management, field updates, assignee toggling,
* validation rules, and network submission logic for both task creation and editing workflows.
*
* @param {DbTask & { assignees?: { id: string }[] }} [initialData] - Initial task data loaded for editing workflows.
* @param {string} [mode] - The current operating mode (e.g., "edit").
* @param {TaskStatus} [defaultStatus] - The fallback status applied when creating new tasks.
* @returns {Object} An object containing form state values, error states, and event handling functions.
*/
export function useTaskForm(
initialData?: DbTask & { assignees?: { id: string }[] },
mode?: string,
defaultStatus?: TaskStatus,
) {
const router = useRouter();
const [, startTransition] = useTransition();
const isEditMode = mode === "edit" && initialData;
const todayString = new Date().toISOString().split("T")[0];
const dueDateObj = initialData?.dueDate
? new Date(initialData.dueDate)
: null;
const [form, setForm] = useState({
title: initialData?.title ?? "",
description: initialData?.description ?? "",
priority: (initialData?.priority ?? "medium") as TaskPriority,
status: (initialData?.status ?? defaultStatus ?? "todo") as TaskStatus,
assignees: initialData?.assignees?.map((a) => a.id) ?? [],
dueDate: dueDateObj ? dueDateObj.toISOString().split("T")[0] : todayString,
dueTime: dueDateObj ? dueDateObj.toTimeString().slice(0, 5) : "23:59",
});
const [error, setError] = useState<string | null>(null);
/**
* Updates a single property within the form state object.
*
* @param {string} field - The target field name to update.
* @param {any} value - The new value for the field.
*/
const updateField = (field: string, value: any) => {
setForm((prev) => ({ ...prev, [field]: value }));
};
/**
* Toggles the inclusion of a user ID within the assignees selection list.
*
* @param {string} userId - The unique identifier of the user to toggle.
*/
const handleAssigneeToggle = (userId: string) => {
const assignees = form.assignees.includes(userId)
? form.assignees.filter((id) => id !== userId)
: [...form.assignees, userId];
updateField("assignees", assignees);
};
/**
* Validates form input against Zod schemas and dispatches POST or PATCH network requests to persist task changes.
*
* @param {React.SubmitEvent} e - The form submission event.
*/
const handleSubmit = (e: React.SubmitEvent) => {
e.preventDefault();
setError(null);
const finalDate = form.dueDate || todayString;
const combinedDateTime = new Date(`${finalDate}T${form.dueTime}`);
if (!isEditMode && combinedDateTime <= new Date()) {
setError("The due date and time must be in the future.");
return;
}
const validationResult = taskSchema.safeParse({
...form,
dueDate: combinedDateTime.toISOString(),
});
if (!validationResult.success) {
setError(validationResult.error.issues[0].message);
return;
}
startTransition(async () => {
try {
const response = await fetch("/api/tasks", {
method: isEditMode ? "PATCH" : "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
...validationResult.data,
id: initialData?.id,
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText || "Failed to save task.");
}
router.push("/dashboard");
router.refresh();
} catch (err: any) {
console.error("Error submitting task:", err);
setError(err.message || "Something went wrong. Please try again.");
}
});
};
return {
form,
error,
isEditMode,
todayString,
updateField,
handleAssigneeToggle,
handleSubmit,
};
}