Files
nrml_dashboard/e exec app composer dump-autoloaddocker compose exec app composer dump-autoload

23 KiB

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/**'],
- },
- },
});