working on statistics page.

This commit is contained in:
killian-larcher
2024-12-15 16:32:21 +01:00
parent 48e154b9ff
commit f4fa966d89
10 changed files with 433 additions and 34 deletions
-15
View File
@@ -1,15 +0,0 @@
import {PageParams} from "@/types/next";
export default async function RoutePage(props: PageParams<{}>) {
return (
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="aspect-video rounded-xl bg-muted/50"/>
<div className="aspect-video rounded-xl bg-muted/50"/>
<div className="aspect-video rounded-xl bg-muted/50"/>
</div>
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min"/>
</div>
)
}
@@ -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 (
<Page>
<PageHeader>
<PageTitle>
Statistics
</PageTitle>
</PageHeader>
<PageContent className="flex flex-col gap-y-4">
<div className="flex flex-col md:flex-row gap-4">
<Card className="w-full">
<CardHeader>
<CardTitle>Projects</CardTitle>
</CardHeader>
<CardContent>
{projectsCount}
</CardContent>
</Card>
<Card className="w-full">
<CardHeader>
<CardTitle>KPI 2</CardTitle>
</CardHeader>
<CardContent>
</CardContent>
</Card>
<Card className="w-full">
<CardHeader>
<CardTitle>KPI 3</CardTitle>
</CardHeader>
<CardContent>
</CardContent>
</Card>
</div>
<div className="flex flex-col md:flex-row gap-4">
<Card className="w-full">
<CardHeader>
<CardTitle>Evolution of the number of backups</CardTitle>
</CardHeader>
<CardContent>
<EvolutionLineChart data={backupsEvolution}/>
</CardContent>
</Card>
<Card className="w-full">
<CardHeader>
<CardTitle>Success rate of backups</CardTitle>
</CardHeader>
<CardContent>
<PercentageLineChart data={backupsRate}/>
</CardContent>
</Card>
</div>
</PageContent>
</Page>
)
}
+2 -2
View File
@@ -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",
-5
View File
@@ -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"
@@ -32,8 +32,8 @@ export const SidebarMenuCustom = (props: SidebarMenuCustomProps) => {
icon: ShieldHalf,
},
{
title: "Statistic",
url: "kpi",
title: "Statistics",
url: "statistics",
icon: ChartArea,
},
{
@@ -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 (
// <ChartContainer config={chartConfig}>
// <LineChart
// accessibilityLayer
// data={data}
// margin={{
// left: 12,
// right: 12,
// }}
// >
// <CartesianGrid vertical={false}/>
// <XAxis
// dataKey="date"
// tickLine={false}
// axisLine={false}
// tickMargin={8}
// tickFormatter={(value) => humanReadableDate(Date(value)).split(' ')[0]}
// />
// <YAxis/>
// <ChartTooltip
// cursor={false}
// content={<ChartTooltipContent hideLabel/>}
// />
// <Line
// dataKey="count"
// type="linear"
// stroke="var(--color-desktop)"
// strokeWidth={2}
// dot={false}
// />
// </LineChart>
// </ChartContainer>
// )
// }
//
// 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<string, { success: number; failed: number; total: number }>);
//
// // Format data for the chart
// const formattedData = Object.entries(dailyStats).map(([date, stats]) => ({
// date,
// successRate: (stats.success / stats.total) * 100,
// }));
//
// return (
// <ChartContainer config={chartConfig}>
// <LineChart data={formattedData}>
// <CartesianGrid strokeDasharray="3 3"/>
// <XAxis dataKey="date"/>
// <YAxis domain={[0, 100]} tickFormatter={(tick) => `${tick}%`}/>
// {/*<Tooltip formatter={(value: number) => `${value.toFixed(2)}%`}/>*/}
// <ChartTooltip
// content={<ChartTooltipContent/>}
// cursor={false}
// defaultIndex={1}
// formatter={(value, name) => (
// <div className="flex min-w-[130px] items-center text-xs text-muted-foreground">
// {chartConfig[name as keyof typeof chartConfig]?.label ||
// name}
// <div className="ml-auto flex items-baseline gap-0.5 font-mono font-medium tabular-nums text-foreground">
// {value}
// <span className="font-normal text-muted-foreground">
// %
// </span>
// </div>
// </div>
// )}
// />
// <Line type="step" dataKey="successRate" stroke="#8884d8" strokeWidth={2}/>
// </LineChart>
// </ChartContainer>
// )
//
// }
//
@@ -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 (
<ChartContainer config={chartConfig}>
<LineChart
accessibilityLayer
data={data}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false}/>
<XAxis
dataKey="date"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => humanReadableDate(Date(value)).split(' ')[0]}
/>
<YAxis/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel/>}
/>
<Line
dataKey="count"
type="linear"
stroke="var(--color-desktop)"
strokeWidth={2}
dot={false}
/>
</LineChart>
</ChartContainer>
)
}
@@ -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<string, { success: number; failed: number; total: number }>);
// Format data for the chart
const formattedData = Object.entries(dailyStats).map(([date, stats]) => ({
date,
successRate: (stats.success / stats.total) * 100,
}));
return (
<ChartContainer config={chartConfig}>
<LineChart
accessibilityLayer
data={formattedData}
>
<CartesianGrid strokeDasharray="3 3"/>
<XAxis dataKey="date"/>
<YAxis domain={[0, 100]} tickFormatter={(tick) => `${tick}%`}/>
<ChartTooltip
content={<ChartTooltipContent/>}
cursor={false}
defaultIndex={1}
formatter={(value, name) => (
<div className="flex min-w-[130px] items-center text-xs text-muted-foreground">
{chartConfig[name as keyof typeof chartConfig]?.label ||
name}
<div
className="ml-auto flex items-baseline gap-0.5 font-mono font-medium tabular-nums text-foreground">
{value}
<span className="font-normal text-muted-foreground">
%
</span>
</div>
</div>
)}
/>
<Line type="step" dataKey="successRate" stroke="#8884d8" strokeWidth={2}/>
</LineChart>
</ChartContainer>
)
}
+1 -2
View File
@@ -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<Restoration>[] = [
{
accessorKey: "id",
+8 -8
View File
@@ -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"