feat(tasks): pre-fill task status when creating from column header and route new task button
This commit is contained in:
parent
dd2a8a5637
commit
3e7d943fe0
7 changed files with 49 additions and 18 deletions
|
|
@ -86,6 +86,7 @@ export default function Column(props: ColumnProps) {
|
|||
}`}
|
||||
>
|
||||
<ColumnHeader
|
||||
id={props.id}
|
||||
title={props.title}
|
||||
color={props.color}
|
||||
count={props.count}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue