refactor: consolidate hourly chart components and update sales-by-hour data logic
This commit is contained in:
parent
b1eb893cfe
commit
a280eb8a64
@ -17,14 +17,14 @@ class AnalysisController extends Controller
|
||||
public function __invoke()
|
||||
{
|
||||
$stats = [
|
||||
'total_sales' => $this->getStats(fn() => Order::count()),
|
||||
'total_revenue' => $this->getStats(fn() => Order::sum('total')),
|
||||
'total_expenses' => $this->getStats(fn() => Expense::sum('amount')),
|
||||
'cogs' => $this->getStats(fn() => Order::sum('hpp')),
|
||||
'total_discount' => $this->getStats(fn() => Order::sum('discount')),
|
||||
'total_purchases' => $this->getStats(fn() => Purchase::sum('total')),
|
||||
'products_sold' => $this->getStats(fn() => OrderItem::sum('qty')),
|
||||
'total_customers' => $this->getStats(fn() => Order::distinct('customer_name')->count('customer_name')),
|
||||
'total_sales' => $this->getStats(fn () => Order::count()),
|
||||
'total_revenue' => $this->getStats(fn () => Order::sum('total')),
|
||||
'total_expenses' => $this->getStats(fn () => Expense::sum('amount')),
|
||||
'cogs' => $this->getStats(fn () => Order::sum('hpp')),
|
||||
'total_discount' => $this->getStats(fn () => Order::sum('discount')),
|
||||
'total_purchases' => $this->getStats(fn () => Purchase::sum('total')),
|
||||
'products_sold' => $this->getStats(fn () => OrderItem::sum('qty')),
|
||||
'total_customers' => $this->getStats(fn () => Order::distinct('customer_name')->count('customer_name')),
|
||||
];
|
||||
|
||||
$stats['aov'] = $this->calculateAov($stats['total_revenue'], $stats['total_sales']);
|
||||
@ -114,7 +114,7 @@ public function __invoke()
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
$ordersByHourRaw = DB::table('orders')
|
||||
$salesByHourRaw = DB::table('orders')
|
||||
->select(
|
||||
DB::raw('HOUR(created_at) as hour'),
|
||||
DB::raw('COUNT(*) as total')
|
||||
@ -122,11 +122,11 @@ public function __invoke()
|
||||
->groupBy(DB::raw('HOUR(created_at)'))
|
||||
->get();
|
||||
|
||||
$ordersByHour = collect(range(0, 23))->map(function ($hour) use ($ordersByHourRaw) {
|
||||
$found = $ordersByHourRaw->firstWhere('hour', $hour);
|
||||
$salesByHour = collect(range(0, 23))->map(function ($hour) use ($salesByHourRaw) {
|
||||
$found = $salesByHourRaw->firstWhere('hour', $hour);
|
||||
|
||||
return [
|
||||
'hour' => str_pad($hour, 2, '0', STR_PAD_LEFT) . ':00',
|
||||
'hour' => str_pad($hour, 2, '0', STR_PAD_LEFT).':00',
|
||||
'total' => $found ? (int) $found->total : 0,
|
||||
];
|
||||
});
|
||||
@ -134,7 +134,7 @@ public function __invoke()
|
||||
return Inertia::render('analysis', [
|
||||
'stats' => $stats,
|
||||
'salesByMonth' => $salesByMonth,
|
||||
'ordersByHour' => $ordersByHour,
|
||||
'salesByHour' => $salesByHour,
|
||||
'paymentMethods' => $paymentMethods,
|
||||
'orderStatuses' => $orderStatuses,
|
||||
'orderChannels' => $orderChannels,
|
||||
|
||||
@ -1,84 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import type { ChartConfig } from "@/components/ui/chart";
|
||||
import {
|
||||
ChartContainer,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent
|
||||
} from "@/components/ui/chart";
|
||||
|
||||
export const description = "Menampilkan aktivitas toko berdasarkan pesanan yang masuk per jam."
|
||||
|
||||
const chartConfig = {
|
||||
total: {
|
||||
label: "Total Pesanan",
|
||||
color: "var(--chart-1)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function OrdersByHourChart({ data }: { data: any[] }) {
|
||||
return (
|
||||
<Card className="pt-0">
|
||||
<CardHeader className="flex items-center gap-2 space-y-0 border-b py-5 sm:flex-row">
|
||||
<div className="grid flex-1 gap-1">
|
||||
<CardTitle>Jam Sibuk</CardTitle>
|
||||
<CardDescription>
|
||||
Menampilkan aktivitas toko berdasarkan pesanan yang masuk per jam.
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="aspect-auto h-[250px] w-full"
|
||||
>
|
||||
<AreaChart data={data}>
|
||||
<defs>
|
||||
<linearGradient id="fillTotal" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="5%"
|
||||
stopColor="var(--color-total)"
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="95%"
|
||||
stopColor="var(--color-total)"
|
||||
stopOpacity={0.1}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="hour"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="dot" />}
|
||||
/>
|
||||
<Area
|
||||
dataKey="total"
|
||||
type="natural"
|
||||
fill="url(#fillTotal)"
|
||||
stroke="var(--color-total)"
|
||||
/>
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@ -17,11 +17,9 @@ import {
|
||||
ChartTooltipContent
|
||||
|
||||
} from "@/components/ui/chart"
|
||||
import type {ChartConfig} from "@/components/ui/chart";
|
||||
import type { ChartConfig } from "@/components/ui/chart";
|
||||
|
||||
export const description = "Menampilkan aktivitas toko berdasarkan pesanan yang masuk."
|
||||
|
||||
const chartConfig = {
|
||||
const defaultChartConfig = {
|
||||
today: {
|
||||
label: "Hari Ini",
|
||||
color: "var(--chart-1)",
|
||||
@ -32,48 +30,50 @@ const chartConfig = {
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function SalesByHourChart({ data }: { data: any[] }) {
|
||||
interface SalesByHourChartProps {
|
||||
data: any[];
|
||||
config?: ChartConfig;
|
||||
title?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export function SalesByHourChart({
|
||||
data,
|
||||
config = defaultChartConfig,
|
||||
title = "Jam Sibuk",
|
||||
description = "Menampilkan aktivitas toko berdasarkan pesanan yang masuk per jam."
|
||||
}: SalesByHourChartProps) {
|
||||
const keys = Object.keys(config);
|
||||
|
||||
return (
|
||||
<Card className="pt-0">
|
||||
<CardHeader className="flex items-center gap-2 space-y-0 border-b py-5 sm:flex-row">
|
||||
<div className="grid flex-1 gap-1">
|
||||
<CardTitle>Jam Sibuk</CardTitle>
|
||||
<CardDescription>
|
||||
Menampilkan aktivitas toko berdasarkan pesanan yang masuk.
|
||||
</CardDescription>
|
||||
<CardTitle>{title}</CardTitle>
|
||||
<CardDescription>{description}</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
config={config}
|
||||
className="aspect-auto h-[250px] w-full"
|
||||
>
|
||||
<AreaChart data={data}>
|
||||
<defs>
|
||||
<linearGradient id="fillToday" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="5%"
|
||||
stopColor="var(--color-today)"
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="95%"
|
||||
stopColor="var(--color-today)"
|
||||
stopOpacity={0.1}
|
||||
/>
|
||||
</linearGradient>
|
||||
<linearGradient id="fillYesterday" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="5%"
|
||||
stopColor="var(--color-yesterday)"
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="95%"
|
||||
stopColor="var(--color-yesterday)"
|
||||
stopOpacity={0.1}
|
||||
/>
|
||||
</linearGradient>
|
||||
{keys.map((key) => (
|
||||
<linearGradient key={key} id={`fill${key}`} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="5%"
|
||||
stopColor={`var(--color-${key})`}
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="95%"
|
||||
stopColor={`var(--color-${key})`}
|
||||
stopOpacity={0.1}
|
||||
/>
|
||||
</linearGradient>
|
||||
))}
|
||||
</defs>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
@ -86,18 +86,17 @@ export function SalesByHourChart({ data }: { data: any[] }) {
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="dot" />}
|
||||
/>
|
||||
<Area
|
||||
dataKey="yesterday"
|
||||
type="natural"
|
||||
fill="url(#fillYesterday)"
|
||||
stroke="var(--color-yesterday)"
|
||||
/>
|
||||
<Area
|
||||
dataKey="today"
|
||||
type="natural"
|
||||
fill="url(#fillToday)"
|
||||
stroke="var(--color-today)"
|
||||
/>
|
||||
{/* Render Areas in reverse order so first key in config is on top */}
|
||||
{[...keys].reverse().map((key) => (
|
||||
<Area
|
||||
key={key}
|
||||
dataKey={key}
|
||||
type="natural"
|
||||
fill={`url(#fill${key})`}
|
||||
stroke={`var(--color-${key})`}
|
||||
isAnimationActive={true}
|
||||
/>
|
||||
))}
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
|
||||
@ -4,11 +4,11 @@ import type { AnalysisPageProps } from '@/types';
|
||||
import { Head, usePage } from '@inertiajs/react';
|
||||
import { CustomBarChart } from '../components/charts/bar-chart';
|
||||
import { CustomPieChart } from '../components/charts/pie-chart';
|
||||
import { OrdersByHourChart } from '../components/charts/orders-by-hour-chart';
|
||||
import { SalesByHourChart } from '../components/charts/sales-by-hour-chart';
|
||||
import { RevenueVsPurchasesChart } from '../components/charts/revenue-vs-purchases-chart';
|
||||
|
||||
export default function Analisa() {
|
||||
const { stats, salesByMonth, ordersByHour, paymentMethods, orderStatuses, orderChannels, topProducts, topCustomers } =
|
||||
const { stats, salesByMonth, salesByHour, paymentMethods, orderStatuses, orderChannels, topProducts, topCustomers } =
|
||||
usePage<AnalysisPageProps>().props;
|
||||
|
||||
return (
|
||||
@ -51,7 +51,15 @@ export default function Analisa() {
|
||||
</div>
|
||||
|
||||
<RevenueVsPurchasesChart data={salesByMonth} />
|
||||
<OrdersByHourChart data={ordersByHour} />
|
||||
<SalesByHourChart
|
||||
data={salesByHour}
|
||||
config={{
|
||||
total: {
|
||||
label: "Total Penjualan",
|
||||
color: "var(--chart-1)",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
|
||||
<CustomPieChart
|
||||
|
||||
Loading…
Reference in New Issue
Block a user