xtablo-source/apps/main/src/components/DashboardTaskList.tsx
Arthur Belleville e8044182d8
fix: resolve lint and formatting issues in apps/main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:33:38 +02:00

182 lines
6.1 KiB
TypeScript

import { cn } from "@xtablo/shared";
import type { KanbanTask, TaskStatus } from "@xtablo/shared-types";
import { TaskModal } from "@xtablo/tablo-views";
import { CheckCircle2, Plus } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { useTablosList } from "../hooks/tablos";
import { useAllTasks, useUpdateTask } from "../hooks/tasks";
import { useUser } from "../providers/UserStoreProvider";
type TaskWithTablo = KanbanTask & {
tablos: { id: string; name: string; color: string | null } | null;
};
const STATUS_BADGE: Record<TaskStatus, { className: string; labelKey: string }> = {
todo: {
className: "bg-blue-50 text-blue-600 dark:bg-blue-950/30 dark:text-blue-400",
labelKey: "dashboard.taskList.status.todo",
},
in_progress: {
className: "bg-yellow-50 text-yellow-600 dark:bg-yellow-950/30 dark:text-yellow-400",
labelKey: "dashboard.taskList.status.inProgress",
},
in_review: {
className: "bg-purple-50 text-purple-600 dark:bg-purple-950/30 dark:text-purple-400",
labelKey: "dashboard.taskList.status.inReview",
},
done: {
className: "bg-green-50 text-green-600 dark:bg-green-950/30 dark:text-green-400",
labelKey: "dashboard.taskList.status.done",
},
};
function TaskRow({
task,
onToggleDone,
}: {
task: TaskWithTablo;
onToggleDone: (task: TaskWithTablo) => void;
}) {
const { t } = useTranslation("pages");
const navigate = useNavigate();
const status = task.status ?? "todo";
const isDone = status === "done";
const badge = STATUS_BADGE[status];
const dateStr = task.updated_at ?? task.created_at;
const formattedDate = dateStr
? new Intl.DateTimeFormat(undefined, {
month: "short",
day: "numeric",
year: "numeric",
}).format(new Date(dateStr))
: "";
return (
<div
className="flex items-center gap-3 px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors border-b border-gray-200 dark:border-gray-700 cursor-pointer"
onClick={() => {
if (task.tablos) {
navigate(`/tablos/${task.tablos.id}?section=tasks`);
}
}}
>
{/* Checkbox */}
<button
className={cn(
"w-8 h-8 min-h-[44px] min-w-[44px] rounded-full border-2 flex items-center justify-center shrink-0",
isDone
? "bg-purple-600 border-purple-600"
: "border-gray-300 hover:border-purple-400 dark:border-gray-600 dark:hover:border-purple-500"
)}
onClick={(e) => {
e.stopPropagation();
onToggleDone(task);
}}
>
{isDone && <CheckCircle2 className="w-4 h-4 text-white" />}
</button>
{/* Title + Tablo (stacked on mobile) */}
<div className="flex-1 min-w-0">
<p
className={cn(
"text-sm font-medium truncate",
isDone
? "line-through text-gray-400 dark:text-gray-500"
: "text-gray-900 dark:text-gray-100"
)}
>
{task.title}
</p>
<div className="flex items-center gap-2 mt-1">
{task.tablos && (
<>
<div
className={cn(
"w-4 h-4 rounded flex items-center justify-center text-xs shrink-0",
task.tablos.color || "bg-gray-400"
)}
>
<span className="text-white font-bold text-[8px]">
{task.tablos.name.charAt(0).toUpperCase()}
</span>
</div>
<span className="text-xs text-gray-500 dark:text-gray-400 truncate">
{task.tablos.name}
</span>
</>
)}
{formattedDate && (
<span className="text-xs text-gray-400 dark:text-gray-500 hidden sm:inline whitespace-nowrap">
{formattedDate}
</span>
)}
</div>
</div>
{/* Status badge */}
<span
className={cn(
"px-2 sm:px-3 py-1 rounded-full text-xs font-medium whitespace-nowrap shrink-0",
badge.className
)}
>
{t(badge.labelKey)}
</span>
</div>
);
}
export function DashboardTaskList() {
const { t } = useTranslation("pages");
const user = useUser();
const { data: allTasks } = useAllTasks();
const { data: tablos } = useTablosList();
const updateTask = useUpdateTask();
const [isTaskModalOpen, setIsTaskModalOpen] = useState(false);
// Filter to tasks assigned to the current user, limited to recent ones
const myTasks = allTasks?.filter((task) => task.assignee_id === user.id).slice(0, 7) ?? [];
const handleToggleDone = (task: TaskWithTablo) => {
const newStatus: TaskStatus = task.status === "done" ? "todo" : "done";
updateTask.mutate({ id: task.id, status: newStatus });
};
if (myTasks.length === 0) return null;
return (
<>
<div className="bg-white dark:bg-gray-800 rounded-2xl border border-gray-100 dark:border-gray-700">
<div className="flex items-center justify-between px-4 py-4 sm:py-5 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-xl sm:text-2xl font-semibold text-gray-900 dark:text-gray-100">
{t("dashboard.taskList.title")}
</h2>
<button
className="flex items-center gap-2 px-4 py-2.5 min-h-[44px] bg-white dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300"
onClick={() => setIsTaskModalOpen(true)}
>
<Plus className="w-4 h-4" />
<span>{t("dashboard.taskList.addTask")}</span>
</button>
</div>
<div>
{myTasks.map((task) => (
<TaskRow key={task.id} task={task} onToggleDone={handleToggleDone} />
))}
</div>
</div>
<TaskModal
isOpen={isTaskModalOpen}
onClose={() => setIsTaskModalOpen(false)}
tablos={tablos}
allowTabloSelection
initialStatus="todo"
/>
</>
);
}