refactor: decouple analytics page into server-side data fetching and client-side presentation components

This commit is contained in:
poyrazavsever
2026-06-06 20:14:16 +03:00
parent 4dbe8ff98b
commit 1cd00d4efe
4 changed files with 551 additions and 628 deletions
@@ -0,0 +1,166 @@
"use client";
import { useState } from "react";
import { Card, CardContent } from "poyraz-ui/atoms";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "poyraz-ui/molecules";
import {
Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis,
PieChart, Pie, Cell, Legend
} from "recharts";
import { BarChart3, Filter } from "lucide-react";
export type AnalyticsData = {
tasks: { id: string; status: string; created_at: string; due_at?: string }[];
projects: { id: string; name: string }[];
finances: { id: string; type: string; amount: number; transaction_date: string; project_id?: string }[];
};
type AnalyticsClientProps = {
data: AnalyticsData;
};
const COLORS = ["hsl(var(--primary))", "hsl(var(--destructive))", "#eab308", "#3b82f6", "#8b5cf6"];
export function AnalyticsClient({ data }: AnalyticsClientProps) {
const [dateRange, setDateRange] = useState("this_month");
const now = new Date();
const filterByDate = (dateStr: string | null) => {
if (!dateStr) return false;
const date = new Date(dateStr);
if (dateRange === "this_week") {
const firstDay = new Date(now.setDate(now.getDate() - now.getDay() + (now.getDay() === 0 ? -6 : 1)));
return date >= firstDay;
}
if (dateRange === "this_month") {
return date.getMonth() === now.getMonth() && date.getFullYear() === now.getFullYear();
}
if (dateRange === "this_year") {
return date.getFullYear() === now.getFullYear();
}
return true;
};
const filteredFinances = data.finances.filter(f => filterByDate(f.transaction_date));
const filteredTasks = data.tasks.filter(t => filterByDate(t.created_at || t.due_at));
// Project-based income
const projectIncomeMap = new Map();
filteredFinances.filter(f => f.type === "income" && f.project_id).forEach(f => {
const project = data.projects.find(p => p.id === f.project_id);
const name = project ? project.name : "Bilinmeyen";
if (!projectIncomeMap.has(name)) {
projectIncomeMap.set(name, { name, value: 0 });
}
projectIncomeMap.get(name).value += Number(f.amount);
});
const projectIncomeData = Array.from(projectIncomeMap.values());
// Task completion stats
const completedTasks = filteredTasks.filter(t => t.status === "completed").length;
const activeTasks = filteredTasks.filter(t => t.status !== "completed" && t.status !== "cancelled").length;
const taskStatusData = [
{ name: "Tamamlanan", value: completedTasks },
{ name: "Devam Eden", value: activeTasks }
];
return (
<div className="mx-auto flex max-w-7xl flex-col gap-6">
<div className="flex flex-col gap-4 border-b border-border pb-5 lg:flex-row lg:items-end lg:justify-between">
<div className="space-y-2">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<BarChart3 className="h-4 w-4" />
Analizler
</div>
<div>
<h1 className="text-3xl font-semibold tracking-normal text-foreground">
Performans ve Finans Analizi
</h1>
<p className="mt-1 max-w-2xl text-sm text-muted-foreground">
Müşteri bazlı gelirler, görev tamamlama oranları ve proje ilerleme grafikleri.
</p>
</div>
</div>
<div className="flex items-center gap-3">
<Select value={dateRange} onValueChange={setDateRange}>
<SelectTrigger className="w-[160px]">
<SelectValue placeholder="Tarih aralığı" />
</SelectTrigger>
<SelectContent>
<SelectItem value="this_week">Bu Hafta</SelectItem>
<SelectItem value="this_month">Bu Ay</SelectItem>
<SelectItem value="this_year">Bu Yıl</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-2">
<Card>
<CardContent className="p-6">
<h3 className="mb-6 text-sm font-semibold text-foreground">Proje Bazlı Gelir Dağılımı</h3>
<div className="h-[300px] w-full">
{projectIncomeData.length > 0 ? (
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={projectIncomeData}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
paddingAngle={5}
dataKey="value"
>
{projectIncomeData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip
formatter={(value: number) => `${value}`}
contentStyle={{
backgroundColor: 'hsl(var(--background))',
borderColor: 'hsl(var(--border))',
borderRadius: '0.375rem',
}}
/>
<Legend />
</PieChart>
</ResponsiveContainer>
) : (
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">Veri bulunamadı.</div>
)}
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<h3 className="mb-6 text-sm font-semibold text-foreground">Görev Durumu Analizi</h3>
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={taskStatusData}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="hsl(var(--border))" />
<XAxis dataKey="name" axisLine={false} tickLine={false} tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }} dy={10} />
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }} dx={-10} />
<Tooltip
cursor={{ fill: 'hsl(var(--muted))', opacity: 0.4 }}
contentStyle={{
backgroundColor: 'hsl(var(--background))',
borderColor: 'hsl(var(--border))',
borderRadius: '0.375rem',
}}
/>
<Bar dataKey="value" fill="hsl(var(--primary))" radius={[4, 4, 0, 0]} barSize={60} />
</BarChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
</div>
</div>
);
}