diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php
index e4dbd0a..e3806fa 100644
--- a/app/Http/Controllers/DashboardController.php
+++ b/app/Http/Controllers/DashboardController.php
@@ -9,6 +9,7 @@
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Purchase;
+use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
@@ -127,6 +128,19 @@ public function __invoke()
->limit(5)
->get();
+ /** @var User $user */
+ $user = auth()->user();
+ if ($user->hasRole('Admin')) {
+ unset(
+ $stats['cogs'],
+ $stats['aov'],
+ $stats['gross_profit'],
+ $stats['net_profit'],
+ $stats['profit_margin'],
+ $stats['total_purchases']
+ );
+ }
+
return Inertia::render('dashboard', [
'stats' => $stats,
'salesByHour' => $salesByHour,
diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx
index 7f47544..665dee9 100644
--- a/resources/js/pages/dashboard.tsx
+++ b/resources/js/pages/dashboard.tsx
@@ -1,15 +1,33 @@
-import { Head, usePage } from '@inertiajs/react';
-import { useEffect, useState } from 'react';
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().props;
+ 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(() => {
@@ -22,43 +40,43 @@ export default function Dashboard() {
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'
- };
-}
+ 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'
- };
-}
+ 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 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',
@@ -68,52 +86,103 @@ return {
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'
+ 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 (
<>
-
+
{/* Welcome Section */}
{/* 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 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) => (
+ {statItems.map((item, index) => (
))}
-
diff --git a/tests/Feature/Admin/DashboardTest.php b/tests/Feature/Admin/DashboardTest.php
index 974508d..45cb443 100644
--- a/tests/Feature/Admin/DashboardTest.php
+++ b/tests/Feature/Admin/DashboardTest.php
@@ -3,6 +3,7 @@
use App\Models\Expense;
use App\Models\Order;
use App\Models\Purchase;
+use Spatie\Permission\Models\Role;
use function Pest\Laravel\actingAs;
use function Pest\Laravel\get;
@@ -70,4 +71,28 @@
->has('topCustomers')
);
});
+
+ it('filters sensitive data for users with Admin role', function () {
+ Role::findOrCreate('Admin');
+ $user = createAuthorizedUser(['View:Dashboard']);
+ $user->assignRole('Admin');
+
+ actingAs($user);
+
+ get(route('dashboard'))
+ ->assertOk()
+ ->assertInertia(fn ($page) => $page
+ ->component('dashboard')
+ ->has('stats')
+ ->has('stats.total_sales')
+ ->has('stats.total_revenue')
+ ->missing('stats.cogs')
+ ->missing('stats.aov')
+ ->missing('stats.gross_profit')
+ ->missing('stats.net_profit')
+ ->missing('stats.profit_margin')
+ ->missing('stats.total_purchases')
+ ->has('topCustomers')
+ );
+ });
});