diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php index 098fae5..2037598 100644 --- a/app/Http/Controllers/Admin/DashboardController.php +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -35,6 +35,7 @@ public function index(): Response 'revenueSummary' => $this->dashboardService->getRevenueSummary(), 'marketplaceSummary' => $this->dashboardService->getMarketplaceSummary(), 'cashAccounts' => $this->dashboardService->getCashAccounts(), + 'cashSummary' => $this->dashboardService->getCashSummary($startOfMonth, $endOfMonth), 'monthlyCashFlow' => $this->dashboardService->getMonthlyCashFlow(), 'monthlyExpenses' => $this->dashboardService->getMonthlyExpenses($startOfMonth, $endOfMonth), 'kasbonSummary' => $this->dashboardService->getKasbonSummary(), diff --git a/app/Services/System/DashboardService.php b/app/Services/System/DashboardService.php index c5ac42a..2532f47 100644 --- a/app/Services/System/DashboardService.php +++ b/app/Services/System/DashboardService.php @@ -83,7 +83,6 @@ public function getLowStockProducts(): array { return ProductVariant::query() ->where('stock', '<=', ProductVariant::minStock()) - ->where('stock', '>', 0) ->join('products', 'product_variants.product_id', '=', 'products.id') ->selectRaw("CONCAT(products.name, ' - ', product_variants.name) as full_name, product_variants.stock") ->orderBy('stock') @@ -100,7 +99,6 @@ public function getLowStockMaterials(): array { return RawMaterialPrice::query() ->where('stock', '<=', 5) - ->where('stock', '>', 0) ->join('raw_materials', 'raw_material_prices.raw_material_id', '=', 'raw_materials.id') ->selectRaw("CONCAT(raw_materials.name, ' - ', raw_material_prices.variant) as full_name, raw_material_prices.stock, raw_materials.unit") ->orderBy('stock') @@ -328,6 +326,24 @@ public function getCashAccounts(): array ->toArray(); } + public function getCashSummary(Carbon $startOfMonth, Carbon $endOfMonth): array + { + $summary = CashTransaction::query() + ->whereBetween('created_at', [$startOfMonth, $endOfMonth]) + ->selectRaw(" + COUNT(*) as total_transactions, + COALESCE(SUM(CASE WHEN type = 'deposit' THEN amount ELSE 0 END), 0) as total_deposit, + COALESCE(SUM(CASE WHEN type = 'withdrawal' THEN amount ELSE 0 END), 0) as total_withdrawal + ") + ->first(); + + return [ + 'total_transactions' => (int) ($summary->total_transactions ?? 0), + 'total_deposit' => (int) ($summary->total_deposit ?? 0), + 'total_withdrawal' => (int) ($summary->total_withdrawal ?? 0), + ]; + } + public function getMonthlyCashFlow(): array { $months = collect(); @@ -392,6 +408,10 @@ public function getKasbonSummary(): array ->selectRaw('COUNT(*) as count, COALESCE(SUM(amount), 0) as total') ->first(); + $totalEmployees = EmployeeAdvance::query() + ->distinct('employee_id') + ->count('employee_id'); + return [ 'pending' => [ 'count' => (int) ($pending->count ?? 0), @@ -406,6 +426,7 @@ public function getKasbonSummary(): array 'total' => (int) ($paid->total ?? 0), ], 'total' => (int) ($pending->total ?? 0) + (int) ($approved->total ?? 0) + (int) ($paid->total ?? 0), + 'total_employees' => $totalEmployees, ]; } @@ -506,10 +527,23 @@ public function getAttendanceToday(): array ->distinct('employee_id') ->count('employee_id'); + // Karyawan yang sedang cuti hari ini (approved & aktif hari ini) + $onLeave = LeaveRequest::query() + ->approved() + ->where('start_date', '<=', $today) + ->where('end_date', '>=', $today) + ->distinct('employee_id') + ->count('employee_id'); + + // Absent = tidak hadir & tidak sedang cuti + $notPresent = max(0, $totalEmployees - $present); + $absent = max(0, $notPresent - $onLeave); + return [ 'total_employees' => $totalEmployees, 'present' => $present, - 'absent' => max(0, $totalEmployees - $present), + 'absent' => $absent, + 'on_leave' => $onLeave, ]; } diff --git a/resources/js/components/card/LowStockCard.vue b/resources/js/components/card/LowStockCard.vue new file mode 100644 index 0000000..27d6231 --- /dev/null +++ b/resources/js/components/card/LowStockCard.vue @@ -0,0 +1,63 @@ + + + diff --git a/resources/js/components/card/StatCard.vue b/resources/js/components/card/StatCard.vue new file mode 100644 index 0000000..51cdc3a --- /dev/null +++ b/resources/js/components/card/StatCard.vue @@ -0,0 +1,60 @@ + + + diff --git a/resources/js/components/ui/chart/index.ts b/resources/js/components/ui/chart/index.ts index ef755ab..f95d7d6 100644 --- a/resources/js/components/ui/chart/index.ts +++ b/resources/js/components/ui/chart/index.ts @@ -1,4 +1,5 @@ import type { Component, InjectionKey, Ref } from 'vue' +import { createApp, h, ref } from 'vue' export const ChartConfigSymbol: InjectionKey> = Symbol('chartConfig') @@ -17,3 +18,22 @@ export { default as ChartTooltipContent } from './ChartTooltipContent.vue' export { default as ChartCrosshair } from './ChartCrosshair.vue' export { default as ChartLegend } from './ChartLegend.vue' export { default as ChartLegendContent } from './ChartLegendContent.vue' + +export function componentToString( + config: ChartConfig, + component: Component, + props?: any, +) { + if (typeof window === 'undefined') return '' + const container = document.createElement('div') + const app = createApp({ + render() { + return h(component, props) + }, + }) + app.provide(ChartConfigSymbol, ref(config)) + app.mount(container) + const html = container.innerHTML + app.unmount() + return html +} diff --git a/resources/js/pages/admin/Dashboard.vue b/resources/js/pages/admin/Dashboard.vue index 37f760b..95873b1 100644 --- a/resources/js/pages/admin/Dashboard.vue +++ b/resources/js/pages/admin/Dashboard.vue @@ -1,11 +1,29 @@