feat: implement analysis dashboard with new charts, controller, and reporting components
This commit is contained in:
parent
34190253e5
commit
b1eb893cfe
187
app/Http/Controllers/AnalysisController.php
Normal file
187
app/Http/Controllers/AnalysisController.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\OrderChannel;
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Enums\PaymentMethod;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Order;
|
||||
use App\Models\OrderItem;
|
||||
use App\Models\Purchase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
|
||||
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')),
|
||||
];
|
||||
|
||||
$stats['aov'] = $this->calculateAov($stats['total_revenue'], $stats['total_sales']);
|
||||
$stats['gross_profit'] = $this->calculateDiff($stats['total_revenue'], $stats['cogs']);
|
||||
$stats['net_profit'] = $this->calculateDiff($stats['gross_profit'], $stats['total_expenses']);
|
||||
$stats['profit_margin'] = $this->calculateMargin($stats['net_profit'], $stats['total_revenue']);
|
||||
|
||||
// Revenue vs Purchases per month (this year)
|
||||
$revenueByMonth = DB::table('orders')
|
||||
->select(
|
||||
DB::raw('MONTH(created_at) as month'),
|
||||
DB::raw('SUM(total) as total')
|
||||
)
|
||||
->groupBy(DB::raw('MONTH(created_at)'))
|
||||
->get();
|
||||
|
||||
$purchasesByMonth = DB::table('purchases')
|
||||
->select(
|
||||
DB::raw('MONTH(created_at) as month'),
|
||||
DB::raw('SUM(total) as total')
|
||||
)
|
||||
->groupBy(DB::raw('MONTH(created_at)'))
|
||||
->get();
|
||||
|
||||
$monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
|
||||
|
||||
$salesByMonth = collect(range(1, 12))->map(function ($month) use ($revenueByMonth, $purchasesByMonth, $monthNames) {
|
||||
$revenueFound = $revenueByMonth->firstWhere('month', $month);
|
||||
$purchasesFound = $purchasesByMonth->firstWhere('month', $month);
|
||||
|
||||
return [
|
||||
'month' => $monthNames[$month - 1],
|
||||
'sales' => $revenueFound ? (float) $revenueFound->total : 0,
|
||||
'purchase' => $purchasesFound ? (float) $purchasesFound->total : 0,
|
||||
];
|
||||
});
|
||||
|
||||
$paymentMethods = DB::table('orders')
|
||||
->select('payment_method as name', DB::raw('COUNT(*) as total'))
|
||||
->groupBy('payment_method')
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
if ($item->name) {
|
||||
$item->name = PaymentMethod::tryFrom($item->name)?->label() ?? $item->name;
|
||||
}
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
$orderStatuses = DB::table('orders')
|
||||
->select('order_status as name', DB::raw('COUNT(*) as total'))
|
||||
->groupBy('order_status')
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
if ($item->name) {
|
||||
$item->name = OrderStatus::tryFrom($item->name)?->label() ?? $item->name;
|
||||
}
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
$orderChannels = DB::table('orders')
|
||||
->select('order_channel as name', DB::raw('COUNT(*) as total'))
|
||||
->groupBy('order_channel')
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
if ($item->name) {
|
||||
$item->name = OrderChannel::tryFrom($item->name)?->label() ?? $item->name;
|
||||
}
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
$topProducts = DB::table('order_items')
|
||||
->join('products', 'order_items.product_id', '=', 'products.id')
|
||||
->select('products.name as name', DB::raw('SUM(order_items.qty) as total'))
|
||||
->groupBy('products.name')
|
||||
->orderByDesc('total')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
$topCustomers = DB::table('orders')
|
||||
->select('customer_name as name', DB::raw('SUM(total) as total'))
|
||||
->whereNotNull('customer_name')
|
||||
->groupBy('customer_name')
|
||||
->orderByDesc('total')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
$ordersByHourRaw = DB::table('orders')
|
||||
->select(
|
||||
DB::raw('HOUR(created_at) as hour'),
|
||||
DB::raw('COUNT(*) as total')
|
||||
)
|
||||
->groupBy(DB::raw('HOUR(created_at)'))
|
||||
->get();
|
||||
|
||||
$ordersByHour = collect(range(0, 23))->map(function ($hour) use ($ordersByHourRaw) {
|
||||
$found = $ordersByHourRaw->firstWhere('hour', $hour);
|
||||
|
||||
return [
|
||||
'hour' => str_pad($hour, 2, '0', STR_PAD_LEFT) . ':00',
|
||||
'total' => $found ? (int) $found->total : 0,
|
||||
];
|
||||
});
|
||||
|
||||
return Inertia::render('analysis', [
|
||||
'stats' => $stats,
|
||||
'salesByMonth' => $salesByMonth,
|
||||
'ordersByHour' => $ordersByHour,
|
||||
'paymentMethods' => $paymentMethods,
|
||||
'orderStatuses' => $orderStatuses,
|
||||
'orderChannels' => $orderChannels,
|
||||
'topProducts' => $topProducts,
|
||||
'topCustomers' => $topCustomers,
|
||||
]);
|
||||
}
|
||||
|
||||
private function getStats(callable $callback): array
|
||||
{
|
||||
$value = $callback();
|
||||
|
||||
return [
|
||||
'value' => $value,
|
||||
'yesterday' => null,
|
||||
'change' => null,
|
||||
];
|
||||
}
|
||||
|
||||
private function calculateAov($revenue, $sales): array
|
||||
{
|
||||
$value = $sales['value'] > 0 ? $revenue['value'] / $sales['value'] : 0;
|
||||
|
||||
return [
|
||||
'value' => $value,
|
||||
'yesterday' => null,
|
||||
'change' => null,
|
||||
];
|
||||
}
|
||||
|
||||
private function calculateDiff($a, $b): array
|
||||
{
|
||||
return [
|
||||
'value' => $a['value'] - $b['value'],
|
||||
'yesterday' => null,
|
||||
'change' => null,
|
||||
];
|
||||
}
|
||||
|
||||
private function calculateMargin($profit, $revenue): array
|
||||
{
|
||||
$value = $revenue['value'] > 0 ? ($profit['value'] / $revenue['value']) * 100 : 0;
|
||||
|
||||
return [
|
||||
'value' => round($value, 2),
|
||||
'yesterday' => null,
|
||||
'change' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,3 @@
|
||||
import { Link } from '@inertiajs/react';
|
||||
import { Boxes, DollarSign, History, LayoutGrid, List, ScrollText, Settings, ShoppingBag, ShoppingCart, User, Wallet } from 'lucide-react';
|
||||
import AppLogo from '@/components/app-logo';
|
||||
import { NavMain } from '@/components/nav-main';
|
||||
import {
|
||||
@ -10,8 +8,10 @@ import {
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { dashboard } from '@/routes';
|
||||
import { analysis, dashboard } from '@/routes';
|
||||
import category from '@/routes/category';
|
||||
import { Link } from '@inertiajs/react';
|
||||
import { BarChart3, Boxes, DollarSign, History, LayoutGrid, List, ScrollText, Settings, ShoppingBag, ShoppingCart, User, Wallet } from 'lucide-react';
|
||||
|
||||
import expense from '@/routes/expense';
|
||||
import order from '@/routes/order';
|
||||
@ -28,6 +28,11 @@ const mainNavItems: NavItem[] = [
|
||||
href: dashboard().url,
|
||||
icon: LayoutGrid,
|
||||
},
|
||||
{
|
||||
title: 'Analisa',
|
||||
href: analysis().url,
|
||||
icon: BarChart3,
|
||||
},
|
||||
];
|
||||
|
||||
const masterNavItems: NavItem[] = [
|
||||
|
||||
@ -20,7 +20,8 @@ export function StatCard({
|
||||
isPercentage = false,
|
||||
className
|
||||
}: StatCardProps) {
|
||||
const isPositive = stat.change >= 0;
|
||||
const hasComparison = stat.yesterday !== null;
|
||||
const isPositive = stat.change !== null && stat.change >= 0;
|
||||
|
||||
return (
|
||||
<Card className={cn("@container/card", className)}>
|
||||
@ -31,20 +32,24 @@ export function StatCard({
|
||||
<CardTitle className="text-2xl font-bold tabular-nums @[250px]/card:text-3xl">
|
||||
{isPercentage ? `${stat.value}%` : (isCurrency ? formatCurrency(stat.value) : formatNumber(stat.value))}
|
||||
</CardTitle>
|
||||
<CardAction>
|
||||
<Badge variant="default" className={`flex gap-1 px-1.5 py-0.5 text-xs font-bold ${isPositive ? 'bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300' : 'bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300'}`}>
|
||||
{isPositive ? <TrendingUp className="size-3" /> : <TrendingDown className="size-3" />}
|
||||
{stat.change === null
|
||||
? '∞'
|
||||
: `${Math.abs(stat.change)}%`}
|
||||
</Badge>
|
||||
</CardAction>
|
||||
{hasComparison && (
|
||||
<CardAction>
|
||||
<Badge variant="default" className={`flex gap-1 px-1.5 py-0.5 text-xs font-bold ${isPositive ? 'bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300' : 'bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300'}`}>
|
||||
{isPositive ? <TrendingUp className="size-3" /> : <TrendingDown className="size-3" />}
|
||||
{stat.change === null
|
||||
? '∞'
|
||||
: `${Math.abs(stat.change)}%`}
|
||||
</Badge>
|
||||
</CardAction>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-xs">
|
||||
<div className="text-muted-foreground">
|
||||
Kemarin: <span className="font-semibold">{isPercentage ? `${stat.yesterday}%` : (isCurrency ? formatCurrency(stat.yesterday) : formatNumber(stat.yesterday))}</span>
|
||||
</div>
|
||||
</CardFooter>
|
||||
{hasComparison && (
|
||||
<CardFooter className="flex-col items-start gap-1.5 text-xs">
|
||||
<div className="text-muted-foreground">
|
||||
Kemarin: <span className="font-semibold">{isPercentage ? `${stat.yesterday}%` : (isCurrency ? formatCurrency(stat.yesterday) : formatNumber(stat.yesterday))}</span>
|
||||
</div>
|
||||
</CardFooter>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
84
resources/js/components/charts/orders-by-hour-chart.tsx
Normal file
84
resources/js/components/charts/orders-by-hour-chart.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
"use client"
|
||||
|
||||
import { Bar, BarChart, 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"
|
||||
import { formatCurrency } from "@/lib/formatters"
|
||||
|
||||
export const description = "Perbandingan total penjualan dan belanja per bulan."
|
||||
|
||||
const chartConfig = {
|
||||
sales: {
|
||||
label: "Penjualan",
|
||||
color: "var(--chart-1)",
|
||||
},
|
||||
purchase: {
|
||||
label: "Belanja",
|
||||
color: "var(--chart-2)",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function RevenueVsPurchasesChart({ data }: { data: any[] }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Penjualan vs Belanja</CardTitle>
|
||||
<CardDescription>Perbandingan total penjualan dan belanja per bulan</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig} className="aspect-auto h-[300px] w-full">
|
||||
<BarChart accessibilityLayer data={data}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
indicator="dashed"
|
||||
formatter={(value, name) => [
|
||||
formatCurrency(Number(value)),
|
||||
` - ${chartConfig[name as keyof typeof chartConfig]?.label ?? name}`,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Bar dataKey="sales" fill="var(--color-sales)" radius={4} />
|
||||
<Bar dataKey="purchase" fill="var(--color-purchase)" radius={4} />
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@ -8,14 +8,14 @@ export const formatTime = (date: Date) => {
|
||||
|
||||
export const formatDate = (date: Date | string | undefined) => {
|
||||
if (!date) {
|
||||
return '-';
|
||||
}
|
||||
return '-';
|
||||
}
|
||||
|
||||
const parsedDate = date instanceof Date ? date : new Date(date);
|
||||
|
||||
if (isNaN(parsedDate.getTime())) {
|
||||
return '-';
|
||||
}
|
||||
return '-';
|
||||
}
|
||||
|
||||
return parsedDate.toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
@ -32,6 +32,13 @@ export const formatCurrency = (value: number) => {
|
||||
}).format(value);
|
||||
};
|
||||
|
||||
export const formatShortCurrency = (value: number) => {
|
||||
if (value >= 1_000_000_000) return `${(value / 1_000_000_000).toFixed(1)}M`;
|
||||
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}jt`;
|
||||
if (value >= 1_000) return `${(value / 1_000).toFixed(0)}rb`;
|
||||
return String(value);
|
||||
}
|
||||
|
||||
export const formatNumber = (value: number) => {
|
||||
return new Intl.NumberFormat('id-ID').format(value);
|
||||
};
|
||||
|
||||
104
resources/js/pages/analysis.tsx
Normal file
104
resources/js/pages/analysis.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import { StatCard } from '@/components/cards/stat-card';
|
||||
import { analysis } from '@/routes';
|
||||
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 { RevenueVsPurchasesChart } from '../components/charts/revenue-vs-purchases-chart';
|
||||
|
||||
export default function Analisa() {
|
||||
const { stats, salesByMonth, ordersByHour, paymentMethods, orderStatuses, orderChannels, topProducts, topCustomers } =
|
||||
usePage<AnalysisPageProps>().props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head title="Analisa" />
|
||||
|
||||
<div className="flex h-full min-h-screen flex-1 flex-col gap-4 overflow-x-auto rounded-xl p-6 @container/main">
|
||||
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Analisa</h1>
|
||||
</div>
|
||||
|
||||
{/* 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 @xl/main:grid-cols-2 @4xl/main:grid-cols-4 dark:*:data-[slot=card]:bg-card">
|
||||
{[
|
||||
{ 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 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) => (
|
||||
<div
|
||||
key={index}
|
||||
className="animate-in fade-in slide-in-from-bottom-4 fill-mode-both"
|
||||
style={{ animationDelay: `${index * 100}ms` }}
|
||||
>
|
||||
<StatCard
|
||||
{...item}
|
||||
className="transition-all duration-300 hover:scale-[1.03] hover:shadow-xl hover:-translate-y-1"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<RevenueVsPurchasesChart data={salesByMonth} />
|
||||
<OrdersByHourChart data={ordersByHour} />
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
|
||||
<CustomPieChart
|
||||
title="Metode Pembayaran"
|
||||
description="Distribusi pesanan berdasarkan metode pembayaran."
|
||||
data={paymentMethods}
|
||||
colorOffset={0}
|
||||
/>
|
||||
<CustomPieChart
|
||||
title="Status Pesanan"
|
||||
description="Distribusi pesanan berdasarkan status."
|
||||
data={orderStatuses}
|
||||
colorOffset={120}
|
||||
/>
|
||||
<CustomPieChart
|
||||
title="Channel Pesanan"
|
||||
description="Distribusi pesanan berdasarkan channel/platform."
|
||||
data={orderChannels}
|
||||
colorOffset={240}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<CustomBarChart
|
||||
title="Top 5 Produk Terlaris"
|
||||
description="Berdasarkan jumlah produk yang terjual."
|
||||
data={topProducts}
|
||||
colorOffset={45}
|
||||
/>
|
||||
<CustomBarChart
|
||||
title="Top 5 Pelanggan Setia"
|
||||
description="Berdasarkan total nominal belanja."
|
||||
data={topCustomers}
|
||||
colorOffset={180}
|
||||
isCurrency={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Analisa.layout = {
|
||||
breadcrumbs: [
|
||||
{
|
||||
title: 'Analisa',
|
||||
href: analysis().url,
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -26,3 +26,16 @@ export interface DashboardPageProps {
|
||||
stats: DashboardStats;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface AnalysisPageProps {
|
||||
auth: Auth;
|
||||
stats: DashboardStats;
|
||||
salesByMonth: any[];
|
||||
ordersByHour: any[];
|
||||
paymentMethods: any[];
|
||||
orderStatuses: any[];
|
||||
orderChannels: any[];
|
||||
topProducts: any[];
|
||||
topCustomers: any[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@ -7,10 +7,12 @@
|
||||
'canRegister' => Features::enabled(Features::registration()),
|
||||
])->name('home');
|
||||
|
||||
use App\Http\Controllers\AnalysisController;
|
||||
use App\Http\Controllers\DashboardController;
|
||||
|
||||
Route::middleware(['auth', 'verified'])->group(function () {
|
||||
Route::get('dashboard', DashboardController::class)->name('dashboard');
|
||||
Route::get('analysis', AnalysisController::class)->name('analysis');
|
||||
});
|
||||
|
||||
require __DIR__.'/settings.php';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user