feat: add sales by hour statistics to dashboard with interactive chart component
This commit is contained in:
parent
80b7705b3f
commit
7ee44c8fd9
@ -7,6 +7,7 @@
|
|||||||
use App\Models\OrderItem;
|
use App\Models\OrderItem;
|
||||||
use App\Models\Purchase;
|
use App\Models\Purchase;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
|
|
||||||
class DashboardController extends Controller
|
class DashboardController extends Controller
|
||||||
@ -32,8 +33,39 @@ public function __invoke()
|
|||||||
$stats['net_profit'] = $this->calculateDiff($stats['gross_profit'], $stats['total_expenses']);
|
$stats['net_profit'] = $this->calculateDiff($stats['gross_profit'], $stats['total_expenses']);
|
||||||
$stats['profit_margin'] = $this->calculateMargin($stats['net_profit'], $stats['total_revenue']);
|
$stats['profit_margin'] = $this->calculateMargin($stats['net_profit'], $stats['total_revenue']);
|
||||||
|
|
||||||
|
// Get sales by hour
|
||||||
|
$todayOrders = DB::table('orders')
|
||||||
|
->whereDate('created_at', $today)
|
||||||
|
->select(
|
||||||
|
DB::raw('HOUR(created_at) as hour'),
|
||||||
|
DB::raw('COUNT(*) as total')
|
||||||
|
)
|
||||||
|
->groupBy(DB::raw('HOUR(created_at)'))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$yesterdayOrders = DB::table('orders')
|
||||||
|
->whereDate('created_at', $yesterday)
|
||||||
|
->select(
|
||||||
|
DB::raw('HOUR(created_at) as hour'),
|
||||||
|
DB::raw('COUNT(*) as total')
|
||||||
|
)
|
||||||
|
->groupBy(DB::raw('HOUR(created_at)'))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$salesByHour = collect(range(0, 23))->map(function ($hour) use ($todayOrders, $yesterdayOrders) {
|
||||||
|
$todayFound = $todayOrders->firstWhere('hour', $hour);
|
||||||
|
$yesterdayFound = $yesterdayOrders->firstWhere('hour', $hour);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'hour' => str_pad($hour, 2, '0', STR_PAD_LEFT).':00',
|
||||||
|
'today' => $todayFound ? (int) $todayFound->total : 0,
|
||||||
|
'yesterday' => $yesterdayFound ? (int) $yesterdayFound->total : 0,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
return Inertia::render('dashboard', [
|
return Inertia::render('dashboard', [
|
||||||
'stats' => $stats,
|
'stats' => $stats,
|
||||||
|
'salesByHour' => $salesByHour,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
106
resources/js/components/charts/sales-by-hour-chart.tsx
Normal file
106
resources/js/components/charts/sales-by-hour-chart.tsx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card"
|
||||||
|
import {
|
||||||
|
ChartContainer,
|
||||||
|
ChartLegend,
|
||||||
|
ChartLegendContent,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
type ChartConfig,
|
||||||
|
} from "@/components/ui/chart"
|
||||||
|
|
||||||
|
export const description = "Menampilkan aktivitas toko berdasarkan pesanan yang masuk."
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
today: {
|
||||||
|
label: "Hari Ini",
|
||||||
|
color: "var(--chart-1)",
|
||||||
|
},
|
||||||
|
yesterday: {
|
||||||
|
label: "Kemarin",
|
||||||
|
color: "var(--chart-2)",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig
|
||||||
|
|
||||||
|
export function SalesByHourChart({ 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.
|
||||||
|
</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="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>
|
||||||
|
</defs>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<XAxis
|
||||||
|
dataKey="hour"
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
tickMargin={8}
|
||||||
|
/>
|
||||||
|
<ChartTooltip
|
||||||
|
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)"
|
||||||
|
/>
|
||||||
|
<ChartLegend content={<ChartLegendContent />} />
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -4,10 +4,10 @@ import { dashboard } from '@/routes';
|
|||||||
import { DashboardPageProps } from '@/types';
|
import { DashboardPageProps } from '@/types';
|
||||||
import { StatCard } from '@/components/cards/stat-card';
|
import { StatCard } from '@/components/cards/stat-card';
|
||||||
import { WelcomeCard } from '@/components/cards/welcome-card';
|
import { WelcomeCard } from '@/components/cards/welcome-card';
|
||||||
|
import { SalesByHourChart } from '../components/charts/sales-by-hour-chart';
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const { auth, stats } = usePage<DashboardPageProps>().props;
|
const { auth, stats, salesByHour } = usePage<DashboardPageProps & { salesByHour: any[] }>().props;
|
||||||
const [time, setTime] = useState(new Date());
|
const [time, setTime] = useState(new Date());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -99,6 +99,8 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SalesByHourChart data={salesByHour} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user