From f4fa966d894a2c13ad3d25bb1a7e932159165b43 Mon Sep 17 00:00:00 2001 From: killian-larcher Date: Sun, 15 Dec 2024 16:32:21 +0100 Subject: [PATCH] working on statistics page. --- app/(customer)/dashboard/kpi/page.tsx | 15 -- app/(customer)/dashboard/statistics/page.tsx | 93 +++++++++++ package.json | 4 +- src/components/ui/chart.tsx | 5 - .../SideBar/SideBarMenu/SideBarMenu.tsx | 4 +- .../Dashboard/statistics/line-chart.tsx | 144 ++++++++++++++++++ .../wrappers/charts/evolution-line-chart.tsx | 98 ++++++++++++ .../wrappers/charts/percentage-line-chart.tsx | 85 +++++++++++ src/features/restore/columns.tsx | 3 +- yarn.lock | 16 +- 10 files changed, 433 insertions(+), 34 deletions(-) delete mode 100644 app/(customer)/dashboard/kpi/page.tsx create mode 100644 app/(customer)/dashboard/statistics/page.tsx create mode 100644 src/components/wrappers/Dashboard/statistics/line-chart.tsx create mode 100644 src/components/wrappers/charts/evolution-line-chart.tsx create mode 100644 src/components/wrappers/charts/percentage-line-chart.tsx diff --git a/app/(customer)/dashboard/kpi/page.tsx b/app/(customer)/dashboard/kpi/page.tsx deleted file mode 100644 index 5da0066f..00000000 --- a/app/(customer)/dashboard/kpi/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import {PageParams} from "@/types/next"; - -export default async function RoutePage(props: PageParams<{}>) { - - return ( -
-
-
-
-
-
-
-
- ) -} \ No newline at end of file diff --git a/app/(customer)/dashboard/statistics/page.tsx b/app/(customer)/dashboard/statistics/page.tsx new file mode 100644 index 00000000..9dcd580b --- /dev/null +++ b/app/(customer)/dashboard/statistics/page.tsx @@ -0,0 +1,93 @@ +import {PageParams} from "@/types/next"; +import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page"; +import {prisma} from "@/prisma"; +import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"; +import {EvolutionLineChart} from "@/components/wrappers/charts/evolution-line-chart"; +import {PercentageLineChart} from "@/components/wrappers/charts/percentage-line-chart"; + +export default async function RoutePage(props: PageParams<{}>) { + + const projectsCount = await prisma.project.count({ + where: { + organization: { + slug: "default" + } + }, + }); + + const backupsEvolution = await prisma.backup.findMany({ + select: { + createdAt: true, + }, + orderBy: { + createdAt: "asc", + }, + }); + + const backupsRate = await prisma.backup.groupBy({ + by: ['createdAt', 'status'], + where: { + status: { + in: ['success', 'failed'], + }, + }, + _count: { + id: true, + }, + }); + + + return ( + + + + Statistics + + + +
+ + + Projects + + + {projectsCount} + + + + + KPI 2 + + + + + + + KPI 3 + + + + +
+
+ + + Evolution of the number of backups + + + + + + + + Success rate of backups + + + + + +
+
+
+ ) +} \ No newline at end of file diff --git a/package.json b/package.json index ed90c347..f34e28c0 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "dockerode": "^4.0.2", "embla-carousel-react": "^8.3.1", "input-otp": "^1.4.0", - "lucide-react": "^0.454.0", + "lucide-react": "^0.468.0", "minio": "^8.0.2", "next": "15.0.3", "next-auth": "^5.0.0-beta.25", @@ -70,7 +70,7 @@ "react-hook-form": "^7.53.1", "react-resizable-panels": "^2.1.6", "react-twc": "^1.4.2", - "recharts": "^2.13.3", + "recharts": "^2.15.0", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", "sonner": "^1.6.1", diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx index 0510f5be..8620baa3 100644 --- a/src/components/ui/chart.tsx +++ b/src/components/ui/chart.tsx @@ -2,11 +2,6 @@ import * as React from "react" import * as RechartsPrimitive from "recharts" -import { - NameType, - Payload, - ValueType, -} from "recharts/types/component/DefaultTooltipContent" import { cn } from "@/lib/utils" diff --git a/src/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu.tsx b/src/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu.tsx index 74d6f962..afd55e8d 100644 --- a/src/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu.tsx +++ b/src/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu.tsx @@ -32,8 +32,8 @@ export const SidebarMenuCustom = (props: SidebarMenuCustomProps) => { icon: ShieldHalf, }, { - title: "Statistic", - url: "kpi", + title: "Statistics", + url: "statistics", icon: ChartArea, }, { diff --git a/src/components/wrappers/Dashboard/statistics/line-chart.tsx b/src/components/wrappers/Dashboard/statistics/line-chart.tsx new file mode 100644 index 00000000..5566341d --- /dev/null +++ b/src/components/wrappers/Dashboard/statistics/line-chart.tsx @@ -0,0 +1,144 @@ +// "use client" +// +// import {CartesianGrid, Line, LineChart, Tooltip, XAxis, YAxis} from "recharts" +// import {ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart" +// import {humanReadableDate} from "@/utils/date-formatting"; +// +// +// export type lineChartProps = { +// data: any +// } +// +// export function EvolutionLineChart(props: lineChartProps) { +// +// const {data} = props +// +// // Process data to calculate cumulative count +// const cumulativeData = data.reduce((acc, backup) => { +// const date = backup.createdAt.toISOString().split("T")[0]; // Format as YYYY-MM-DD +// +// // Increment count for the current date or initialize it +// if (acc.length && acc[acc.length - 1].date === date) { +// acc[acc.length - 1].count += 1; +// } else { +// const lastCount = acc.length ? acc[acc.length - 1].count : 0; +// acc.push({date, count: lastCount + 1}); +// } +// +// return acc; +// }, [] as { date: string; count: number }[]); +// +// +// const chartConfig = { +// date: { +// label: "Date", +// color: "#2563eb", +// }, +// count: { +// label: "Number of backups", +// color: "#60a5fa", +// }, +// } satisfies ChartConfig +// +// +// return ( +// +// +// +// humanReadableDate(Date(value)).split(' ')[0]} +// /> +// +// } +// /> +// +// +// +// ) +// } +// +// export function PercentageLineChart(props: lineChartProps) { +// +// const {data} = props +// +// const chartConfig = { +// date: { +// label: "Date", +// color: "#2563eb", +// }, +// successRate: { +// label: "Success Rate", +// color: "#60a5fa", +// }, +// } satisfies ChartConfig +// +// const dailyStats = data.reduce((acc, backup) => { +// const date = backup.createdAt.toISOString().split("T")[0]; // Format YYYY-MM-DD +// const status = backup.status; +// +// if (!acc[date]) { +// acc[date] = {success: 0, failed: 0, total: 0}; +// } +// +// acc[date][status === "success" ? "success" : "failed"] += backup._count.id; +// acc[date].total += backup._count.id; +// +// return acc; +// }, {} as Record); +// +// // Format data for the chart +// const formattedData = Object.entries(dailyStats).map(([date, stats]) => ({ +// date, +// successRate: (stats.success / stats.total) * 100, +// })); +// +// return ( +// +// +// +// +// `${tick}%`}/> +// {/* `${value.toFixed(2)}%`}/>*/} +// } +// cursor={false} +// defaultIndex={1} +// formatter={(value, name) => ( +//
+// {chartConfig[name as keyof typeof chartConfig]?.label || +// name} +//
+// {value} +// +// % +// +//
+//
+// )} +// /> +// +//
+//
+// ) +// +// } +// diff --git a/src/components/wrappers/charts/evolution-line-chart.tsx b/src/components/wrappers/charts/evolution-line-chart.tsx new file mode 100644 index 00000000..d5b7aa2e --- /dev/null +++ b/src/components/wrappers/charts/evolution-line-chart.tsx @@ -0,0 +1,98 @@ +"use client" + +import {ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent} from "@/components/ui/chart"; +import {CartesianGrid, Line, LineChart, XAxis, YAxis} from "recharts"; +import {humanReadableDate} from "@/utils/date-formatting"; + +const data = [ + {date: "2024-12-01", count: 1}, + {date: "2024-12-02", count: 2}, + {date: "2024-12-03", count: 4}, + {date: "2024-12-04", count: 8}, + {date: "2024-12-05", count: 9}, + {date: "2024-12-06", count: 9}, + {date: "2024-12-07", count: 10}, + {date: "2024-12-08", count: 13}, + {date: "2024-12-09", count: 15}, + {date: "2024-12-10", count: 18}, + +] + + +type Data = { + createdAt: Date; +} + + +export type evolutionLineChartProps = { + data: Data[] +} + +export function EvolutionLineChart(props: evolutionLineChartProps) { + + const {data} = props + console.log("aaa data", data) + + + // Process data to calculate cumulative count + const cumulativeData = data.reduce((acc, backup) => { + const date = backup.createdAt.toISOString().split("T")[0]; // Format as YYYY-MM-DD + + // Increment count for the current date or initialize it + if (acc.length && acc[acc.length - 1].date === date) { + acc[acc.length - 1].count += 1; + } else { + const lastCount = acc.length ? acc[acc.length - 1].count : 0; + acc.push({date, count: lastCount + 1}); + } + + return acc; + }, [] as { date: string; count: number }[]); + + + const chartConfig = { + date: { + label: "Date", + color: "#2563eb", + }, + count: { + label: "Number of backups", + color: "#60a5fa", + }, + } satisfies ChartConfig + + + return ( + + + + humanReadableDate(Date(value)).split(' ')[0]} + /> + + } + /> + + + + ) +} \ No newline at end of file diff --git a/src/components/wrappers/charts/percentage-line-chart.tsx b/src/components/wrappers/charts/percentage-line-chart.tsx new file mode 100644 index 00000000..ef50109c --- /dev/null +++ b/src/components/wrappers/charts/percentage-line-chart.tsx @@ -0,0 +1,85 @@ +"use client" + +import {ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent} from "@/components/ui/chart"; +import {CartesianGrid, Line, LineChart, XAxis, YAxis} from "recharts"; + +type Data = { + createdAt: Date; + status: "success" | "failed"; + _count: { id: number }; +} + +export type percentageLineChartProps = { + data: Data[] +} + + +export function PercentageLineChart(props: percentageLineChartProps) { + + const {data} = props + + const chartConfig = { + date: { + label: "Date", + color: "#2563eb", + }, + successRate: { + label: "Success Rate", + color: "#60a5fa", + }, + } satisfies ChartConfig + + const dailyStats = data.reduce((acc, backup) => { + const date = backup.createdAt.toISOString().split("T")[0]; // Format YYYY-MM-DD + const status = backup.status; + + if (!acc[date]) { + acc[date] = {success: 0, failed: 0, total: 0}; + } + + acc[date][status === "success" ? "success" : "failed"] += backup._count.id; + acc[date].total += backup._count.id; + + return acc; + }, {} as Record); + + // Format data for the chart + const formattedData = Object.entries(dailyStats).map(([date, stats]) => ({ + date, + successRate: (stats.success / stats.total) * 100, + })); + + return ( + + + + + `${tick}%`}/> + } + cursor={false} + defaultIndex={1} + formatter={(value, name) => ( +
+ {chartConfig[name as keyof typeof chartConfig]?.label || + name} +
+ {value} + + % + +
+
+ )} + /> + +
+
+ ) + +} + diff --git a/src/features/restore/columns.tsx b/src/features/restore/columns.tsx index 0b3eef37..f8175aee 100644 --- a/src/features/restore/columns.tsx +++ b/src/features/restore/columns.tsx @@ -10,12 +10,11 @@ import { DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import {Button} from "@/components/ui/button"; -import {Download, MoreHorizontal, Trash2} from "lucide-react"; +import {MoreHorizontal} from "lucide-react"; import {ReloadIcon} from "@radix-ui/react-icons"; import {Restoration} from "@prisma/client"; - export const restoreColumns: ColumnDef[] = [ { accessorKey: "id", diff --git a/yarn.lock b/yarn.lock index 77f7fc8b..dfa5c00d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4046,10 +4046,10 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lucide-react@^0.454.0: - version "0.454.0" - resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.454.0.tgz#a81b9c482018720f07ead0503ae502d94d528444" - integrity sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ== +lucide-react@^0.468.0: + version "0.468.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.468.0.tgz#830c1bfd905575ddd23b832baa420c87db166910" + integrity sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA== make-dir@^3.1.0: version "3.1.0" @@ -4862,10 +4862,10 @@ recharts-scale@^0.4.4: dependencies: decimal.js-light "^2.4.1" -recharts@^2.13.3: - version "2.13.3" - resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.13.3.tgz#a5ce61e493dff921a14a14a8f42a9f2d2bbefd5a" - integrity sha512-YDZ9dOfK9t3ycwxgKbrnDlRC4BHdjlY73fet3a0C1+qGMjXVZe6+VXmpOIIhzkje5MMEL8AN4hLIe4AMskBzlA== +recharts@^2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.15.0.tgz#0b77bff57a43885df9769ae649a14cb1a7fe19aa" + integrity sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw== dependencies: clsx "^2.0.0" eventemitter3 "^4.0.1"