diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index df6c765..f9a5dfb 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -102,12 +102,33 @@ public function __invoke() return $item; }); + $topProducts = DB::table('order_items') + ->join('products', 'order_items.product_id', '=', 'products.id') + ->join('orders', 'order_items.order_id', '=', 'orders.id') + ->select('products.name as name', DB::raw('SUM(order_items.qty) as total')) + ->whereDate('orders.created_at', $today) + ->groupBy('products.name') + ->orderByDesc('total') + ->limit(5) + ->get(); + + $topCustomers = DB::table('orders') + ->select('customer_name as name', DB::raw('SUM(total) as total')) + ->whereDate('created_at', $today) + ->whereNotNull('customer_name') + ->groupBy('customer_name') + ->orderByDesc('total') + ->limit(5) + ->get(); + return Inertia::render('dashboard', [ 'stats' => $stats, 'salesByHour' => $salesByHour, 'paymentMethods' => $paymentMethods, 'orderStatuses' => $orderStatuses, 'orderChannels' => $orderChannels, + 'topProducts' => $topProducts, + 'topCustomers' => $topCustomers, ]); } diff --git a/resources/js/components/charts/bar-chart.tsx b/resources/js/components/charts/bar-chart.tsx new file mode 100644 index 0000000..d69266e --- /dev/null +++ b/resources/js/components/charts/bar-chart.tsx @@ -0,0 +1,116 @@ +"use client" + +import React, { useMemo } from "react" +import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from "recharts" + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + type ChartConfig, +} from "@/components/ui/chart" + +export const CustomBarChart = React.memo(function CustomBarChart({ + title, + description, + data, + colorOffset = 0, + isCurrency = false +}: { + title: string + description: string + data: any[] + colorOffset?: number + isCurrency?: boolean +}) { + 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, '_') + + const hue = Math.floor((index * 137.5 + colorOffset) % 360) + + cfg[key] = { + label: String(item.name), + color: `hsl(${hue}, 60%, 65%)`, + } + return { + ...item, + fill: `var(--color-${key})`, + name: String(item.name), + total: Number(item.total) + } + }) + return { config: cfg, chartData: formattedData } + }, [data, colorOffset]) + + const formatValue = (val: any) => { + if (!isCurrency) return val; + return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(val); + }; + + return ( + + + {title} + {description} + + + + + + + + } + /> + + + + + + + + + ) +}) diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx index 2a9a561..179fd9f 100644 --- a/resources/js/pages/dashboard.tsx +++ b/resources/js/pages/dashboard.tsx @@ -5,10 +5,11 @@ 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 { CustomBarChart } from '../components/charts/bar-chart'; import { CustomPieChart } from '../components/charts/pie-chart'; export default function Dashboard() { - const { auth, stats, salesByHour, paymentMethods, orderStatuses, orderChannels } = usePage().props; + const { auth, stats, salesByHour, paymentMethods, orderStatuses, orderChannels, topProducts, topCustomers } = usePage().props; const [time, setTime] = useState(new Date()); useEffect(() => { @@ -123,6 +124,22 @@ export default function Dashboard() { colorOffset={240} /> + +
+ + +
);