diff --git a/app/Http/Controllers/AnalysisController.php b/app/Http/Controllers/AnalysisController.php
index a03e400..69220e9 100644
--- a/app/Http/Controllers/AnalysisController.php
+++ b/app/Http/Controllers/AnalysisController.php
@@ -44,7 +44,14 @@ public function __invoke(Request $request)
'total_discount' => $this->getStats(fn () => $applyFilter(Order::query())->sum('discount')),
'total_purchases' => $this->getStats(fn () => $applyFilter(Purchase::query())->sum('total')),
'products_sold' => $this->getStats(fn () => $applyFilter(OrderItem::query())->sum('qty')),
- 'total_customers' => $this->getStats(fn () => $applyFilter(Order::query())->distinct('customer_name')->count('customer_name')),
+ 'total_customers' => $this->getStats(fn () => $applyFilter(Order::query())->whereNotNull('customer_name')->distinct('customer_name')->count('customer_name')),
+ 'repeat_customers' => $this->getStats(fn () => $applyFilter(DB::table('orders'))
+ ->whereNotNull('customer_name')
+ ->select('customer_name')
+ ->groupBy('customer_name')
+ ->havingRaw('COUNT(*) > 1')
+ ->get()
+ ->count()),
];
$stats['aov'] = $this->calculateAov($stats['total_revenue'], $stats['total_sales']);
@@ -56,6 +63,14 @@ public function __invoke(Request $request)
];
$stats['profit_margin'] = $this->calculateMargin($stats['net_profit'], $stats['total_revenue']);
+ $totalCustomers = $stats['total_customers']['value'];
+ $repeatCustomers = $stats['repeat_customers']['value'];
+ $stats['repeat_order_percentage'] = [
+ 'value' => $totalCustomers > 0 ? round(($repeatCustomers / $totalCustomers) * 100, 2) : 0,
+ 'yesterday' => null,
+ 'change' => null,
+ ];
+
$isSqlite = DB::getDriverName() === 'sqlite';
$monthSelect = $isSqlite ? "CAST(strftime('%m', created_at) AS INTEGER)" : 'MONTH(created_at)';
diff --git a/resources/js/components/charts/knob-chart.tsx b/resources/js/components/charts/knob-chart.tsx
new file mode 100644
index 0000000..b4fc2b6
--- /dev/null
+++ b/resources/js/components/charts/knob-chart.tsx
@@ -0,0 +1,129 @@
+"use client"
+
+import React, { useMemo } from "react"
+import {
+ Label,
+ PolarGrid,
+ PolarRadiusAxis,
+ RadialBar,
+ RadialBarChart,
+} from "recharts"
+
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card"
+import { ChartConfig, ChartContainer } from "@/components/ui/chart"
+
+export const KnobChart = React.memo(function KnobChart({
+ title,
+ description,
+ percentage,
+ label,
+ footerText
+}: {
+ title: string
+ description: string
+ percentage: number
+ label?: string
+ footerText?: string
+}) {
+ const { chartData, chartConfig } = useMemo(() => {
+ return {
+ chartData: [
+ {
+ name: "value",
+ value: percentage,
+ fill: "hsl(var(--primary))",
+ },
+ ],
+ chartConfig: {
+ value: {
+ label: label || "Value",
+ },
+ } satisfies ChartConfig,
+ }
+ }, [percentage, label])
+
+ return (
+