Files
nrml_dashboard/e exec app composer dump-autoloaddocker compose exec app composer dump-autoload
2026-06-16 10:45:41 +07:00

598 lines
23 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
diff --git a/Dockerfile b/Dockerfile
index af83013..b506a23 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,6 +14,7 @@ RUN apt-get update && apt-get install -y \

# PHP Extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
+RUN chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache


# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
diff --git a/dashboard/bootstrap/app.php b/dashboard/bootstrap/app.php
index c3928c5..9f5e292 100644
--- a/dashboard/bootstrap/app.php
+++ b/dashboard/bootstrap/app.php
@@ -6,13 +6,15 @@

return Application::configure(basePath: dirname(__DIR__))
->withRouting(
- web: __DIR__.'/../routes/web.php',
- api: __DIR__.'/../routes/api.php',
- commands: __DIR__.'/../routes/console.php',
+ web: __DIR__ . '/../routes/web.php',
+ api: __DIR__ . '/../routes/api.php',
+ commands: __DIR__ . '/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
- //
+ $middleware->alias([
+ 'role' => \App\Http\Middleware\RoleMiddleware::class,
+ ]);
})
->withExceptions(function (Exceptions $exceptions): void {
//
diff --git a/dashboard/composer.json b/dashboard/composer.json
index 52a3a8a..aad37ad 100644
--- a/dashboard/composer.json
+++ b/dashboard/composer.json
@@ -12,6 +12,7 @@
},
"require-dev": {
"fakerphp/faker": "^1.23",
+ "laravel/breeze": "^2.4",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",
"laravel/sail": "^1.41",
diff --git a/dashboard/composer.lock b/dashboard/composer.lock
index 7ba63ad..23b4e2e 100644
--- a/dashboard/composer.lock
+++ b/dashboard/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "c514d8f7b9fc5970bdd94287905ef584",
+ "content-hash": "18c3a10710e6e4641721ddfbd649de8d",
"packages": [
{
"name": "brick/math",
@@ -6195,6 +6195,67 @@
},
"time": "2025-04-30T06:54:44+00:00"
},
+ {
+ "name": "laravel/breeze",
+ "version": "v2.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/breeze.git",
+ "reference": "28cefeaf6af20177ddf5cc7b93e87e4ad79d533f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/breeze/zipball/28cefeaf6af20177ddf5cc7b93e87e4ad79d533f",
+ "reference": "28cefeaf6af20177ddf5cc7b93e87e4ad79d533f",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/console": "^11.0|^12.0|^13.0",
+ "illuminate/filesystem": "^11.0|^12.0|^13.0",
+ "illuminate/support": "^11.0|^12.0|^13.0",
+ "illuminate/validation": "^11.0|^12.0|^13.0",
+ "php": "^8.2.0",
+ "symfony/console": "^7.0|^8.0"
+ },
+ "require-dev": {
+ "laravel/framework": "^11.0|^12.0|^13.0",
+ "orchestra/testbench-core": "^9.0|^10.0|^11.0",
+ "phpstan/phpstan": "^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Laravel\\Breeze\\BreezeServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Breeze\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "Minimal Laravel authentication scaffolding with Blade and Tailwind.",
+ "keywords": [
+ "auth",
+ "laravel"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/breeze/issues",
+ "source": "https://github.com/laravel/breeze"
+ },
+ "time": "2026-03-10T19:59:01+00:00"
+ },
{
"name": "laravel/pail",
"version": "v1.2.6",
diff --git a/dashboard/package.json b/dashboard/package.json
index 7686b29..2ea7e1d 100644
--- a/dashboard/package.json
+++ b/dashboard/package.json
@@ -7,11 +7,15 @@
"dev": "vite"
},
"devDependencies": {
+ "@tailwindcss/forms": "^0.5.2",
"@tailwindcss/vite": "^4.0.0",
+ "alpinejs": "^3.4.2",
+ "autoprefixer": "^10.4.2",
"axios": "^1.11.0",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0.0",
- "tailwindcss": "^4.0.0",
+ "postcss": "^8.4.31",
+ "tailwindcss": "^3.1.0",
"vite": "^7.0.7"
}
}
diff --git a/dashboard/resources/css/app.css b/dashboard/resources/css/app.css
index 3e6abea..b5c61c9 100644
--- a/dashboard/resources/css/app.css
+++ b/dashboard/resources/css/app.css
@@ -1,11 +1,3 @@
-@import 'tailwindcss';
-
-@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
-@source '../../storage/framework/views/*.php';
-@source '../**/*.blade.php';
-@source '../**/*.js';
-
-@theme {
- --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
- 'Segoe UI Symbol', 'Noto Color Emoji';
-}
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/dashboard/resources/js/app.js b/dashboard/resources/js/app.js
index e59d6a0..a8093be 100644
--- a/dashboard/resources/js/app.js
+++ b/dashboard/resources/js/app.js
@@ -1 +1,7 @@
import './bootstrap';
+
+import Alpine from 'alpinejs';
+
+window.Alpine = Alpine;
+
+Alpine.start();
diff --git a/dashboard/resources/views/layouts/app.blade.php b/dashboard/resources/views/layouts/app.blade.php
index e086ead..0a471a4 100644
--- a/dashboard/resources/views/layouts/app.blade.php
+++ b/dashboard/resources/views/layouts/app.blade.php
@@ -1,305 +1,36 @@
<!DOCTYPE html>
-<html>
-
-<head>
- <title>NRML Dashboard</title>
- <meta name="viewport" content="width=device-width, initial-scale=1">
-
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
- <script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1"></script>
- <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.2.0"></script>
- <script src="https://cdn.jsdelivr.net/npm/html-to-image@1.11.11/dist/html-to-image.min.js"></script>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
- <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
-
- <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
- <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
-
- <script src="/js/dashboard/filter.js"></script>
- <script src="/js/dashboard/charts.js"></script>
- <script src="/js/dashboard/export.js"></script>
-
- <style>
- body {
- margin: 0;
- }
-
- .top-navbar {
- height: 60px;
- background: #0B8F3C;
- color: white;
- display: flex;
- align-items: center;
- padding: 0 25px;
- }
-
- .brand-title {
- font-weight: 600;
- font-size: 18px;
- color: #f8f9fa;
- }
-
- .nav-bar {
- display: flex;
- background: white;
- border-bottom: 1px solid #dcdcdc;
- padding: 0 20px;
- position: sticky;
- top: 0;
- z-index: 1000;
- }
- .btn{
- border-radius: 0 !important;
- }
-
- .btn-theme-outline {
- background-color: #fff;
- color: #0B8F3C;
- border: 1px solid #0B8F3C;
- }
-
- .btn-theme-outline:hover {
- background-color: #cce0d4;
- }
-
- .nav-item {
- padding: 12px 18px;
- text-decoration: none;
- color: #262626;
- font-weight: 500;
- border-bottom: 3px solid transparent;
- font-size: 14px;
- }
-
- .nav-item:hover {
- background: #cce0d4;
- }
-
- .active-tab {
- color: #0B8F3C;
- border-bottom: 3px solid #0B8F3C;
- background: #e5efe8;
- }
-
- .content-area {
- padding: 20px;
- background: #f8f9fa;
- min-height: calc(100vh - 110px);
- }
-
- .card {
- border-radius: 0px !important;
- border: 1px solid #E5E7EB;
- }
-
- .form-select {
- border-radius: 0px !important;
- }
-
- .shadow-sm {
- box-shadow: none !important;
- }
-
- .card h3 {
- color: #0B8F3C;
- }
-
-
- .export-control {
- position: relative;
- }
-
- #exportItems {
- display: flex;
- gap: 8px;
- opacity: 0;
- transform: translateX(-10px);
- pointer-events: none;
- width: 0;
- overflow: hidden;
- transition: all 0.2s ease;
- }
-
- #exportItems.show {
- opacity: 1;
- transform: translateX(0);
- pointer-events: auto;
- width: auto;
- }
-
- .export-modal {
- display: none;
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.4);
- z-index: 10000;
- }
-
- .export-content {
- background: white;
- padding: 20px;
- width: 400px;
- margin: 10% auto;
- border-radius: 10px;
- }
-
- /* SLIDE FEATURE (from master) */
- .slide-wrapper {
- position: relative;
- overflow: hidden;
- height: 100%;
- min-height: 400px;
- }
-
- .slide {
- position: absolute;
- width: 100%;
- top: 0;
- left: 100%;
- opacity: 0;
- transition: all 0.5s ease-in-out;
- }
-
- .slide.active {
- left: 0;
- opacity: 1;
- z-index: 2;
- }
-
- .slide.prev {
- left: -100%;
- opacity: 0;
- }
-
- .slide-btn {
- position: absolute;
- top: 10%;
- transform: translateY(-50%);
- background: rgba(0, 128, 0, 0.43);
- color: white;
- border: none;
- padding: 8px 15px;
- cursor: pointer;
- z-index: 10;
- }
-
- .prev-btn {
- right: 75px;
- }
-
- .next-btn {
- right: 25px;
- }
-
- .slide-btn:hover {
- background: rgba(7, 120, 24, 0.8);
- }
-
- @media print {
- #floatingExport {
- display: none !important;
- }
-
- .nav-bar,
- .top-navbar {
- display: none !important;
- }
-
- .card {
- page-break-inside: avoid;
- }
- }
- </style>
-</head>
-
-<body>
-
- <div class="top-navbar">
- <div class="brand-title">
- National Reference Medical Laboratory Surveillance Dashboard
- </div>
- <div class="ms-auto small">
- Last update: 12:05 | 2026-03-15
- </div>
- </div>
-
- <div class="nav-bar">
-
- <a href="/dashboard" class="nav-item {{ request()->is('dashboard') ? 'active-tab' : '' }}">
- Overview
- </a>
-
- @foreach($programs as $program)
- @if($program->code === 'SEQ')
- <a href="/dashboard/seq" class="nav-item {{ request()->is('dashboard/seq') ? 'active-tab' : '' }}">
- SEQ
- </a>
- @else
- <a href="/dashboard/{{ strtolower($program->code) }}"
- class="nav-item {{ request()->is('dashboard/' . strtolower($program->code)) ? 'active-tab' : '' }}">
- {{ $program->code }}
- </a>
- @endif
- @endforeach
-
- <div class="ms-auto d-flex align-items-center gap-2 pe-3">
-
- <button onclick="reloadDataSource()" class="btn btn-sm btn-theme-outline">
- Refresh Data
- </button>
-
- <div id="exportControl" class="d-flex align-items-center gap-2">
-
- <button id="exportToggle" class="btn btn-sm btn-theme-outline">
- Export ▸
- </button>
-
- <div id="exportItems" class="align-items-center gap-2">
- <button class="btn btn-sm btn-light" onclick="openChartSelector()">Charts</button>
- <button class="btn btn-sm btn-light" onclick="exportFullDashboard()">Screen</button>
- <button class="btn btn-sm btn-light" onclick="window.print()">Print</button>
- <button class="btn btn-sm btn-outline-secondary" id="exportClose">✕</button>
- </div>
-
- <div id="chartModal" class="export-modal">
- <div class="export-content">
- <h5>Select Charts</h5>
- <div id="chartList"></div>
-
- <div class="mt-3 d-flex justify-content-end gap-2">
- <button onclick="closeChartSelector()">Cancel</button>
- <button onclick="exportSelectedCharts()">Download PDF</button>
- </div>
+<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="csrf-token" content="{{ csrf_token() }}">
+
+ <title>{{ config('app.name', 'Laravel') }}</title>
+
+ <!-- Fonts -->
+ <link rel="preconnect" href="https://fonts.bunny.net">
+ <link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
+
+ <!-- Scripts -->
+ @vite(['resources/css/app.css', 'resources/js/app.js'])
+ </head>
+ <body class="font-sans antialiased">
+ <div class="min-h-screen bg-gray-100 dark:bg-gray-900">
+ @include('layouts.navigation')
+
+ <!-- Page Heading -->
+ @isset($header)
+ <header class="bg-white dark:bg-gray-800 shadow">
+ <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
+ {{ $header }}
</div>
- </div>
-
- </div>
-
- </div>
-
- </div>
+ </header>
+ @endisset

