feat: add dashboard client components for finance, journal, projects, tasks, analytics, calendar, clients, and main overview
This commit is contained in:
@@ -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 },
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 +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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user