feat: add dashboard client components for finance, journal, projects, tasks, analytics, calendar, clients, and main overview

This commit is contained in:
Poyraz
2026-06-16 18:45:51 +03:00
parent 5486a19d57
commit 3634b3a227
8 changed files with 82 additions and 33 deletions
@@ -58,8 +58,8 @@ export function AnalyticsClient({ data }: AnalyticsClientProps) {
const projectIncomeData = Array.from(projectIncomeMap.values()); const projectIncomeData = Array.from(projectIncomeMap.values());
// Task completion stats // Task completion stats
const completedTasks = filteredTasks.filter(t => t.status === "completed").length; const completedTasks = filteredTasks.filter(t => t.status === "done").length;
const activeTasks = filteredTasks.filter(t => t.status !== "completed" && t.status !== "cancelled").length; const activeTasks = filteredTasks.filter(t => t.status !== "done" && t.status !== "cancelled").length;
const taskStatusData = [ const taskStatusData = [
{ name: "Tamamlanan", value: completedTasks }, { name: "Tamamlanan", value: completedTasks },
+15 -6
View File
@@ -19,6 +19,7 @@ import {
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
toast,
} from "poyraz-ui/molecules"; } from "poyraz-ui/molecules";
import { CalendarDays, Clock, Pencil, Plus, Trash2 } from "lucide-react"; import { CalendarDays, Clock, Pencil, Plus, Trash2 } from "lucide-react";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
@@ -293,6 +294,13 @@ function CalendarEventDialog({
try { try {
await action(formData); await action(formData);
setOpen(false); setOpen(false);
toast.success(mode === "create" ? "Etkinlik eklendi." : "Etkinlik güncellendi.");
} catch (error) {
toast.error(
error instanceof Error
? error.message
: "Etkinlik kaydedilirken beklenmeyen bir hata oluştu.",
);
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }
@@ -306,20 +314,21 @@ function CalendarEventDialog({
{mode === "create" ? "Etkinlik ekle" : "Düzenle"} {mode === "create" ? "Etkinlik ekle" : "Düzenle"}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-[min(680px,calc(100dvh-6rem))] overflow-hidden sm:max-w-xl"> <DialogContent className="flex max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] flex-col overflow-hidden p-0 sm:max-h-[min(680px,calc(100dvh-4rem))] sm:max-w-xl data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95">
<form action={handleSubmit} className="flex max-h-[min(640px,calc(100dvh-9rem))] flex-col"> <form action={handleSubmit} className="flex min-h-0 flex-1 flex-col overflow-hidden">
{event ? <input type="hidden" name="id" value={event.id} /> : null} {event ? <input type="hidden" name="id" value={event.id} /> : null}
<DialogHeader className="shrink-0 pb-5"> <DialogHeader className="shrink-0 px-5 pb-4 pt-5 pr-12">
<DialogTitle>{mode === "create" ? "Yeni etkinlik" : "Etkinliği düzenle"}</DialogTitle> <DialogTitle>{mode === "create" ? "Yeni etkinlik" : "Etkinliği düzenle"}</DialogTitle>
<DialogDescription>Takvim etkinliğini proje, görev veya müşteriyle ilişkilendir.</DialogDescription> <DialogDescription>Takvim etkinliğini proje, görev veya müşteriyle ilişkilendir.</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="tiny-scrollbar min-h-0 flex-1 overflow-y-auto pr-2"> <div className="tiny-scrollbar min-h-0 flex-1 overflow-y-auto px-5 pb-5">
<EventFormFields event={event} defaultDate={defaultDate} clients={clients} projects={projects} tasks={tasks} /> <EventFormFields event={event} defaultDate={defaultDate} clients={clients} projects={projects} tasks={tasks} />
</div> </div>
<DialogFooter className="shrink-0 border-t border-border pt-5"> <DialogFooter className="shrink-0 border-t border-border bg-background p-5">
<Button type="submit" disabled={isSubmitting}> <Button type="submit" disabled={isSubmitting} className="w-full gap-2 sm:w-auto">
{mode === "create" ? <Plus className="h-4 w-4" /> : <Pencil className="h-4 w-4" />}
{isSubmitting ? "Kaydediliyor" : mode === "create" ? "Etkinliği ekle" : "Değişiklikleri kaydet"} {isSubmitting ? "Kaydediliyor" : mode === "create" ? "Etkinliği ekle" : "Değişiklikleri kaydet"}
</Button> </Button>
</DialogFooter> </DialogFooter>
+22 -6
View File
@@ -25,6 +25,7 @@ import {
TabsContent, TabsContent,
TabsList, TabsList,
TabsTrigger, TabsTrigger,
toast,
} from "poyraz-ui/molecules"; } from "poyraz-ui/molecules";
import { import {
Archive, Archive,
@@ -133,8 +134,14 @@ export function ClientsClient({
try { try {
await updateClientPipelineStage(clientId, newStage as any); await updateClientPipelineStage(clientId, newStage as any);
toast.success("Müşteri aşaması güncellendi.");
} catch (error) { } catch (error) {
setLocalClients(clients); setLocalClients(clients);
toast.error(
error instanceof Error
? error.message
: "Müşteri aşaması güncellenemedi.",
);
} }
} }
@@ -440,6 +447,13 @@ function ClientDialog({
try { try {
await action(formData); await action(formData);
setOpen(false); setOpen(false);
toast.success(mode === "create" ? "Müşteri eklendi." : "Müşteri güncellendi.");
} catch (error) {
toast.error(
error instanceof Error
? error.message
: "Müşteri kaydedilirken beklenmeyen bir hata oluştu.",
);
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }
@@ -458,10 +472,10 @@ function ClientDialog({
</Button> </Button>
)} )}
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-xl max-h-[90vh] overflow-y-auto"> <DialogContent className="flex max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] flex-col overflow-hidden p-0 sm:max-h-[min(680px,calc(100dvh-4rem))] sm:max-w-xl data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95">
<form action={handleSubmit} className="space-y-5"> <form action={handleSubmit} className="flex min-h-0 flex-1 flex-col overflow-hidden">
{client ? <input type="hidden" name="id" value={client.id} /> : null} {client ? <input type="hidden" name="id" value={client.id} /> : null}
<DialogHeader> <DialogHeader className="shrink-0 px-5 pb-4 pt-5 pr-12">
<DialogTitle> <DialogTitle>
{mode === "create" ? "Yeni müşteri" : "Müşteriyi düzenle"} {mode === "create" ? "Yeni müşteri" : "Müşteriyi düzenle"}
</DialogTitle> </DialogTitle>
@@ -470,10 +484,12 @@ function ClientDialog({
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<ClientFormFields client={client} /> <div className="tiny-scrollbar min-h-0 flex-1 overflow-y-auto px-5 pb-5">
<ClientFormFields client={client} />
</div>
<DialogFooter> <DialogFooter className="shrink-0 border-t border-border bg-background p-5">
<Button type="submit" disabled={isSubmitting} className="gap-2"> <Button type="submit" disabled={isSubmitting} className="w-full gap-2 sm:w-auto">
{mode === "create" ? <Plus className="h-4 w-4" /> : <Pencil className="h-4 w-4" />} {mode === "create" ? <Plus className="h-4 w-4" /> : <Pencil className="h-4 w-4" />}
{isSubmitting {isSubmitting
? "Kaydediliyor" ? "Kaydediliyor"
+1 -1
View File
@@ -50,7 +50,7 @@ export function DashboardClient({ data }: DashboardClientProps) {
const activeProjectsCount = (data.projects || []).filter(p => p.status === "active").length; const activeProjectsCount = (data.projects || []).filter(p => p.status === "active").length;
const completedTasksCount = (data.tasks || []).filter(t => t.status === "completed" && filterByDate(t.updated_at || t.created_at)).length; const completedTasksCount = (data.tasks || []).filter(t => t.status === "done" && filterByDate(t.updated_at || t.created_at)).length;
const filteredLogs = (data.logs || []).filter(l => filterByDate(l.log_date)); const filteredLogs = (data.logs || []).filter(l => filterByDate(l.log_date));
const avgMood = filteredLogs.length > 0 const avgMood = filteredLogs.length > 0
+15 -6
View File
@@ -19,6 +19,7 @@ import {
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
toast,
} from "poyraz-ui/molecules"; } from "poyraz-ui/molecules";
import { import {
ArrowDownRight, ArrowDownRight,
@@ -300,6 +301,13 @@ function FinanceDialog({
try { try {
await action(formData); await action(formData);
setOpen(false); setOpen(false);
toast.success(mode === "create" ? "İşlem eklendi." : "İşlem güncellendi.");
} catch (error) {
toast.error(
error instanceof Error
? error.message
: "Finans işlemi kaydedilirken beklenmeyen bir hata oluştu.",
);
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }
@@ -313,18 +321,19 @@ function FinanceDialog({
{mode === "create" ? "İşlem ekle" : "Düzenle"} {mode === "create" ? "İşlem ekle" : "Düzenle"}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-[min(680px,calc(100dvh-6rem))] overflow-hidden sm:max-w-xl"> <DialogContent className="flex max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] flex-col overflow-hidden p-0 sm:max-h-[min(680px,calc(100dvh-4rem))] sm:max-w-xl data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95">
<form action={handleSubmit} className="flex max-h-[min(640px,calc(100dvh-9rem))] flex-col"> <form action={handleSubmit} className="flex min-h-0 flex-1 flex-col overflow-hidden">
{transaction ? <input type="hidden" name="id" value={transaction.id} /> : null} {transaction ? <input type="hidden" name="id" value={transaction.id} /> : null}
<DialogHeader className="shrink-0 pb-5"> <DialogHeader className="shrink-0 px-5 pb-4 pt-5 pr-12">
<DialogTitle>{mode === "create" ? "Yeni finans işlemi" : "Finans işlemini düzenle"}</DialogTitle> <DialogTitle>{mode === "create" ? "Yeni finans işlemi" : "Finans işlemini düzenle"}</DialogTitle>
<DialogDescription>Gelir veya gider kaydını müşteri/proje bağlantısıyla kaydet.</DialogDescription> <DialogDescription>Gelir veya gider kaydını müşteri/proje bağlantısıyla kaydet.</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="tiny-scrollbar min-h-0 flex-1 overflow-y-auto pr-2"> <div className="tiny-scrollbar min-h-0 flex-1 overflow-y-auto px-5 pb-5">
<FinanceFormFields transaction={transaction} clients={clients} projects={projects} /> <FinanceFormFields transaction={transaction} clients={clients} projects={projects} />
</div> </div>
<DialogFooter className="shrink-0 border-t border-border pt-5"> <DialogFooter className="shrink-0 border-t border-border bg-background p-5">
<Button type="submit" disabled={isSubmitting}> <Button type="submit" disabled={isSubmitting} className="w-full gap-2 sm:w-auto">
{mode === "create" ? <Plus className="h-4 w-4" /> : <Pencil className="h-4 w-4" />}
{isSubmitting ? "Kaydediliyor" : mode === "create" ? "İşlemi ekle" : "Değişiklikleri kaydet"} {isSubmitting ? "Kaydediliyor" : mode === "create" ? "İşlemi ekle" : "Değişiklikleri kaydet"}
</Button> </Button>
</DialogFooter> </DialogFooter>
+14 -6
View File
@@ -14,6 +14,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
toast,
} from "poyraz-ui/molecules"; } from "poyraz-ui/molecules";
import { import {
Activity, Activity,
@@ -259,6 +260,13 @@ function DailyLogDialog({ mode, log }: { mode: "create" | "edit"; log?: DailyLog
try { try {
await action(formData); await action(formData);
setOpen(false); setOpen(false);
toast.success(mode === "create" ? "Günlük eklendi." : "Günlük güncellendi.");
} catch (error) {
toast.error(
error instanceof Error
? error.message
: "Günlük kaydedilirken beklenmeyen bir hata oluştu.",
);
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }
@@ -272,22 +280,22 @@ function DailyLogDialog({ mode, log }: { mode: "create" | "edit"; log?: DailyLog
{mode === "create" ? "Günlük ekle" : "Düzenle"} {mode === "create" ? "Günlük ekle" : "Düzenle"}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-[min(640px,calc(100dvh-6rem))] overflow-hidden sm:max-w-xl data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95"> <DialogContent className="flex max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] flex-col overflow-hidden p-0 sm:max-h-[min(680px,calc(100dvh-4rem))] sm:max-w-xl data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95">
<form action={handleSubmit} className="flex max-h-[min(600px,calc(100dvh-9rem))] flex-col"> <form action={handleSubmit} className="flex min-h-0 flex-1 flex-col overflow-hidden">
{log ? <input type="hidden" name="id" value={log.id} /> : null} {log ? <input type="hidden" name="id" value={log.id} /> : null}
<DialogHeader className="shrink-0 pb-5"> <DialogHeader className="shrink-0 px-5 pb-4 pt-5 pr-12">
<DialogTitle>{mode === "create" ? "Yeni günlük kayıt" : "Günlük kaydı düzenle"}</DialogTitle> <DialogTitle>{mode === "create" ? "Yeni günlük kayıt" : "Günlük kaydı düzenle"}</DialogTitle>
<DialogDescription> <DialogDescription>
Günün mood, enerji ve çalışma memnuniyeti skorlarını kaydet. Günün mood, enerji ve çalışma memnuniyeti skorlarını kaydet.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="tiny-scrollbar min-h-0 flex-1 overflow-y-auto pr-2"> <div className="tiny-scrollbar min-h-0 flex-1 overflow-y-auto px-5 pb-5">
<DailyLogFormFields log={log} /> <DailyLogFormFields log={log} />
</div> </div>
<DialogFooter className="shrink-0 border-t border-border pt-5"> <DialogFooter className="shrink-0 border-t border-border bg-background p-5">
<Button type="submit" disabled={isSubmitting} className="gap-2"> <Button type="submit" disabled={isSubmitting} className="w-full gap-2 sm:w-auto">
{isSubmitting ? "Kaydediliyor" : mode === "create" ? "Kaydı ekle" : "Değişiklikleri kaydet"} {isSubmitting ? "Kaydediliyor" : mode === "create" ? "Kaydı ekle" : "Değişiklikleri kaydet"}
</Button> </Button>
</DialogFooter> </DialogFooter>
+6 -6
View File
@@ -448,17 +448,17 @@ function ProjectDialog({
{iconOnly ? null : mode === "create" ? "Proje ekle" : "Düzenle"} {iconOnly ? null : mode === "create" ? "Proje ekle" : "Düzenle"}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-[min(720px,calc(100dvh-6rem))] overflow-hidden sm:max-w-2xl data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95"> <DialogContent className="flex max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] flex-col overflow-hidden p-0 sm:max-h-[min(680px,calc(100dvh-4rem))] sm:max-w-2xl data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95">
<form action={handleSubmit} className="flex max-h-[min(680px,calc(100dvh-9rem))] flex-col"> <form action={handleSubmit} className="flex min-h-0 flex-1 flex-col overflow-hidden">
{project ? <input type="hidden" name="id" value={project.id} /> : null} {project ? <input type="hidden" name="id" value={project.id} /> : null}
<DialogHeader className="shrink-0 pb-5"> <DialogHeader className="shrink-0 px-5 pb-4 pt-5 pr-12">
<DialogTitle>{mode === "create" ? "Yeni proje" : "Projeyi düzenle"}</DialogTitle> <DialogTitle>{mode === "create" ? "Yeni proje" : "Projeyi düzenle"}</DialogTitle>
<DialogDescription> <DialogDescription>
Müşteri projelerini ve kişisel side projectleri aynı modelde takip et. Müşteri projelerini ve kişisel side projectleri aynı modelde takip et.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="tiny-scrollbar min-h-0 flex-1 overflow-y-auto pr-2"> <div className="tiny-scrollbar min-h-0 flex-1 overflow-y-auto px-5 pb-5">
<ProjectFormFields <ProjectFormFields
project={project} project={project}
clients={clients} clients={clients}
@@ -467,8 +467,8 @@ function ProjectDialog({
/> />
</div> </div>
<DialogFooter className="shrink-0 border-t border-border pt-5"> <DialogFooter className="shrink-0 border-t border-border bg-background p-5">
<Button type="submit" disabled={isSubmitting} className="gap-2"> <Button type="submit" disabled={isSubmitting} className="w-full gap-2 sm:w-auto">
{mode === "create" ? <Plus className="h-4 w-4" /> : <Pencil className="h-4 w-4" />} {mode === "create" ? <Plus className="h-4 w-4" /> : <Pencil className="h-4 w-4" />}
{isSubmitting {isSubmitting
? "Kaydediliyor" ? "Kaydediliyor"
+7
View File
@@ -551,6 +551,13 @@ function TaskDialog({
try { try {
await action(formData); await action(formData);
setOpen(false); setOpen(false);
toast.success(mode === "create" ? "Görev eklendi." : "Görev güncellendi.");
} catch (error) {
toast.error(
error instanceof Error
? error.message
: "Görev kaydedilirken beklenmeyen bir hata oluştu.",
);
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }