feat: add order distribution charts for payment methods, status, and channels to dashboard

This commit is contained in:
Yoga Pangestu 2026-04-23 13:20:34 +07:00
parent 7ee44c8fd9
commit f7288db5cb
3 changed files with 145 additions and 1 deletions

View File

@ -2,6 +2,9 @@
namespace App\Http\Controllers;
use App\Enums\OrderChannel;
use App\Enums\OrderStatus;
use App\Enums\PaymentMethod;
use App\Models\Expense;
use App\Models\Order;
use App\Models\OrderItem;
@ -63,9 +66,48 @@ public function __invoke()
];
});
$paymentMethods = DB::table('orders')
->select('payment_method as name', DB::raw('COUNT(*) as total'))
->groupBy('payment_method')
->get()
->map(function ($item) {
if ($item->name) {
$item->name = PaymentMethod::tryFrom($item->name)?->label() ?? $item->name;
}
return $item;
});
$orderStatuses = DB::table('orders')
->select('order_status as name', DB::raw('COUNT(*) as total'))
->groupBy('order_status')
->get()
->map(function ($item) {
if ($item->name) {
$item->name = OrderStatus::tryFrom($item->name)?->label() ?? $item->name;
}
return $item;
});
$orderChannels = DB::table('orders')
->select('order_channel as name', DB::raw('COUNT(*) as total'))
->groupBy('order_channel')
->get()
->map(function ($item) {
if ($item->name) {
$item->name = OrderChannel::tryFrom($item->name)?->label() ?? $item->name;
}
return $item;
});
return Inertia::render('dashboard', [
'stats' => $stats,
'salesByHour' => $salesByHour,
'paymentMethods' => $paymentMethods,
'orderStatuses' => $orderStatuses,
'orderChannels' => $orderChannels,
]);
}

View File

@ -0,0 +1,80 @@
"use client"
import React, { useMemo } from "react"
import { Pie, PieChart } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/components/ui/chart"
export const CustomPieChart = React.memo(function CustomPieChart({
title,
description,
data,
colorOffset = 0,
}: {
title: string
description: string
data: any[]
colorOffset?: number
}) {
const { config, chartData } = useMemo(() => {
const cfg: ChartConfig = {
total: { label: "Total" },
}
const formattedData = data.map((item, index) => {
const key = String(item.name).toLowerCase().replace(/[^a-z0-9]/g, '_')
// Generate distinct HSL color based on index and chart-level offset
// 137.5 is the golden angle, which distributes colors nicely
const hue = Math.floor((index * 137.5 + colorOffset) % 360)
cfg[key] = {
label: String(item.name),
color: `hsl(${hue}, 70%, 50%)`,
}
return {
...item,
fill: `var(--color-${key})`,
name: String(item.name),
}
})
return { config: cfg, chartData: formattedData }
}, [data, colorOffset])
return (
<Card className="flex flex-col">
<CardHeader className="items-center pb-0">
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent className="flex-1 pb-0">
<ChartContainer
config={config}
className="mx-auto aspect-square max-h-[250px] pb-0 [&_.recharts-pie-label-text]:fill-foreground [&_.recharts-wrapper]:outline-none [&_.recharts-surface]:outline-none"
>
<PieChart style={{ outline: "none" }}>
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
<Pie
data={chartData}
dataKey="total"
nameKey="name"
label
/>
</PieChart>
</ChartContainer>
</CardContent>
</Card>
)
})

View File

@ -5,9 +5,10 @@ import { DashboardPageProps } from '@/types';
import { StatCard } from '@/components/cards/stat-card';
import { WelcomeCard } from '@/components/cards/welcome-card';
import { SalesByHourChart } from '../components/charts/sales-by-hour-chart';
import { CustomPieChart } from '../components/charts/pie-chart';
export default function Dashboard() {
const { auth, stats, salesByHour } = usePage<DashboardPageProps & { salesByHour: any[] }>().props;
const { auth, stats, salesByHour, paymentMethods, orderStatuses, orderChannels } = usePage<DashboardPageProps & { salesByHour: any[], paymentMethods: any[], orderStatuses: any[], orderChannels: any[] }>().props;
const [time, setTime] = useState(new Date());
useEffect(() => {
@ -101,6 +102,27 @@ export default function Dashboard() {
</div>
<SalesByHourChart data={salesByHour} />
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
<CustomPieChart
title="Metode Pembayaran"
description="Distribusi pesanan berdasarkan metode pembayaran."
data={paymentMethods}
colorOffset={0}
/>
<CustomPieChart
title="Status Pesanan"
description="Distribusi pesanan berdasarkan status."
data={orderStatuses}
colorOffset={120}
/>
<CustomPieChart
title="Channel Pesanan"
description="Distribusi pesanan berdasarkan channel/platform."
data={orderChannels}
colorOffset={240}
/>
</div>
</div>
</>
);