- <div class="main-wrapper">
- <div class="content-area">
- @yield('content')
+ <!-- Page Content -->
+ <main>
+ {{ $slot }}
+ </main>
</div>
- </div>
-
- @yield('scripts')
-
- <script>
- window.addEventListener("click", (e) => {
- const modal = document.getElementById("chartModal");
- if (e.target === modal) modal.style.display = "none";
- });
-
- function reloadDataSource() {
- fetch(`/api/dashboard/reload`)
- .then(res => res.json())
- .then(() => location.reload());
- }
- </script>
-
-</body>
-
+ </body>
</html>
diff --git a/dashboard/routes/web.php b/dashboard/routes/web.php
index 4bcbea0..69ac7ac 100644
--- a/dashboard/routes/web.php
+++ b/dashboard/routes/web.php
@@ -1,17 +1,40 @@
<?php

-
-use Illuminate\Support\Facades\Route;
+use App\Http\Controllers\ProfileController;
use App\Http\Controllers\DashboardController;
+use Illuminate\Support\Facades\Route;

Route::get('/', function () {
- return view('welcome');
+
+ return auth()->check()
+ ? redirect()->route('dashboard')
+ : redirect()->route('login');
+
});

+Route::get('/dashboard', function () {
+ return view('dashboard');
+})->middleware(['auth', 'verified'])->name('dashboard');
+
+Route::middleware('auth')->group(function () {
+ Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
+ Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
+ Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
+ Route::get('/dashboard', [DashboardController::class, 'overview'])
+ ->name('dashboard');
+ Route::get('/dashboard/{code}', [DashboardController::class, 'detail'])
+ ->name('dashboard.detail');
+});
+Route::middleware(['auth', 'role:admin'])->group(function () {
+
+ Route::get('/admin/users', function () {
+ return view('admin.users');
+ });
+
+ Route::get('/admin/settings', function () {
+ return view('admin.settings');
+ });

-Route::get('/dashboard/seq', function () {
- return view('dashboard.sequencing');
});
-Route::get('/dashboard', [DashboardController::class, 'overview']);
-Route::get('/dashboard/{code}', [DashboardController::class, 'detail']);

+require __DIR__ . '/auth.php';
diff --git a/dashboard/vite.config.js b/dashboard/vite.config.js
index f35b4e7..421b569 100644
--- a/dashboard/vite.config.js
+++ b/dashboard/vite.config.js
@@ -1,6 +1,5 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
-import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
plugins: [
@@ -8,11 +7,5 @@ export default defineConfig({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
- tailwindcss(),
],
- server: {
- watch: {
- ignored: ['**/storage/framework/views/**'],
- },
- },
});