From e8e7a1fce517025b56083f1615ddccb338b4d9df Mon Sep 17 00:00:00 2001 From: vchandra22 Date: Sun, 27 Apr 2025 20:40:49 +0700 Subject: [PATCH] feat: create dashboard page add the charts for report patient --- app/Http/Controllers/DashboardController.php | 54 +++ .../Controllers/TransactionController.php | 16 - app/Models/Patient.php | 16 +- package-lock.json | 348 +++++++++++++++++- package.json | 1 + resources/js/components/app-sidebar.tsx | 11 +- resources/js/pages/dashboard.tsx | 84 ++++- routes/web.php | 5 +- 8 files changed, 485 insertions(+), 50 deletions(-) create mode 100644 app/Http/Controllers/DashboardController.php diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php new file mode 100644 index 0000000..8cdaae6 --- /dev/null +++ b/app/Http/Controllers/DashboardController.php @@ -0,0 +1,54 @@ +where('registration_datetime', '>=', now()->subDays(7)) + ->groupBy('date') + ->orderBy('date') + ->get() + ->map(function ($item) { + return [ + 'date' => Carbon::parse($item->date)->format('d M'), + 'count' => $item->count + ]; + }); + + // Data pendapatan 7 hari terakhir + $dailyRevenue = Transaction::selectRaw('DATE(transaction_datetime) as date, SUM(grand_total) as total') + ->where('transaction_datetime', '>=', now()->subDays(7)) + ->groupBy('date') + ->orderBy('date') + ->get() + ->map(function ($item) { + return [ + 'date' => Carbon::parse($item->date)->format('d M'), + 'total' => (float) $item->total + ]; + }); + + // Statistik hari ini + $todayStats = [ + 'patient_count' => Registration::whereDate('registration_datetime', today())->count(), + 'revenue' => Transaction::whereDate('transaction_datetime', today())->sum('grand_total'), + 'pending_payments' => Transaction::where('status', 'pending')->count(), + ]; + + return Inertia::render('dashboard', [ + 'patientRegistrations' => $patientRegistrations, + 'dailyRevenue' => $dailyRevenue, + 'todayStats' => $todayStats + ]); + } +} diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 8a1382b..0b7fc46 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -131,7 +131,6 @@ class TransactionController extends Controller */ public function store(Request $request) { - // Validate the main transaction data $validated = $request->validate([ 'registration_id' => 'required|exists:t_registration,id', 'patient_id' => 'required|exists:m_patient,id', @@ -154,26 +153,20 @@ class TransactionController extends Controller 'notes' => 'nullable|string', ]); - // Start database transaction return DB::transaction(function () use ($validated, $request) { - // Generate invoice number $invoiceNumber = Transaction::generateInvoiceNumber(); - // Get procedure details $procedure = Procedure::findOrFail($validated['procedure_id']); - // Set default values for optional fields $discountAmount = $validated['discount_amount'] ?? 0; $insuranceCoveredAmount = $validated['insurance_covered_amount'] ?? 0; $patientResponsibility = $validated['grand_total'] - $insuranceCoveredAmount; - // Calculate change amount if payment made $changeAmount = 0; if ($validated['paid_amount'] > $patientResponsibility) { $changeAmount = $validated['paid_amount'] - $patientResponsibility; } - // Create transaction $transaction = Transaction::create([ 'invoice_number' => $invoiceNumber, 'registration_id' => $validated['registration_id'], @@ -197,7 +190,6 @@ class TransactionController extends Controller 'notes' => $validated['notes'] ?? null, ]); - // Create transaction detail TransactionDetail::create([ 'transaction_id' => $transaction->id, 'procedure_id' => $validated['procedure_id'], @@ -212,7 +204,6 @@ class TransactionController extends Controller 'notes' => $validated['notes'] ?? null, ]); - // Update registration payment status if needed if ($validated['status'] === 'paid') { $registration = Registration::findOrFail($validated['registration_id']); $registration->update(['payment_status' => 'paid']); @@ -274,7 +265,6 @@ class TransactionController extends Controller ]; }); - // Get the first detail for the form $firstDetail = $transaction->details->first(); // Hitung ulang patient_responsibility jika diperlukan @@ -362,21 +352,17 @@ class TransactionController extends Controller ]); return DB::transaction(function () use ($validated, $transaction) { - // Get procedure details $procedure = Procedure::findOrFail($validated['procedure_id']); - // Set default values for optional fields $discountAmount = $validated['discount_amount'] ?? 0; $insuranceCoveredAmount = $validated['insurance_covered_amount'] ?? 0; $patientResponsibility = $validated['grand_total'] - $insuranceCoveredAmount; - // Calculate change amount if payment made $changeAmount = 0; if ($validated['paid_amount'] > $patientResponsibility) { $changeAmount = $validated['paid_amount'] - $patientResponsibility; } - // Update transaction $transaction->update([ 'registration_id' => $validated['registration_id'], 'patient_id' => $validated['patient_id'], @@ -398,7 +384,6 @@ class TransactionController extends Controller 'notes' => $validated['notes'], ]); - // Update the first transaction detail or create a new one $detail = TransactionDetail::where('transaction_id', $transaction->id)->first(); $detailData = [ 'transaction_id' => $transaction->id, @@ -420,7 +405,6 @@ class TransactionController extends Controller TransactionDetail::create($detailData); } - // Update registration payment status if needed $registration = Registration::findOrFail($validated['registration_id']); if ($validated['status'] === 'paid') { $registration->update(['payment_status' => 'paid']); diff --git a/app/Models/Patient.php b/app/Models/Patient.php index a8b174c..44a09c7 100644 --- a/app/Models/Patient.php +++ b/app/Models/Patient.php @@ -6,6 +6,7 @@ use App\Traits\InteractsWithUuid; use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\DB; class Patient extends Model { @@ -61,10 +62,17 @@ class Patient extends Model $prefix = 'MRN'; $year = Carbon::now()->format('Y'); $month = Carbon::now()->format('m'); - $sequence = self::whereYear('created_at', $year) - ->whereMonth('created_at', $month) - ->count() + 1; - return sprintf('%s-%s%s-%04d', $prefix, $year, $month, $sequence); + return DB::transaction(function() use ($prefix, $year, $month) { + $latestRecord = self::whereYear('created_at', $year) + ->whereMonth('created_at', $month) + ->orderBy('id', 'desc') + ->first(); + + $sequence = $latestRecord ? + (int)substr($latestRecord->medical_record_number, -4) + 1 : 1; + + return sprintf('%s-%s%s-%04d', $prefix, $year, $month, $sequence); + }); } } diff --git a/package-lock.json b/package-lock.json index df998c7..235a09b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "react": "^19.0.0", "react-day-picker": "^8.10.1", "react-dom": "^19.0.0", + "recharts": "^2.15.3", "sonner": "^2.0.3", "tailwind-merge": "^3.0.1", "tailwindcss": "^4.0.0", @@ -285,6 +286,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", @@ -3954,6 +3967,69 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -3971,7 +4047,7 @@ "version": "22.13.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz", "integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -4788,6 +4864,127 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -4869,6 +5066,12 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4961,6 +5164,16 @@ "node": ">=0.10.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5445,6 +5658,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5452,6 +5671,15 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -5966,6 +6194,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -6785,7 +7022,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -6930,7 +7166,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7322,7 +7557,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -7421,7 +7655,6 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/react-refresh": { @@ -7480,6 +7713,21 @@ } } }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", @@ -7502,6 +7750,60 @@ } } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/recharts": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.3.tgz", + "integrity": "sha512-EdOPzTwcFSuqtvkDoaM5ws/Km1+WTAO2eizL7rqiG0V2UVhTnz0m7J2i0CjVPUCdEkZImaWvXLbZDS2H5t6GFQ==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -7525,6 +7827,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -8128,6 +8436,12 @@ "node": ">=6" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", @@ -8361,7 +8675,7 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -8447,6 +8761,28 @@ } } }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", diff --git a/package.json b/package.json index 66acc8c..6c00209 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "react": "^19.0.0", "react-day-picker": "^8.10.1", "react-dom": "^19.0.0", + "recharts": "^2.15.3", "sonner": "^2.0.3", "tailwind-merge": "^3.0.1", "tailwindcss": "^4.0.0", diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index 257e0ef..3f73e26 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -65,16 +65,7 @@ const masterNavItems = [ ]; const footerNavItems: NavItem[] = [ - { - title: 'Repository', - href: 'https://github.com/laravel/react-starter-kit', - icon: Folder, - }, - { - title: 'Documentation', - href: 'https://laravel.com/docs/starter-kits', - icon: BookOpen, - }, + // ]; export function AppSidebar() { diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx index 083954a..b2ef3d9 100644 --- a/resources/js/pages/dashboard.tsx +++ b/resources/js/pages/dashboard.tsx @@ -1,7 +1,8 @@ -import { PlaceholderPattern } from '@/components/ui/placeholder-pattern'; +import React from 'react'; import AppLayout from '@/layouts/app-layout'; -import { type BreadcrumbItem } from '@/types'; import { Head } from '@inertiajs/react'; +import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { type BreadcrumbItem } from '@/types'; const breadcrumbs: BreadcrumbItem[] = [ { @@ -10,24 +11,85 @@ const breadcrumbs: BreadcrumbItem[] = [ }, ]; -export default function Dashboard() { +export default function Dashboard({ auth, patientRegistrations, dailyRevenue, todayStats }) { return (
+ {/* Today's Statistics */}
-
- +
+

Pasien Hari Ini

+

{todayStats.patient_count}

-
- +
+

Pendapatan Hari Ini

+

+ {new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR' + }).format(todayStats.revenue)} +

-
- +
+

Pembayaran Tertunda

+

{todayStats.pending_payments}

-
- + + {/* Charts */} +
+ {/* Patient Registration Chart */} +
+

+ Jumlah Pasien Mendaftar (7 Hari Terakhir) +

+
+ + + + + + + + + + +
+
+ + {/* Revenue Chart */} +
+

+ Pendapatan Harian (7 Hari Terakhir) +

+
+ + + + + + [ + new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR' + }).format(value), + 'Total Pendapatan' + ]} + /> + + + + +
+
diff --git a/routes/web.php b/routes/web.php index b1be637..569012b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,6 @@ group(function () { - Route::get('dashboard', function () { - return Inertia::render('dashboard'); - })->name('dashboard'); + Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); // employee route Route::get('/employees', [EmployeeController::class, 'index'])->name('employees.index');