dress/resources/js/pages/dashboard.tsx

237 lines
9.0 KiB
TypeScript

import { StatCard } from '@/components/cards/stat-card';
import { WelcomeCard } from '@/components/cards/welcome-card';
import { dashboard } from '@/routes';
import type { DashboardPageProps } from '@/types';
import { Head, usePage } from '@inertiajs/react';
import { useEffect, useState } from 'react';
import { CustomBarChart } from '../components/charts/bar-chart';
import { CustomPieChart } from '../components/charts/pie-chart';
import { SalesByHourChart } from '../components/charts/sales-by-hour-chart';
export default function Dashboard() {
const {
auth,
stats,
salesByHour,
paymentMethods,
orderStatuses,
orderChannels,
topProducts,
topCustomers,
} = usePage<
DashboardPageProps & {
salesByHour: any[];
paymentMethods: any[];
orderStatuses: any[];
orderChannels: any[];
topProducts: any[];
topCustomers: any[];
}
>().props;
const [time, setTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(timer);
}, []);
const getTimeTheme = () => {
const hour = time.getHours();
if (hour >= 5 && hour < 11) {
return {
greeting: 'Selamat Pagi',
class: 'bg-gradient-to-br from-amber-50 to-orange-100 dark:from-amber-950/40 dark:to-orange-900/40 border-amber-200 dark:border-amber-800/30 shadow-sm',
text: 'text-amber-950 dark:text-amber-50',
subtext: 'text-amber-900/70 dark:text-amber-200/70',
decoration: 'bg-amber-500/10 dark:bg-amber-500/20',
pageBackground: 'bg-amber-50/50 dark:bg-amber-950/10',
card: 'bg-white/80 dark:bg-amber-900/10 backdrop-blur-sm border-amber-200/50 dark:border-amber-800/20',
placeholder: 'stroke-amber-500/20 dark:stroke-amber-400/20',
};
}
if (hour >= 11 && hour < 15) {
return {
greeting: 'Selamat Siang',
class: 'bg-gradient-to-br from-sky-50 to-blue-100 dark:from-sky-950/40 dark:to-blue-900/40 border-sky-200 dark:border-sky-800/30 shadow-sm',
text: 'text-sky-950 dark:text-sky-50',
subtext: 'text-sky-900/70 dark:text-sky-200/70',
decoration: 'bg-sky-500/10 dark:bg-sky-500/20',
pageBackground: 'bg-sky-50/50 dark:bg-sky-950/10',
card: 'bg-white/80 dark:bg-sky-900/10 backdrop-blur-sm border-sky-200/50 dark:border-sky-800/20',
placeholder: 'stroke-sky-500/20 dark:stroke-sky-400/20',
};
}
if (hour >= 15 && hour < 19) {
return {
greeting: 'Selamat Sore',
class: 'bg-gradient-to-br from-orange-50 to-purple-100 dark:from-orange-950/40 dark:to-purple-900/40 border-orange-200 dark:border-orange-800/30 shadow-sm',
text: 'text-orange-950 dark:text-orange-50',
subtext: 'text-orange-900/70 dark:text-orange-200/70',
decoration: 'bg-orange-500/10 dark:bg-orange-500/20',
pageBackground: 'bg-orange-50/50 dark:bg-orange-950/10',
card: 'bg-white/80 dark:bg-orange-900/10 backdrop-blur-sm border-orange-200/50 dark:border-orange-800/20',
placeholder: 'stroke-orange-500/20 dark:stroke-orange-400/20',
};
}
return {
greeting: 'Selamat Malam',
class: 'bg-gradient-to-br from-slate-900 via-indigo-950 to-black text-white border-indigo-500/20 shadow-indigo-950',
text: 'text-indigo-50 drop-shadow-md',
subtext: 'text-indigo-200/70 drop-shadow-sm',
decoration: 'bg-indigo-500/20',
pageBackground: 'bg-slate-100 dark:bg-slate-950',
card: 'bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-800',
placeholder: 'stroke-slate-300 dark:stroke-slate-700',
};
};
const theme = getTimeTheme();
const isAdmin = auth.roles.includes('Admin');
const statItems = [
{
title: 'Total Penjualan',
stat: stats.total_sales,
isCurrency: false,
},
{
title: 'Total Pendapatan',
stat: stats.total_revenue,
},
{
title: 'Total Pengeluaran',
stat: stats.total_expenses,
},
{
title: 'HPP (Modal)',
stat: stats.cogs,
sensitive: true,
},
{
title: 'AOV (Avg Order Value)',
stat: stats.aov,
sensitive: true,
},
{
title: 'Profit Margin',
stat: stats.profit_margin,
isPercentage: true,
sensitive: true,
},
{
title: 'Total Diskon',
stat: stats.total_discount,
},
{
title: 'Laba Kotor',
stat: stats.gross_profit,
sensitive: true,
},
{
title: 'Laba Bersih',
stat: stats.net_profit,
sensitive: true,
},
{
title: 'Total Pembelian',
stat: stats.total_purchases,
sensitive: true,
},
{
title: 'Produk Terjual',
stat: stats.products_sold,
isCurrency: false,
},
{
title: 'Total Pelanggan',
stat: stats.total_customers,
isCurrency: false,
},
].filter((item) => !isAdmin || !item.sensitive);
return (
<>
<Head title="Dasbor" />
<div
className={`@container/main flex h-full min-h-screen flex-1 flex-col gap-4 overflow-x-auto rounded-xl p-3 transition-colors duration-1000`}
>
{/* Welcome Section */}
<WelcomeCard user={auth.user} time={time} theme={theme} />
{/* Stats Grid */}
<div className="grid grid-cols-1 gap-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card *:data-[slot=card]:shadow-xs @sm/main:grid-cols-2 @3xl/main:grid-cols-3 @5xl/main:grid-cols-4 @7xl/main:grid-cols-6 dark:*:data-[slot=card]:bg-card">
{statItems.map((item, index) => (
<div
key={index}
className="animate-in fill-mode-both fade-in slide-in-from-bottom-4"
style={{ animationDelay: `${index * 100}ms` }}
>
<StatCard
{...item}
className="!gap-3 !py-4 transition-all duration-300 hover:-translate-y-1 hover:scale-[1.03] hover:shadow-xl [&_[data-slot=card-footer]]:!px-4 [&_[data-slot=card-header]]:!px-4"
/>
</div>
))}
</div>
<SalesByHourChart
title="Jam Sibuk"
description="Grafik aktivitas transaksi berdasarkan waktu (jam) dalam sehari."
data={salesByHour}
/>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
<CustomPieChart
title="Metode Pembayaran"
description="Distribusi transaksi berdasarkan metode pembayaran yang digunakan."
data={paymentMethods}
colorOffset={0}
/>
<CustomPieChart
title="Status Pesanan"
description="Status terkini dari seluruh pesanan yang masuk."
data={orderStatuses}
colorOffset={120}
/>
<CustomPieChart
title="Channel Pesanan"
description="Sumber pesanan berdasarkan platform atau saluran penjualan."
data={orderChannels}
colorOffset={240}
/>
</div>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
<CustomBarChart
title="Top 5 Produk"
description="Daftar produk dengan volume penjualan tertinggi."
data={topProducts}
colorOffset={45}
/>
<CustomBarChart
title="Top 5 Pelanggan"
description="Pelanggan dengan total nominal belanja tertinggi (setia)."
data={topCustomers}
colorOffset={180}
isCurrency={true}
/>
</div>
</div>
</>
);
}
Dashboard.layout = {
breadcrumbs: [
{
title: 'Dasbor',
href: dashboard(),
},
],
};