feat(calendar): görevleri takvimde göster
- Bitiş tarihi olan ve done olmayan görevler takvimde turuncu pill olarak gösteriliyor - Göreve tıklamak /tasks sayfasına yönlendiriyor, etkinlik formu açmıyor - COLOR_BG'ye 'task' rengi eklendi (orange)
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useMemo, useState, useTransition } from "react";
|
||||
import { ChevronLeft, ChevronRight, Loader2, Plus, Trash2 } from "lucide-react";
|
||||
import { ChevronLeft, ChevronRight, Loader2, Plus, SquareCheckBig, Trash2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -98,6 +99,7 @@ export function CalendarClient({ events, customers }: Props) {
|
||||
};
|
||||
|
||||
const handleEdit = (event: EventRow) => {
|
||||
if (event.source === "task") return;
|
||||
setEditing(event);
|
||||
setDefaultDate(undefined);
|
||||
setFormOpen(true);
|
||||
@@ -186,28 +188,43 @@ export function CalendarClient({ events, customers }: Props) {
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{dayEvents.slice(0, 3).map((e) => (
|
||||
<button
|
||||
key={e.id}
|
||||
type="button"
|
||||
onClick={() => handleEdit(e)}
|
||||
className={cn(
|
||||
"truncate rounded border px-1.5 py-0.5 text-left text-xs",
|
||||
COLOR_BG[e.color] ?? COLOR_BG[""],
|
||||
)}
|
||||
title={e.title}
|
||||
>
|
||||
{!e.allDay && (
|
||||
<span className="opacity-70">
|
||||
{new Date(e.start).toLocaleTimeString("tr-TR", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}{" "}
|
||||
</span>
|
||||
)}
|
||||
{e.title}
|
||||
</button>
|
||||
))}
|
||||
{dayEvents.slice(0, 3).map((e) =>
|
||||
e.source === "task" ? (
|
||||
<Link
|
||||
key={e.id}
|
||||
href="/tasks"
|
||||
className={cn(
|
||||
"flex items-center gap-1 truncate rounded border px-1.5 py-0.5 text-left text-xs",
|
||||
COLOR_BG["task"],
|
||||
)}
|
||||
title={e.title}
|
||||
>
|
||||
<SquareCheckBig className="size-3 shrink-0 opacity-70" />
|
||||
<span className="truncate">{e.title}</span>
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
key={e.id}
|
||||
type="button"
|
||||
onClick={() => handleEdit(e)}
|
||||
className={cn(
|
||||
"truncate rounded border px-1.5 py-0.5 text-left text-xs",
|
||||
COLOR_BG[e.color] ?? COLOR_BG[""],
|
||||
)}
|
||||
title={e.title}
|
||||
>
|
||||
{!e.allDay && (
|
||||
<span className="opacity-70">
|
||||
{new Date(e.start).toLocaleTimeString("tr-TR", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}{" "}
|
||||
</span>
|
||||
)}
|
||||
{e.title}
|
||||
</button>
|
||||
),
|
||||
)}
|
||||
{dayEvents.length > 3 && (
|
||||
<span className="text-muted-foreground px-1 text-xs">
|
||||
+{dayEvents.length - 3} daha
|
||||
|
||||
@@ -8,6 +8,8 @@ export type EventRow = {
|
||||
customerId: string;
|
||||
customerName: string;
|
||||
color: string;
|
||||
source?: "event" | "task";
|
||||
taskStatus?: string;
|
||||
};
|
||||
|
||||
export type Customer = { id: string; name: string };
|
||||
@@ -28,5 +30,6 @@ export const COLOR_BG: Record<string, string> = {
|
||||
red: "bg-red-500/15 text-red-700 dark:text-red-300 border-red-500/30",
|
||||
violet: "bg-violet-500/15 text-violet-700 dark:text-violet-300 border-violet-500/30",
|
||||
slate: "bg-slate-500/15 text-slate-700 dark:text-slate-300 border-slate-500/30",
|
||||
task: "bg-orange-500/15 text-orange-700 dark:text-orange-300 border-orange-500/30",
|
||||
"": "bg-primary/10 text-primary border-primary/20",
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { redirect } from "next/navigation";
|
||||
|
||||
import { listCalendarEvents } from "@/lib/appwrite/calendar-queries";
|
||||
import { listCustomers } from "@/lib/appwrite/customer-queries";
|
||||
import { listTasks } from "@/lib/appwrite/task-queries";
|
||||
import { requireTenant } from "@/lib/appwrite/tenant-guard";
|
||||
import { CalendarClient } from "./components/calendar-client";
|
||||
|
||||
@@ -18,13 +19,43 @@ export default async function CalendarPage() {
|
||||
redirect("/onboarding");
|
||||
}
|
||||
|
||||
const [events, customers] = await Promise.all([
|
||||
const [events, customers, tasks] = await Promise.all([
|
||||
listCalendarEvents(ctx.tenantId),
|
||||
listCustomers(ctx.tenantId),
|
||||
listTasks(ctx.tenantId),
|
||||
]);
|
||||
|
||||
const customerMap = new Map(customers.map((c) => [c.$id, c.name]));
|
||||
|
||||
const mappedEvents = events.map((e) => ({
|
||||
id: e.$id,
|
||||
title: e.title,
|
||||
description: e.description ?? "",
|
||||
start: e.start,
|
||||
end: e.end,
|
||||
allDay: Boolean(e.allDay),
|
||||
customerId: e.customerId ?? "",
|
||||
customerName: e.customerId ? customerMap.get(e.customerId) ?? "" : "",
|
||||
color: e.color ?? "",
|
||||
source: "event" as const,
|
||||
}));
|
||||
|
||||
const mappedTasks = tasks
|
||||
.filter((t) => t.dueDate && t.status !== "done")
|
||||
.map((t) => ({
|
||||
id: t.$id,
|
||||
title: t.title,
|
||||
description: t.description ?? "",
|
||||
start: t.dueDate!,
|
||||
end: t.dueDate!,
|
||||
allDay: true,
|
||||
customerId: "",
|
||||
customerName: "",
|
||||
color: "task",
|
||||
source: "task" as const,
|
||||
taskStatus: t.status,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="flex-1 space-y-6 px-6 pt-0">
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -36,17 +67,7 @@ export default async function CalendarPage() {
|
||||
</div>
|
||||
|
||||
<CalendarClient
|
||||
events={events.map((e) => ({
|
||||
id: e.$id,
|
||||
title: e.title,
|
||||
description: e.description ?? "",
|
||||
start: e.start,
|
||||
end: e.end,
|
||||
allDay: Boolean(e.allDay),
|
||||
customerId: e.customerId ?? "",
|
||||
customerName: e.customerId ? customerMap.get(e.customerId) ?? "" : "",
|
||||
color: e.color ?? "",
|
||||
}))}
|
||||
events={[...mappedEvents, ...mappedTasks]}
|
||||
customers={customers.map((c) => ({ id: c.$id, name: c.name }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user