feat(tasks): pre-fill task status when creating from column header and route new task button

This commit is contained in:
Chneemann 2026-08-13 16:59:25 +02:00
parent dd2a8a5637
commit 3e7d943fe0
No known key found for this signature in database
7 changed files with 49 additions and 18 deletions

View file

@ -86,6 +86,7 @@ export default function Column(props: ColumnProps) {
}`}
>
<ColumnHeader
id={props.id}
title={props.title}
color={props.color}
count={props.count}

View file

@ -1,21 +1,25 @@
/**
* @file dashboard/column/ColumnHeader.tsx
* @description Component rendering the column header with title, color indicator, item count, and add button.
* @description Component rendering the column header with title, color indicator, item count, and add button with pre-filled status.
*/
"use client";
import { Plus } from "lucide-react";
import { useRouter } from "next/navigation";
import { TaskStatus } from "@/types/task";
/**
* Properties for the ColumnHeader component.
*
* @interface ColumnHeaderProps
* @property {TaskStatus} id - The unique identifier/status of the column.
* @property {string} title - The title of the column.
* @property {string} [color] - Tailwind CSS color class for the status indicator dot.
* @property {number} count - The number of tasks currently inside this column.
*/
interface ColumnHeaderProps {
id: TaskStatus;
title: string;
color?: string;
count: number;
@ -23,16 +27,19 @@ interface ColumnHeaderProps {
/**
* Renders the header section of a column, displaying a color-coded status indicator,
* the column name, the total task count badge, and a button to add new tasks.
* the column name, the total task count badge, and a button to add new tasks pre-filled with the column status.
*
* @param {ColumnHeaderProps} props - The component props.
* @returns {JSX.Element} The rendered column header component.
*/
export default function ColumnHeader({
id,
title,
color = "bg-primary",
count,
}: ColumnHeaderProps) {
const router = useRouter();
return (
<div className="flex items-center justify-between mb-4 pb-2 border-b border-border/40">
<div className="flex items-center gap-2.5">
@ -42,8 +49,12 @@ export default function ColumnHeader({
{count}
</span>
</div>
<button className="text-background hover:text-foreground p-1.5 rounded-lg bg-primary hover:bg-primary-hover transition-all duration-200 cursor-pointer shadow-sm active:scale-95">
<Plus size={14} />
<button
onClick={() => router.push(`/tasks?task=new&status=${id}`)}
className="text-black hover:text-foreground p-1 rounded-lg border border-black hover:border-foreground bg-primary hover:bg-primary-hover transition-all duration-200 cursor-pointer shadow-sm active:scale-90"
aria-label={`Add task to ${title}`}
>
<Plus size={16} />
</button>
</div>
);

View file

@ -1,11 +1,12 @@
/**
* @file dasboard/header/NewTaskButton.tsx
* @file dashboard/header/NewTaskButton.tsx
* @description Client component rendering an interactive trigger button for creating new tasks.
*/
"use client";
import { Plus } from "lucide-react";
import { useRouter } from "next/navigation";
/**
* Renders a stylized button component with an icon and active state animations to initiate task creation.
@ -13,10 +14,15 @@ import { Plus } from "lucide-react";
* @returns {JSX.Element} The rendered new task button component.
*/
export default function NewTaskButton() {
const router = useRouter();
return (
<button className="inline-flex items-center gap-2 px-3.5 py-2.5 rounded-xl font-medium text-sm text-black hover:text-foreground bg-primary hover:bg-primary-hover active:scale-95 transition-colors duration-200 cursor-pointer">
<Plus size={16} />
New Task
<button
onClick={() => router.push("/tasks?task=new")}
className="inline-flex items-center justify-center gap-2 px-3.5 py-2.5 rounded-xl font-medium text-sm text-black hover:text-foreground bg-primary hover:bg-primary-hover border border-black hover:border-foreground active:scale-95 transition-colors duration-200 cursor-pointer"
>
<Plus size={18} />
<span>New Task</span>
</button>
);
}

View file

@ -34,11 +34,11 @@ export default function TrashLink() {
return (
<Link
href="/trash"
className="relative group p-2 rounded-xl border border-border hover:border-primary transition-all duration-200"
className="relative group p-2 rounded-xl border border-border hover:border-primary transition-all duration-200 active:scale-95 cursor-pointer"
title="View Trash"
>
<Trash2
size={16}
size={18}
className="text-foreground-muted group-hover:text-primary transition-colors"
/>
{count > 0 && (

View file

@ -7,7 +7,7 @@
import { AlertCircle } from "lucide-react";
import Link from "next/link";
import { DbTask } from "@/types/task";
import { DbTask, TaskStatus } from "@/types/task";
import { useTaskForm } from "./useTaskForm";
import TaskHeader from "./task/TaskHeader";
import TaskBasicInfo from "./task/TaskBasicInfo";
@ -22,12 +22,15 @@ import TaskDateTime from "./task/TaskDateTime";
* @property {{ id: string; email: string }[]} users - The list of available users who can be assigned to the task.
* @property {DbTask & { assignees?: { id: string }[] }} [initialData] - Optional initial task data for editing an existing task.
* @property {string} [mode] - Optional mode indicator (e.g., create or edit).
* @property {string | null} [serverError] - Optional server-side error message.
* @property {TaskStatus} [defaultStatus] - Optional default status for new tasks.
*/
interface TaskFormProps {
users: { id: string; email: string }[];
initialData?: DbTask & { assignees?: { id: string }[] };
mode?: string;
serverError?: string | null;
defaultStatus?: TaskStatus;
}
/**
@ -37,7 +40,12 @@ interface TaskFormProps {
* @param {TaskFormProps} props - The component props.
* @returns {JSX.Element} The rendered task form component.
*/
export default function TaskForm({ users, initialData, mode }: TaskFormProps) {
export default function TaskForm({
users,
initialData,
mode,
defaultStatus,
}: TaskFormProps) {
const {
form,
error,
@ -46,7 +54,7 @@ export default function TaskForm({ users, initialData, mode }: TaskFormProps) {
updateField,
handleAssigneeToggle,
handleSubmit,
} = useTaskForm(initialData, mode);
} = useTaskForm(initialData, mode, defaultStatus);
return (
<form onSubmit={handleSubmit} className="space-y-6 mx-auto">

View file

@ -10,17 +10,19 @@ import { redirect } from "next/navigation";
import TaskForm from "./TaskForm";
import { not, eq } from "drizzle-orm";
import { TaskService } from "@/services/task.service";
import { TaskStatus } from "@/types/task";
/**
* Properties for the TaskPage component.
*
* @interface TaskPageProps
* @property {Promise<{ task?: string; id?: string; }>} searchParams - A promise resolving to the search parameters containing mode and task ID.
* @property {Promise<{ task?: string; id?: string; status?: string; }>} searchParams - A promise resolving to the search parameters containing mode, task ID, and status.
*/
interface TaskPageProps {
searchParams: Promise<{
task?: string;
id?: string;
status?: string;
}>;
}
@ -63,6 +65,7 @@ export default async function TaskPage({ searchParams }: TaskPageProps) {
users={users}
initialData={initialData}
mode={params.task}
defaultStatus={params.status as TaskStatus}
/>
</div>
);

View file

@ -11,13 +11,15 @@ import { TaskPriority, TaskStatus, DbTask } from "@/types/task";
* 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] - Optional initial task data for editing workflows.
* @param {string} [mode] - Operation mode indicator (e.g., "edit").
* @returns An object containing form state values, error states, and event handler functions.
* @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();
@ -32,7 +34,7 @@ export function useTaskForm(
title: initialData?.title ?? "",
description: initialData?.description ?? "",
priority: (initialData?.priority ?? "medium") as TaskPriority,
status: (initialData?.status ?? "todo") as TaskStatus,
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",