feat: implement analytics dashboard page with custom Recharts visualizations and Supabase RPC integration

This commit is contained in:
Poyraz
2026-06-17 09:20:21 +03:00
parent 3634b3a227
commit 3504a02229
8 changed files with 463 additions and 145 deletions
+16 -40
View File
@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { Card, CardContent } from "poyraz-ui/atoms";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "poyraz-ui/molecules";
import {
@@ -10,9 +10,12 @@ import {
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 }[];
metrics: {
projectIncomeData: { name: string; value: number }[];
completedTasks: number;
activeTasks: number;
};
range: string;
};
type AnalyticsClientProps = {
@@ -22,44 +25,17 @@ type AnalyticsClientProps = {
const COLORS = ["hsl(var(--primary))", "hsl(var(--destructive))", "#eab308", "#3b82f6", "#8b5cf6"];
export function AnalyticsClient({ data }: AnalyticsClientProps) {
const [dateRange, setDateRange] = useState("this_month");
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
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 handleRangeChange = (newRange: string) => {
const params = new URLSearchParams(searchParams.toString());
params.set("range", newRange);
router.push(`${pathname}?${params.toString()}`);
};
const filteredFinances = data.finances.filter(f => filterByDate(f.transaction_date));
const filteredTasks = data.tasks.filter(t => filterByDate(t.created_at || t.due_at || null));
// 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 === "done").length;
const activeTasks = filteredTasks.filter(t => t.status !== "done" && t.status !== "cancelled").length;
const { projectIncomeData, completedTasks, activeTasks } = data.metrics;
const taskStatusData = [
{ name: "Tamamlanan", value: completedTasks },
@@ -85,7 +61,7 @@ export function AnalyticsClient({ data }: AnalyticsClientProps) {
</div>
<div className="flex items-center gap-3">
<Select value={dateRange} onValueChange={setDateRange}>
<Select value={data.range} onValueChange={handleRangeChange}>
<SelectTrigger className="w-[160px]">
<SelectValue placeholder="Tarih aralığı" />
</SelectTrigger>
+38 -17
View File
@@ -6,7 +6,11 @@ export const metadata = {
title: "Analizler - Neta",
};
export default async function AnalyticsPage() {
export default async function AnalyticsPage({
searchParams,
}: {
searchParams: { [key: string]: string | string[] | undefined };
}) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
@@ -14,24 +18,41 @@ export default async function AnalyticsPage() {
redirect("/login");
}
// Fetch all analytics data in parallel with only needed columns
const [{ data: tasks }, { data: projects }, { data: finances }] =
await Promise.all([
supabase
.from("tasks")
.select("id, status, created_at, due_at"),
supabase
.from("projects")
.select("id, name"),
supabase
.from("finance_transactions")
.select("id, type, amount, transaction_date, project_id"),
]);
const range = typeof searchParams.range === "string" ? searchParams.range : "this_month";
const now = new Date();
let startDate = new Date();
let endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59);
if (range === "this_week") {
const tempNow = new Date();
const firstDay = new Date(tempNow.setDate(tempNow.getDate() - tempNow.getDay() + (tempNow.getDay() === 0 ? -6 : 1)));
firstDay.setHours(0, 0, 0, 0);
startDate = firstDay;
endDate = new Date(firstDay.getTime());
endDate.setDate(endDate.getDate() + 6);
endDate.setHours(23, 59, 59, 999);
} else if (range === "this_month") {
startDate = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0);
endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59);
} else if (range === "this_year") {
startDate = new Date(now.getFullYear(), 0, 1, 0, 0, 0);
endDate = new Date(now.getFullYear(), 11, 31, 23, 59, 59);
}
// Fetch metrics using RPC
const { data: metricsData } = await supabase.rpc('get_analytics_metrics', {
p_start_date: startDate.toISOString(),
p_end_date: endDate.toISOString()
});
const analyticsData = {
tasks: tasks || [],
projects: projects || [],
finances: finances || [],
metrics: metricsData || {
projectIncomeData: [],
completedTasks: 0,
activeTasks: 0
},
range
};
return <AnalyticsClient data={analyticsData} />;