diff --git a/resources/js/components/cards/stat-card.tsx b/resources/js/components/cards/stat-card.tsx index 7a0ca95..f854ee9 100644 --- a/resources/js/components/cards/stat-card.tsx +++ b/resources/js/components/cards/stat-card.tsx @@ -1,9 +1,16 @@ -import { TrendingUp, TrendingDown } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; -import { Card, CardHeader, CardTitle, CardDescription, CardAction, CardFooter } from '@/components/ui/card'; +import { + Card, + CardDescription, + CardFooter, + CardHeader, + CardTitle, + CardAction, +} from '@/components/ui/card'; import { formatCurrency, formatNumber } from '@/lib/formatters'; import { cn } from '@/lib/utils'; import type { StatItem } from '@/types'; +import { TrendingDown, TrendingUp } from 'lucide-react'; export interface StatCardProps { title: string; @@ -18,35 +25,59 @@ export function StatCard({ stat, isCurrency = true, isPercentage = false, - className + className, }: StatCardProps) { const hasComparison = stat.yesterday !== null; const isPositive = stat.change !== null && stat.change >= 0; return ( - - -
- {title} -
- - {isPercentage ? `${stat.value}%` : (isCurrency ? formatCurrency(stat.value) : formatNumber(stat.value))} - - {hasComparison && ( - - - {isPositive ? : } + + +
+ + {title} + + {hasComparison && ( + + {isPositive ? ( + + ) : ( + + )} {stat.change === null ? '∞' : `${Math.abs(stat.change)}%`} - - )} + )} +
+ + + {isPercentage + ? `${stat.value}%` + : isCurrency + ? formatCurrency(stat.value) + : formatNumber(stat.value)} +
+ {hasComparison && ( -
- Kemarin: {isPercentage ? `${stat.yesterday}%` : (isCurrency ? formatCurrency(stat.yesterday) : formatNumber(stat.yesterday))} +
+ Kemarin:{' '} + + {isPercentage + ? `${stat.yesterday}%` + : isCurrency + ? formatCurrency(stat.yesterday) + : formatNumber(stat.yesterday)} +
)} diff --git a/resources/js/pages/analysis.tsx b/resources/js/pages/analysis.tsx index bc7afbe..eeff285 100644 --- a/resources/js/pages/analysis.tsx +++ b/resources/js/pages/analysis.tsx @@ -1,26 +1,48 @@ import { StatCard } from '@/components/cards/stat-card'; import { Button } from '@/components/ui/button'; import { Calendar } from '@/components/ui/calendar'; -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { formatDate } from '@/lib/formatters'; import { analysis } from '@/routes'; import type { AnalysisPageProps } from '@/types'; import { Head, router, usePage } from '@inertiajs/react'; import { CalendarIcon, Filter, FilterX } from 'lucide-react'; import { useState } from 'react'; -import { formatDate } from '@/lib/formatters'; import { AovTrendChart } from '../components/charts/aov-trend-chart'; import { CustomBarChart } from '../components/charts/bar-chart'; +import { KnobChart } from '../components/charts/knob-chart'; import { CustomPieChart } from '../components/charts/pie-chart'; import { RevenueVsProfitChart } from '../components/charts/revenue-vs-profit-chart'; import { RevenueVsPurchasesChart } from '../components/charts/revenue-vs-purchases-chart'; import { SalesByHourChart } from '../components/charts/sales-by-hour-chart'; import { TransactionVolumeChart } from '../components/charts/transaction-volume-chart'; -import { KnobChart } from '../components/charts/knob-chart'; export default function Analisa() { - const { stats, salesByMonth, revenueVsProfit, transactionVolume, salesByHour, paymentMethods, orderStatuses, orderChannels, topProducts, topCustomers, topCategories, filters } = - usePage().props; + const { + stats, + salesByMonth, + revenueVsProfit, + transactionVolume, + salesByHour, + paymentMethods, + orderStatuses, + orderChannels, + topProducts, + topCustomers, + topCategories, + filters, + } = usePage().props; const [period, setPeriod] = useState(filters.period || 'all'); const [startDate, setStartDate] = useState(filters.start_date || ''); @@ -28,19 +50,28 @@ export default function Analisa() { const [isStartOpen, setIsStartOpen] = useState(false); const [isEndOpen, setIsEndOpen] = useState(false); - const formatToYmd = (date: Date) => { - return date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, '0') + "-" + String(date.getDate()).padStart(2, '0'); + return ( + date.getFullYear() + + '-' + + String(date.getMonth() + 1).padStart(2, '0') + + '-' + + String(date.getDate()).padStart(2, '0') + ); }; const applyFilters = (newParams: any) => { - router.get(analysis().url, { - ...filters, - ...newParams - }, { - preserveState: true, - preserveScroll: true, - }); + router.get( + analysis().url, + { + ...filters, + ...newParams, + }, + { + preserveState: true, + preserveScroll: true, + }, + ); }; const handlePeriodChange = (value: string) => { @@ -53,7 +84,11 @@ export default function Analisa() { const handleCustomFilter = () => { if (startDate && endDate) { setPeriod('custom'); - applyFilters({ period: 'custom', start_date: startDate, end_date: endDate }); + applyFilters({ + period: 'custom', + start_date: startDate, + end_date: endDate, + }); } }; @@ -69,10 +104,14 @@ export default function Analisa() { return `${baseDesc} (Periode: ${filters.start_date} s/d ${filters.end_date})`; } switch (filters.period) { - case 'week': return `${baseDesc}`; - case 'month': return `${baseDesc}`; - case 'year': return `${baseDesc}`; - default: return `${baseDesc}`; + case 'week': + return `${baseDesc}`; + case 'month': + return `${baseDesc}`; + case 'year': + return `${baseDesc}`; + default: + return `${baseDesc}`; } }; @@ -80,16 +119,22 @@ export default function Analisa() { <> -
- +
-

Analisa

-

Lihat performa bisnis Anda secara mendalam.

+

+ Analisa +

+

+ Lihat performa bisnis Anda secara mendalam. +

- @@ -102,7 +147,10 @@ export default function Analisa() {
- + - + { - setStartDate(date ? formatToYmd(date) : ''); + setStartDate( + date ? formatToYmd(date) : '', + ); setIsStartOpen(false); }} captionLayout="dropdown" @@ -129,9 +188,14 @@ export default function Analisa() { - s/d + + s/d + - + - + { - setEndDate(date ? formatToYmd(date) : ''); + setEndDate( + date ? formatToYmd(date) : '', + ); setIsEndOpen(false); }} captionLayout="dropdown" @@ -185,30 +260,55 @@ export default function Analisa() {
{/* Stats Grid */} -
+
{[ - { 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 }, - { title: "AOV (Avg Order Value)", stat: stats.aov }, - { title: "Profit Margin", stat: stats.profit_margin, isPercentage: true }, - { title: "Total Diskon", stat: stats.total_discount }, - { title: "Laba Kotor", stat: stats.gross_profit }, - { title: "Laba Bersih", stat: stats.net_profit }, - { title: "Total Gaji", stat: stats.total_payrolls }, - { title: "Total Pembelian", stat: stats.total_purchases }, - { title: "Produk Terjual", stat: stats.products_sold, isCurrency: false }, - { title: "Total Pelanggan", stat: stats.total_customers, isCurrency: false }, + { + 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 }, + { title: 'AOV (Avg Order Value)', stat: stats.aov }, + { + title: 'Profit Margin', + stat: stats.profit_margin, + isPercentage: true, + }, + { title: 'Total Diskon', stat: stats.total_discount }, + { title: 'Laba Kotor', stat: stats.gross_profit }, + { title: 'Laba Bersih', stat: stats.net_profit }, + { title: 'Total Gaji', stat: stats.total_payrolls }, + { + title: 'Total Pembelian', + stat: stats.total_purchases, + }, + { + title: 'Produk Terjual', + stat: stats.products_sold, + isCurrency: false, + }, + { + title: 'Total Pelanggan', + stat: stats.total_customers, + isCurrency: false, + }, ].map((item, index) => (
))} @@ -219,12 +319,16 @@ export default function Analisa() {
@@ -233,16 +337,22 @@ export default function Analisa() { @@ -264,25 +376,33 @@ export default function Analisa() {
@@ -291,13 +411,17 @@ export default function Analisa() {
-
+
{/* Welcome Section */} {/* Stats Grid */} -
+
{[ { title: "Total Penjualan", stat: stats.total_sales, isCurrency: false }, { title: "Total Pendapatan", stat: stats.total_revenue }, @@ -104,7 +104,7 @@ return { >
))} @@ -116,7 +116,7 @@ return { data={salesByHour} /> -
+
-
+