Add dashboard

This commit is contained in:
KMKoushik
2024-05-02 21:47:03 +10:00
parent a9143be5b6
commit 2d4babe618
4 changed files with 329 additions and 83 deletions

View File

@@ -1,85 +1,223 @@
import { AreaChart, Area, XAxis, YAxis, Tooltip } from "recharts";
const data = [
{
name: "Page A",
uv: 4000,
pv: 2400,
amt: 2400,
},
{
name: "Page B",
uv: 3000,
pv: 1398,
amt: 2210,
},
{
name: "Page C",
uv: 2000,
pv: 9800,
amt: 2290,
},
{
name: "Page D",
uv: 2780,
pv: 3908,
amt: 2000,
},
{
name: "Page E",
uv: 1890,
pv: 4800,
amt: 2181,
},
{
name: "Page F",
uv: 2390,
pv: 3800,
amt: 2500,
},
{
name: "Page G",
uv: 3490,
pv: 4300,
amt: 2100,
},
];
import React from "react";
import {
BarChart,
Bar,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
} from "recharts";
import { EmailStatusIcon } from "../emails/email-status-badge";
import { EmailStatus } from "@prisma/client";
import { api } from "~/trpc/react";
import Spinner from "@unsend/ui/src/spinner";
import { Tabs, TabsList, TabsTrigger } from "@unsend/ui/src/tabs";
import { useUrlState } from "~/hooks/useUrlState";
export default function DashboardChart() {
const [days, setDays] = useUrlState("days", "7");
const statusQuery = api.email.dashboard.useQuery({ days: Number(days) });
return (
<AreaChart
width={900}
height={250}
data={data}
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
>
<defs>
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#8884d8" stopOpacity={0.8} />
<stop offset="95%" stopColor="#8884d8" stopOpacity={0} />
</linearGradient>
<linearGradient id="colorPv" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#82ca9d" stopOpacity={0.8} />
<stop offset="95%" stopColor="#82ca9d" stopOpacity={0} />
</linearGradient>
</defs>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Area
type="monotone"
dataKey="uv"
stroke="#8884d8"
fillOpacity={1}
fill="url(#colorUv)"
/>
<Area
type="monotone"
dataKey="pv"
stroke="#82ca9d"
fillOpacity={1}
fill="url(#colorPv)"
/>
</AreaChart>
<div>
<div className="flex justify-between items-center">
<h1 className="font-bold text-lg">Dashboard</h1>
<Tabs
value={days || "7"}
onValueChange={(value) => setDays(value)}
className=""
>
<TabsList>
<TabsTrigger value="7">7 Days</TabsTrigger>
<TabsTrigger value="30">30 Days</TabsTrigger>
</TabsList>
</Tabs>
</div>
<div className="flex flex-col gap-8 mt-8">
<div className="flex flex-wrap gap-2">
{!statusQuery.isLoading && statusQuery.data ? (
<>
<DashboardItemCard
status={"total"}
count={statusQuery.data.totalCount}
percentage={100}
/>
<DashboardItemCard
status={EmailStatus.DELIVERED}
count={statusQuery.data.emailStatusCounts.DELIVERED.count}
percentage={
statusQuery.data.emailStatusCounts.DELIVERED.percentage
}
/>
<DashboardItemCard
status={EmailStatus.BOUNCED}
count={statusQuery.data.emailStatusCounts.BOUNCED.count}
percentage={
statusQuery.data.emailStatusCounts.BOUNCED.percentage
}
/>
<DashboardItemCard
status={EmailStatus.COMPLAINED}
count={statusQuery.data.emailStatusCounts.COMPLAINED.count}
percentage={
statusQuery.data.emailStatusCounts.COMPLAINED.percentage
}
/>
<DashboardItemCard
status={EmailStatus.CLICKED}
count={statusQuery.data.emailStatusCounts.CLICKED.count}
percentage={
statusQuery.data.emailStatusCounts.CLICKED.percentage
}
/>
<DashboardItemCard
status={EmailStatus.OPENED}
count={statusQuery.data.emailStatusCounts.OPENED.count}
percentage={
statusQuery.data.emailStatusCounts.OPENED.percentage
}
/>
</>
) : (
<>
<div className="h-[100px] w-[1/5] min-w-[200px] bg-secondary/10 border rounded-lg p-4 flex justify-center items-center gap-3 ">
<Spinner className="w-4 h-4" innerSvgClass="stroke-primary" />
</div>
<div className="h-[100px] w-[1/5] min-w-[200px] bg-secondary/10 border rounded-lg p-4 flex justify-center items-center gap-3 ">
<Spinner className="w-4 h-4" innerSvgClass="stroke-primary" />
</div>
<div className="h-[100px] w-[1/5] min-w-[200px] bg-secondary/10 border rounded-lg p-4 flex justify-center items-center gap-3 ">
<Spinner className="w-4 h-4" innerSvgClass="stroke-primary" />
</div>
<div className="h-[100px] w-[1/5] min-w-[200px] bg-secondary/10 border rounded-lg p-4 flex justify-center items-center gap-3 ">
<Spinner className="w-4 h-4" innerSvgClass="stroke-primary" />
</div>
<div className="h-[100px] w-[1/5] min-w-[200px] bg-secondary/10 border rounded-lg p-4 flex justify-center items-center gap-3 ">
<Spinner className="w-4 h-4" innerSvgClass="stroke-primary" />
</div>
</>
)}
</div>
{!statusQuery.isLoading && statusQuery.data ? (
<div className="w-full h-[400px] border rounded-lg p-4">
<ResponsiveContainer width="100%" height="100%">
<BarChart
width={900}
height={300}
data={statusQuery.data.emailDailyStatusCounts}
margin={{
top: 20,
right: 30,
left: 20,
bottom: 5,
}}
>
{/* <CartesianGrid strokeDasharray="3 3" /> */}
<XAxis dataKey="name" fontSize={12} />
<YAxis fontSize={12} />
<Tooltip
content={({ payload }) => {
const data = payload?.[0]?.payload as Record<
EmailStatus,
number
> & { name: string };
if (
!data ||
(!data.BOUNCED &&
!data.COMPLAINED &&
!data.DELIVERED &&
!data.OPENED &&
!data.CLICKED)
)
return null;
return (
<div className=" bg-black border shadow-lg p-2 rounded flex flex-col gap-4 px-4">
<p className="text-sm text-muted-foreground">
{data.name}
</p>
{data.DELIVERED ? (
<div>
<p className="text-sm text-[#10b981]">Delivered</p>
<p className="text-xs">{data.DELIVERED} emails</p>
</div>
) : null}
{data.BOUNCED ? (
<div>
<p className="text-sm text-[#ef4444]">Bounced</p>
<p className="text-xs">{data.BOUNCED} emails</p>
</div>
) : null}
{data.COMPLAINED ? (
<div>
<p className="text-sm text-[#eab308]">Complained</p>
<p className="text-xs">{data.COMPLAINED} emails</p>
</div>
) : null}
{data.OPENED ? (
<div>
<p className="text-sm text-[#6366f1]">Opened</p>
<p className="text-xs">{data.OPENED} emails</p>
</div>
) : null}
{data.CLICKED ? (
<div>
<p className="text-sm text-[#06b6d4]">Clicked</p>
<p className="text-xs">{data.CLICKED} emails</p>
</div>
) : null}
</div>
);
}}
cursor={false}
/>
{/* <Legend /> */}
<Bar
barSize={8}
dataKey="DELIVERED"
stackId="a"
fill="#10b981"
/>
<Bar dataKey="BOUNCED" stackId="a" fill="#ef4444" />
<Bar dataKey="COMPLAINED" stackId="a" fill="#eab308" />
<Bar dataKey="OPENED" stackId="a" fill="#6366f1" />
<Bar dataKey="CLICKED" stackId="a" fill="#06b6d4" />
</BarChart>
</ResponsiveContainer>
</div>
) : null}
</div>
</div>
);
}
type DashboardItemCardProps = {
status: EmailStatus | "total";
count: number;
percentage: number;
};
const DashboardItemCard: React.FC<DashboardItemCardProps> = ({
status,
count,
percentage,
}) => {
return (
<div className="h-[100px] w-[16%] min-w-[170px] bg-secondary/10 border rounded-lg p-4 flex flex-col gap-3">
<div className="flex items-center gap-3">
{status !== "total" ? <EmailStatusIcon status={status} /> : null}
<div className=" capitalize">{status.toLowerCase()}</div>
</div>
<div className="flex justify-between items-end">
<div className="text-primary font-light text-2xl font-mono">
{count}
</div>
{status !== "total" ? (
<div className="text-sm pb-1">{percentage}%</div>
) : null}
</div>
</div>
);
};