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"