hi-frontend/apps/admin/src/sections/dashboard/components/revenue-statistics-card.tsx

458 lines
17 KiB
TypeScript

"use client";
import { useQuery } from "@tanstack/react-query";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@workspace/ui/components/card";
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
} from "@workspace/ui/components/chart";
import { Separator } from "@workspace/ui/components/separator";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@workspace/ui/components/tabs";
import Empty from "@workspace/ui/composed/empty";
import { queryRevenueStatistics } from "@workspace/ui/services/admin/console";
import { unitConversion } from "@workspace/ui/utils/unit-conversions";
import { useTranslation } from "react-i18next";
import {
Area,
AreaChart,
Bar,
BarChart,
CartesianGrid,
Label,
Pie,
PieChart,
XAxis,
} from "recharts";
import { Display } from "@/components/display";
export function RevenueStatisticsCard() {
const { t, i18n } = useTranslation("dashboard");
const locale = i18n.language;
const IncomeStatisticsConfig = {
new_purchase: {
label: t("newPurchase", "New Purchase"),
color: "var(--color-chart-1)",
},
repurchase: {
label: t("repurchase", "Repurchase"),
color: "var(--color-chart-2)",
},
total: {
label: t("totalIncome", "Total Income"),
color: "var(--color-chart-3)",
},
};
const { data: RevenueStatistics } = useQuery({
queryKey: ["queryRevenueStatistics"],
queryFn: async () => {
const { data } = await queryRevenueStatistics();
return data.data;
},
});
return (
<Tabs defaultValue="today">
<Card className="h-full pb-0">
<CardHeader className="!flex-row flex items-center justify-between">
<CardTitle>{t("revenueTitle", "Revenue Statistics")}</CardTitle>
<TabsList>
<TabsTrigger value="today">{t("today", "Today")}</TabsTrigger>
<TabsTrigger value="month">{t("month", "Month")}</TabsTrigger>
<TabsTrigger value="total">{t("total", "Total")}</TabsTrigger>
</TabsList>
</CardHeader>
<TabsContent className="h-full" value="today">
<CardContent className="h-80">
{RevenueStatistics?.today.new_order_amount ||
RevenueStatistics?.today.renewal_order_amount ? (
<ChartContainer
className="mx-auto max-h-80"
config={IncomeStatisticsConfig}
>
<PieChart>
<ChartLegend content={<ChartLegendContent />} />
<ChartTooltip
content={<ChartTooltipContent hideLabel />}
cursor={false}
/>
<Pie
data={[
{
type: "new_purchase",
value: unitConversion(
"centsToDollars",
RevenueStatistics?.today.new_order_amount
),
fill: "var(--color-new_purchase)",
},
{
type: "repurchase",
value: unitConversion(
"centsToDollars",
RevenueStatistics?.today.renewal_order_amount
),
fill: "var(--color-repurchase)",
},
]}
dataKey="value"
innerRadius={50}
nameKey="type"
strokeWidth={5}
>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text
dominantBaseline="middle"
textAnchor="middle"
x={viewBox.cx}
y={viewBox.cy}
>
<tspan
className="fill-foreground font-bold text-2xl"
x={viewBox.cx}
y={viewBox.cy}
>
{unitConversion(
"centsToDollars",
RevenueStatistics?.today.amount_total
)}
</tspan>
</text>
);
}
}}
/>
</Pie>
</PieChart>
</ChartContainer>
) : (
<div className="flex h-full items-center justify-center">
<Empty />
</div>
)}
</CardContent>
<CardFooter className="!py-5 flex h-20 flex-row border-t">
<div className="flex w-full items-center gap-2">
<div className="grid flex-1 auto-rows-min gap-0.5">
<div className="text-muted-foreground text-xs">
{t("totalIncome", "Total Income")}
</div>
<div className="font-bold text-xl tabular-nums leading-none">
<Display
type="currency"
value={RevenueStatistics?.today.amount_total}
/>
</div>
</div>
<Separator className="!h-10 mx-2 w-px" orientation="vertical" />
<div className="grid flex-1 auto-rows-min gap-0.5">
<div className="text-muted-foreground text-xs">
{IncomeStatisticsConfig.new_purchase.label}
</div>
<div className="font-bold text-xl tabular-nums leading-none">
<Display
type="currency"
value={RevenueStatistics?.today.new_order_amount}
/>
</div>
</div>
<Separator className="!h-10 mx-2 w-px" orientation="vertical" />
<div className="grid flex-1 auto-rows-min gap-0.5">
<div className="text-muted-foreground text-xs">
{IncomeStatisticsConfig.repurchase.label}
</div>
<div className="font-bold text-xl tabular-nums leading-none">
<Display
type="currency"
value={RevenueStatistics?.today.renewal_order_amount}
/>
</div>
</div>
</div>
</CardFooter>
</TabsContent>
<TabsContent className="h-full" value="month">
<CardContent className="h-80">
{RevenueStatistics?.monthly.list &&
RevenueStatistics?.monthly.list.length > 0 ? (
<ChartContainer
className="max-h-80 w-full"
config={IncomeStatisticsConfig}
>
<BarChart
accessibilityLayer
data={
RevenueStatistics?.monthly.list?.map((item) => ({
date: item.date,
new_purchase: unitConversion(
"centsToDollars",
item.new_order_amount
),
repurchase: unitConversion(
"centsToDollars",
item.renewal_order_amount
),
total: unitConversion(
"centsToDollars",
item.new_order_amount + item.renewal_order_amount
),
})) || []
}
>
<CartesianGrid vertical={false} />
<XAxis
axisLine={false}
dataKey="date"
tickFormatter={(value) => {
const [year, month, day] = value.split("-");
return new Date(year, month - 1, day).toLocaleDateString(
locale,
{
month: "short",
day: "numeric",
}
);
}}
tickLine={false}
tickMargin={10}
/>
<Bar
dataKey="new_purchase"
fill="var(--color-new_purchase)"
radius={[0, 0, 4, 4]}
stackId="a"
/>
<Bar
dataKey="repurchase"
fill="var(--color-repurchase)"
radius={[4, 4, 0, 0]}
stackId="a"
/>
<ChartTooltip
content={
<ChartTooltipContent
formatter={(value, name, item, index) => (
<>
<div
className="h-2.5 w-2.5 shrink-0 rounded-[2px] bg-[--color-bg]"
style={
{
"--color-bg": `var(--color-${name})`,
} as React.CSSProperties
}
/>
{IncomeStatisticsConfig[
name as keyof typeof IncomeStatisticsConfig
]?.label || name}
<div className="ml-auto flex items-baseline gap-0.5 font-medium font-mono text-foreground tabular-nums">
{value}
</div>
{index === 1 && (
<div className="flex basis-full items-center border-t pt-1.5 font-medium text-foreground text-xs">
{t("totalIncome", "Total Income")}
<div className="ml-auto flex items-baseline gap-0.5 font-medium font-mono text-foreground tabular-nums">
{item.payload.total}
</div>
</div>
)}
</>
)}
/>
}
cursor={false}
/>
<ChartLegend content={<ChartLegendContent />} />
</BarChart>
</ChartContainer>
) : (
<div className="flex h-full items-center justify-center">
<Empty />
</div>
)}
</CardContent>
<CardFooter className="!py-5 flex h-20 flex-row border-t">
<div className="flex w-full items-center gap-2">
<div className="grid flex-1 auto-rows-min gap-0.5">
<div className="text-muted-foreground text-xs">
{t("totalIncome", "Total Income")}
</div>
<div className="font-bold text-xl tabular-nums leading-none">
<Display
type="currency"
value={RevenueStatistics?.monthly.amount_total}
/>
</div>
</div>
<Separator className="!h-10 mx-2 w-px" orientation="vertical" />
<div className="grid flex-1 auto-rows-min gap-0.5">
<div className="text-muted-foreground text-xs">
{IncomeStatisticsConfig.new_purchase.label}
</div>
<div className="font-bold text-xl tabular-nums leading-none">
<Display
type="currency"
value={RevenueStatistics?.monthly.new_order_amount}
/>
</div>
</div>
<Separator className="!h-10 mx-2 w-px" orientation="vertical" />
<div className="grid flex-1 auto-rows-min gap-0.5">
<div className="text-muted-foreground text-xs">
{IncomeStatisticsConfig.repurchase.label}
</div>
<div className="font-bold text-xl tabular-nums leading-none">
<Display
type="currency"
value={RevenueStatistics?.monthly.renewal_order_amount}
/>
</div>
</div>
</div>
</CardFooter>
</TabsContent>
<TabsContent className="h-full" value="total">
<CardContent className="h-80">
{RevenueStatistics?.all.list &&
RevenueStatistics?.all.list.length > 0 ? (
<ChartContainer
className="max-h-80 w-full"
config={IncomeStatisticsConfig}
>
<AreaChart
accessibilityLayer
data={
RevenueStatistics?.all.list?.map((item) => ({
date: item.date,
new_purchase: unitConversion(
"centsToDollars",
item.new_order_amount
),
repurchase: unitConversion(
"centsToDollars",
item.renewal_order_amount
),
total: unitConversion(
"centsToDollars",
item.new_order_amount + item.renewal_order_amount
),
})) || []
}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
axisLine={false}
dataKey="date"
tickFormatter={(value) => {
const [year, month] = value.split("-");
return new Date(year, month - 1).toLocaleDateString(
locale,
{
month: "short",
}
);
}}
tickLine={false}
/>
<ChartTooltip
content={
<ChartTooltipContent
formatter={(value, name, item, index) => (
<>
<div
className="h-2.5 w-2.5 shrink-0 rounded-[2px] bg-[--color-bg]"
style={
{
"--color-bg": `var(--color-${name})`,
} as React.CSSProperties
}
/>
{IncomeStatisticsConfig[
name as keyof typeof IncomeStatisticsConfig
]?.label || name}
<div className="ml-auto flex items-baseline gap-0.5 font-medium font-mono text-foreground tabular-nums">
{value}
</div>
{index === 1 && (
<div className="flex basis-full items-center border-t pt-1.5 font-medium text-foreground text-xs">
{t("totalIncome", "Total Income")}
<div className="ml-auto flex items-baseline gap-0.5 font-medium font-mono text-foreground tabular-nums">
{item.payload.total}
</div>
</div>
)}
</>
)}
/>
}
cursor={false}
/>
<Area
dataKey="new_purchase"
fill="var(--color-new_purchase)"
fillOpacity={0.4}
stackId="a"
stroke="var(--color-new_purchase)"
type="natural"
/>
<Area
dataKey="repurchase"
fill="var(--color-repurchase)"
fillOpacity={0.4}
stackId="a"
stroke="var(--color-repurchase)"
type="natural"
/>
<ChartLegend content={<ChartLegendContent />} />
</AreaChart>
</ChartContainer>
) : (
<div className="flex h-full items-center justify-center">
<Empty />
</div>
)}
</CardContent>
<CardFooter className="!py-5 flex h-20 flex-row border-t">
<div className="flex w-full items-center gap-2">
<div className="grid flex-1 auto-rows-min gap-0.5">
<div className="text-muted-foreground text-xs">
{t("totalIncome", "Total Income")}
</div>
<div className="font-bold text-xl tabular-nums leading-none">
<Display
type="currency"
value={RevenueStatistics?.all.amount_total}
/>
</div>
</div>
</div>
</CardFooter>
</TabsContent>
</Card>
</Tabs>
);
}