diff --git a/src/app/(dashboard)/dashboard/components/metrics.tsx b/src/app/(dashboard)/dashboard/components/metrics.tsx index 2315a4e..42a6a35 100644 --- a/src/app/(dashboard)/dashboard/components/metrics.tsx +++ b/src/app/(dashboard)/dashboard/components/metrics.tsx @@ -55,10 +55,14 @@ export function Metrics({ data }: { data: DashboardData["metrics"] }) { tone: data.overdueCount > 0 ? "warning" : "default", }, { - label: "Açık görevler", + label: "Açık görevlerim", value: String(data.openTasks), sub: - data.urgentTasks > 0 ? `${data.urgentTasks} acil` : "Acil görev yok", + data.urgentTasks > 0 + ? `${data.urgentTasks} acil` + : data.openTasks === 0 + ? "Hepsi tamam" + : "Atanmış + atanmamış", icon: CheckSquare, tone: data.urgentTasks > 0 ? "warning" : "default", }, diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index 9195007..fa947f3 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -14,7 +14,7 @@ export default async function DashboardPage() { const ctx = await getActiveContext(); if (!ctx) redirect("/onboarding"); - const data = await getDashboardData(ctx.tenantId); + const data = await getDashboardData(ctx.tenantId, ctx.user.id); const firstName = ctx.user.name?.split(" ")[0] ?? ""; const companyName = ctx.settings?.companyName ?? "Çalışma alanı"; diff --git a/src/app/(dashboard)/tasks/components/task-card.tsx b/src/app/(dashboard)/tasks/components/task-card.tsx index 2f44810..e78c04a 100644 --- a/src/app/(dashboard)/tasks/components/task-card.tsx +++ b/src/app/(dashboard)/tasks/components/task-card.tsx @@ -22,9 +22,12 @@ type Props = { onEdit: (task: TaskRow) => void; onDelete: (task: TaskRow) => void; isOverlay?: boolean; + currentUserId?: string; }; -export function TaskCard({ task, onEdit, onDelete, isOverlay }: Props) { +export function TaskCard({ task, onEdit, onDelete, isOverlay, currentUserId }: Props) { + const assignedToMe = currentUserId && task.assigneeId === currentUserId; + const unassigned = !task.assigneeId; const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: task.id, data: { type: "task", status: task.status } }); @@ -42,6 +45,8 @@ export function TaskCard({ task, onEdit, onDelete, isOverlay }: Props) { style={style} className={cn( "bg-card group rounded-lg border p-3 shadow-sm", + assignedToMe && "border-primary/40 ring-primary/20 ring-1", + unassigned && "border-dashed", isDragging && "opacity-30", isOverlay && "rotate-3 shadow-xl", )} @@ -110,11 +115,23 @@ export function TaskCard({ task, onEdit, onDelete, isOverlay }: Props) { )} - {task.assigneeName && ( + {assignedToMe ? ( + + + Bana atanmış + + ) : task.assigneeName ? ( {task.assigneeName} + ) : ( + + Atanmamış + )} diff --git a/src/app/(dashboard)/tasks/components/tasks-board.tsx b/src/app/(dashboard)/tasks/components/tasks-board.tsx index 6ad78d7..40c14d5 100644 --- a/src/app/(dashboard)/tasks/components/tasks-board.tsx +++ b/src/app/(dashboard)/tasks/components/tasks-board.tsx @@ -1,6 +1,7 @@ "use client"; import { useMemo, useState } from "react"; +import { useRouter } from "next/navigation"; import { DndContext, type DragEndEvent, @@ -17,6 +18,13 @@ import { Loader2, Plus, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { Dialog, DialogContent, @@ -33,16 +41,28 @@ import { TaskFormSheet } from "./task-form-sheet"; import { COLUMNS, type Customer, + FILTER_LABEL, + type TaskFilter, type TaskRow, type TaskStatus, type TeamMember, } from "./types"; import { useTransition } from "react"; +type FilterCounts = { + total: number; + mine: number; + unassigned: number; + mineOrUnassigned: number; +}; + type Props = { tasks: TaskRow[]; customers: Customer[]; teamMembers: TeamMember[]; + currentUserId: string; + filter: TaskFilter; + filterCounts: FilterCounts; }; function Column({ @@ -52,6 +72,7 @@ function Column({ onAdd, onEdit, onDelete, + currentUserId, }: { status: TaskStatus; title: string; @@ -59,6 +80,7 @@ function Column({ onAdd: (status: TaskStatus) => void; onEdit: (task: TaskRow) => void; onDelete: (task: TaskRow) => void; + currentUserId: string; }) { const { setNodeRef, isOver } = useDroppableColumn(status); @@ -94,7 +116,13 @@ function Column({ strategy={verticalListSortingStrategy} > {tasks.map((task) => ( - + ))} {tasks.length === 0 && ( @@ -112,7 +140,14 @@ function useDroppableColumn(status: TaskStatus) { }); } -export function TasksBoard({ tasks: initialTasks, customers, teamMembers }: Props) { +export function TasksBoard({ + tasks: initialTasks, + customers, + teamMembers, + currentUserId, + filter, + filterCounts, +}: Props) { const [tasks, setTasks] = useState(initialTasks); const [activeId, setActiveId] = useState(null); const [formOpen, setFormOpen] = useState(false); @@ -230,9 +265,38 @@ export function TasksBoard({ tasks: initialTasks, customers, teamMembers }: Prop }); }; + const router = useRouter(); + const setFilter = (value: TaskFilter) => { + const params = new URLSearchParams(); + if (value !== "mine_or_unassigned") params.set("view", value); + router.push(`/tasks${params.size ? `?${params}` : ""}`); + }; + return ( <> -
+
+
+ +
+
@@ -269,6 +334,7 @@ export function TasksBoard({ tasks: initialTasks, customers, teamMembers }: Prop onEdit={() => {}} onDelete={() => {}} isOverlay + currentUserId={currentUserId} /> )} diff --git a/src/app/(dashboard)/tasks/components/types.ts b/src/app/(dashboard)/tasks/components/types.ts index 3a45594..45755e6 100644 --- a/src/app/(dashboard)/tasks/components/types.ts +++ b/src/app/(dashboard)/tasks/components/types.ts @@ -18,6 +18,15 @@ export type TaskRow = { export type Customer = { id: string; name: string }; export type TeamMember = { id: string; name: string }; +export type TaskFilter = "all" | "mine" | "unassigned" | "mine_or_unassigned"; + +export const FILTER_LABEL: Record = { + all: "Hepsi", + mine: "Bana atanmış", + unassigned: "Atanmamış", + mine_or_unassigned: "Bana atanmış + Atanmamış", +}; + export const COLUMNS: { key: TaskStatus; title: string }[] = [ { key: "backlog", title: "Beklemede" }, { key: "todo", title: "Yapılacak" }, diff --git a/src/app/(dashboard)/tasks/page.tsx b/src/app/(dashboard)/tasks/page.tsx index b3f568e..d616e8a 100644 --- a/src/app/(dashboard)/tasks/page.tsx +++ b/src/app/(dashboard)/tasks/page.tsx @@ -6,12 +6,19 @@ import { listTasks } from "@/lib/appwrite/task-queries"; import { requireTenant } from "@/lib/appwrite/tenant-guard"; import { createAdminClient } from "@/lib/appwrite/server"; import { TasksBoard } from "./components/tasks-board"; +import type { TaskFilter } from "./components/types"; export const metadata: Metadata = { title: "İşletmem — Görevler", }; -export default async function TasksPage() { +const ALLOWED_FILTERS: TaskFilter[] = ["all", "mine", "unassigned", "mine_or_unassigned"]; + +export default async function TasksPage({ + searchParams, +}: { + searchParams: Promise<{ view?: string }>; +}) { let ctx; try { ctx = await requireTenant(); @@ -19,11 +26,35 @@ export default async function TasksPage() { redirect("/onboarding"); } - const [tasks, customers] = await Promise.all([ + const sp = await searchParams; + const filter: TaskFilter = + (ALLOWED_FILTERS as string[]).includes(sp.view ?? "") + ? (sp.view as TaskFilter) + : "mine_or_unassigned"; + + const [allTasks, customers] = await Promise.all([ listTasks(ctx.tenantId), listCustomers(ctx.tenantId), ]); + const tasks = allTasks.filter((t) => { + const assignee = t.assigneeId ?? ""; + if (filter === "mine") return assignee === ctx.user.id; + if (filter === "unassigned") return !assignee; + if (filter === "mine_or_unassigned") + return !assignee || assignee === ctx.user.id; + return true; + }); + + const filterCounts = { + total: allTasks.length, + mine: allTasks.filter((t) => t.assigneeId === ctx.user.id).length, + unassigned: allTasks.filter((t) => !t.assigneeId).length, + mineOrUnassigned: allTasks.filter( + (t) => !t.assigneeId || t.assigneeId === ctx.user.id, + ).length, + }; + let teamMembers: { id: string; name: string }[] = []; try { const { teams } = createAdminClient(); @@ -45,7 +76,8 @@ export default async function TasksPage() {

{ctx.settings?.companyName ?? "Çalışma alanı"}

Görevler

- Sürükle-bırak ile durumları değiştirin, ekibinizle iş takibini kolaylaştırın. + Sürükle-bırak ile durumları değiştirin. Üstteki filtreden başkalarına atanmış + görevleri de görebilirsiniz.

@@ -65,6 +97,9 @@ export default async function TasksPage() { }))} customers={customers.map((c) => ({ id: c.$id, name: c.name }))} teamMembers={teamMembers} + currentUserId={ctx.user.id} + filter={filter} + filterCounts={filterCounts} /> ); diff --git a/src/lib/appwrite/dashboard-queries.ts b/src/lib/appwrite/dashboard-queries.ts index 832f70e..72d1ae8 100644 --- a/src/lib/appwrite/dashboard-queries.ts +++ b/src/lib/appwrite/dashboard-queries.ts @@ -47,7 +47,10 @@ function monthLabel(d: Date): string { return MONTH_SHORT[d.getMonth()]; } -export async function getDashboardData(tenantId: string): Promise { +export async function getDashboardData( + tenantId: string, + currentUserId?: string, +): Promise { const { tablesDB } = createAdminClient(); const [customers, invoices, financeEntries, tasks, services] = await Promise.all([ @@ -129,10 +132,14 @@ export async function getDashboardData(tenantId: string): Promise let openTasks = 0; let urgentTasks = 0; for (const t of taskList) { - if ((t.status ?? "todo") !== "done") { - openTasks += 1; - if ((t.priority ?? "medium") === "urgent") urgentTasks += 1; + if ((t.status ?? "todo") === "done") continue; + // Personal scope: own assigned + unassigned. Falls back to all if no userId. + if (currentUserId) { + const assignee = t.assigneeId ?? ""; + if (assignee && assignee !== currentUserId) continue; } + openTasks += 1; + if ((t.priority ?? "medium") === "urgent") urgentTasks += 1; } const activeCustomers = customerList.filter((c) => (c.status ?? "active") === "active").length;