Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
@@ -0,0 +1,6 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
@@ -0,0 +1,333 @@
:root {
/* Apple + Stripe Color Palette (Light) */
--auth-bg-gradient-1: #fdfbfb;
--auth-bg-gradient-2: #ebedee;
--auth-bg-gradient-3: #e0c3fc;
--auth-bg-gradient-4: #8ec5fc;
--auth-glass-bg: rgba(255, 255, 255, 0.7);
--auth-glass-border: rgba(255, 255, 255, 0.5);
--auth-glass-shadow: rgba(0, 0, 0, 0.05);
--auth-text-main: #1d1d1f;
--auth-text-muted: #86868b;
--auth-input-bg: rgba(255, 255, 255, 0.8);
--auth-input-border: #d2d2d7;
--auth-input-focus: #0066cc;
--auth-input-focus-shadow: rgba(0, 102, 204, 0.2);
--auth-btn-bg: #0066cc;
--auth-btn-text: #ffffff;
--auth-btn-hover: #0077ed;
--auth-shape-1: rgba(142, 197, 252, 0.6);
--auth-shape-2: rgba(224, 195, 252, 0.6);
--auth-shape-3: rgba(255, 209, 255, 0.6);
}
[data-bs-theme="dark"] {
/* Apple + Stripe Color Palette (Dark) */
--auth-bg-gradient-1: #000000;
--auth-bg-gradient-2: #1c1c1e;
--auth-bg-gradient-3: #2c1b4d;
--auth-bg-gradient-4: #1a2a42;
--auth-glass-bg: rgba(28, 28, 30, 0.65);
--auth-glass-border: rgba(255, 255, 255, 0.1);
--auth-glass-shadow: rgba(0, 0, 0, 0.3);
--auth-text-main: #f5f5f7;
--auth-text-muted: #86868b;
--auth-input-bg: rgba(44, 44, 46, 0.8);
--auth-input-border: #3a3a3c;
--auth-input-focus: #2997ff;
--auth-input-focus-shadow: rgba(41, 151, 255, 0.25);
--auth-btn-bg: #2997ff;
--auth-btn-text: #ffffff;
--auth-btn-hover: #0071e3;
--auth-shape-1: rgba(26, 42, 66, 0.8);
--auth-shape-2: rgba(44, 27, 77, 0.8);
--auth-shape-3: rgba(66, 42, 66, 0.8);
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(-45deg, var(--auth-bg-gradient-1), var(--auth-bg-gradient-2), var(--auth-bg-gradient-3), var(--auth-bg-gradient-4));
background-size: 400% 400%;
animation: gradientBG 15s ease infinite;
color: var(--auth-text-main);
overflow: hidden;
position: relative;
transition: background-color 0.4s ease, color 0.4s ease;
}
/* Animated Gradient Background */
@keyframes gradientBG {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Canvas Particle Layer */
#particleCanvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
/* Floating Shapes */
.bg-shape {
position: absolute;
filter: blur(60px);
border-radius: 50%;
z-index: 1;
animation: float 20s infinite alternate;
}
.shape-1 {
width: 400px;
height: 400px;
background: var(--auth-shape-1);
top: -100px;
left: -100px;
animation-delay: 0s;
}
.shape-2 {
width: 500px;
height: 500px;
background: var(--auth-shape-2);
bottom: -150px;
right: -100px;
animation-delay: -5s;
}
.shape-3 {
width: 300px;
height: 300px;
background: var(--auth-shape-3);
top: 40%;
left: 50%;
animation-delay: -10s;
}
@keyframes float {
0% { transform: translate(0, 0) rotate(0deg) scale(1); }
33% { transform: translate(30px, -50px) rotate(10deg) scale(1.1); }
66% { transform: translate(-20px, 20px) rotate(-5deg) scale(0.9); }
100% { transform: translate(0, 0) rotate(0deg) scale(1); }
}
/* Wrapper */
.auth-wrapper {
position: relative;
z-index: 10;
width: 100%;
max-width: 1000px;
padding-inline: 20px;
}
/* Glass Card */
.glass-card {
background: var(--auth-glass-bg);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border: 1px solid var(--auth-glass-border);
border-radius: 24px;
box-shadow: 0 8px 32px var(--auth-glass-shadow), 0 1px 3px rgba(0,0,0,0.02);
overflow: hidden;
display: flex;
flex-direction: row;
min-height: 550px;
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.glass-card:hover {
box-shadow: 0 12px 48px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.02);
transform: translateY(-2px);
}
/* Two-column layout */
.auth-illustration {
flex: 1;
background: rgba(255, 255, 255, 0.03);
border-inline-end: 1px solid var(--auth-glass-border);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 3rem;
text-align: center;
}
.auth-form-container {
flex: 1;
padding: 3rem;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
}
/* Typography */
.auth-title {
font-weight: 700;
font-size: 1.75rem;
margin-bottom: 0.5rem;
letter-spacing: -0.02em;
color: var(--auth-text-main);
}
.auth-subtitle {
color: var(--auth-text-muted);
font-size: 0.95rem;
margin-bottom: 2rem;
}
/* Microsoft Fluent / Stripe Inputs */
.form-floating > .form-control,
.form-floating > .form-control:focus {
background-color: var(--auth-input-bg);
border: 1.5px solid var(--auth-input-border);
color: var(--auth-text-main);
border-radius: 12px;
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.form-floating > .form-control:focus {
border-color: var(--auth-input-focus);
box-shadow: 0 0 0 4px var(--auth-input-focus-shadow);
}
.form-floating > label {
color: var(--auth-text-muted);
}
.form-control:focus ~ label {
color: var(--auth-input-focus);
}
/* Micro-interaction Button */
.btn-auth {
background-color: var(--auth-btn-bg);
color: var(--auth-btn-text);
border: none;
border-radius: 12px;
padding: 14px 20px;
font-weight: 600;
font-size: 1rem;
letter-spacing: 0.01em;
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
position: relative;
overflow: hidden;
}
.btn-auth:hover {
background-color: var(--auth-btn-hover);
transform: scale(1.02);
box-shadow: 0 4px 12px var(--auth-input-focus-shadow);
}
.btn-auth:active {
transform: scale(0.98);
}
/* Loading Overlay */
.auth-loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--auth-glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 24px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.auth-loading-overlay.active {
opacity: 1;
pointer-events: all;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid var(--auth-input-border);
border-top-color: var(--auth-btn-bg);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Theme Switcher & Lang Toggle */
.auth-controls {
position: absolute;
top: 1.5rem;
inset-inline-end: 1.5rem;
display: flex;
gap: 0.5rem;
z-index: 20;
}
.control-btn {
background: var(--auth-glass-bg);
border: 1px solid var(--auth-glass-border);
color: var(--auth-text-muted);
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.control-btn:hover {
color: var(--auth-text-main);
background: var(--auth-input-bg);
transform: rotate(15deg) scale(1.1);
}
/* Responsive */
@media (max-width: 768px) {
.glass-card {
flex-direction: column;
}
.auth-illustration {
border-inline-end: none;
border-bottom: 1px solid var(--auth-glass-border);
padding: 2rem;
flex: none;
}
.auth-form-container {
padding: 2rem;
}
}
@@ -0,0 +1,161 @@
/* Premium 2026 Enterprise Theme Overrides */
/* Define variables specifically for the premium theme */
body[data-premium-theme="true"] {
/* Apple + Stripe Color Palette (Light) */
--premium-bg-gradient-1: #fdfbfb;
--premium-bg-gradient-2: #ebedee;
--premium-bg-gradient-3: #e0c3fc;
--premium-bg-gradient-4: #8ec5fc;
--premium-glass-bg: rgba(255, 255, 255, 0.7);
--premium-glass-border: rgba(255, 255, 255, 0.5);
--premium-glass-shadow: rgba(0, 0, 0, 0.05);
--premium-text-main: #1d1d1f;
--premium-text-muted: #86868b;
--premium-input-bg: rgba(255, 255, 255, 0.8);
--premium-input-border: #d2d2d7;
--premium-input-focus: #0066cc;
--premium-input-focus-shadow: rgba(0, 102, 204, 0.2);
--premium-btn-bg: #0066cc;
--premium-btn-text: #ffffff;
--premium-btn-hover: #0077ed;
/* Font family override */
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
color: var(--premium-text-main);
/* Animated Gradient Background */
background: linear-gradient(-45deg, var(--premium-bg-gradient-1), var(--premium-bg-gradient-2), var(--premium-bg-gradient-3), var(--premium-bg-gradient-4));
background-size: 400% 400%;
animation: premiumGradientBG 15s ease infinite;
min-height: 100vh;
transition: background-color 0.4s ease, color 0.4s ease;
}
@keyframes premiumGradientBG {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Base resets when premium is active */
body[data-premium-theme="true"] .bg-primary,
body[data-premium-theme="true"] .bg-white,
body[data-premium-theme="true"] .bg-body-tertiary {
background-color: transparent !important;
}
/* Navbar Upgrade */
body[data-premium-theme="true"] .navbar {
background: rgba(255, 255, 255, 0.6) !important;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid var(--premium-glass-border);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.03) !important;
}
body[data-premium-theme="true"] .navbar-dark .navbar-brand,
body[data-premium-theme="true"] .navbar-dark .nav-link,
body[data-premium-theme="true"] .navbar-dark .navbar-toggler-icon {
color: var(--premium-text-main) !important;
}
body[data-premium-theme="true"] .navbar-dark .nav-link:hover {
color: var(--premium-btn-hover) !important;
}
body[data-premium-theme="true"] .navbar-dark .navbar-toggler {
border-color: rgba(0,0,0,0.1);
}
body[data-premium-theme="true"] .navbar-dark .navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") !important;
}
/* Card Upgrades (Glassmorphism) */
body[data-premium-theme="true"] .card {
background: var(--premium-glass-bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid var(--premium-glass-border) !important;
border-radius: 20px !important;
box-shadow: 0 8px 32px var(--premium-glass-shadow) !important;
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
body[data-premium-theme="true"] .card:hover {
transform: translateY(-2px);
box-shadow: 0 12px 40px rgba(0,0,0,0.08) !important;
}
body[data-premium-theme="true"] .card-header,
body[data-premium-theme="true"] .card-footer {
background: transparent !important;
border-color: var(--premium-glass-border) !important;
}
/* Forms & Inputs Upgrade */
body[data-premium-theme="true"] .form-control,
body[data-premium-theme="true"] .form-select {
background-color: var(--premium-input-bg);
border: 1px solid var(--premium-input-border);
border-radius: 10px;
color: var(--premium-text-main);
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
body[data-premium-theme="true"] .form-control:focus,
body[data-premium-theme="true"] .form-select:focus {
border-color: var(--premium-input-focus);
box-shadow: 0 0 0 3px var(--premium-input-focus-shadow);
background-color: #ffffff;
}
/* Buttons Upgrade */
body[data-premium-theme="true"] .btn {
border-radius: 10px;
font-weight: 500;
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
body[data-premium-theme="true"] .btn-primary {
background-color: var(--premium-btn-bg);
border-color: var(--premium-btn-bg);
color: var(--premium-btn-text);
}
body[data-premium-theme="true"] .btn-primary:hover,
body[data-premium-theme="true"] .btn-primary:focus {
background-color: var(--premium-btn-hover);
border-color: var(--premium-btn-hover);
box-shadow: 0 4px 12px var(--premium-input-focus-shadow);
transform: scale(1.02);
}
/* Footer Upgrade */
body[data-premium-theme="true"] .footer {
background-color: transparent;
border-top: none;
color: var(--premium-text-muted);
}
/* Canvas Particle Background container */
#premiumParticleCanvas {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: -1;
pointer-events: none;
opacity: 0;
transition: opacity 1s ease;
}
body[data-premium-theme="true"] #premiumParticleCanvas {
opacity: 1;
}
@@ -0,0 +1,136 @@
document.addEventListener('DOMContentLoaded', () => {
initThemeManager();
initParticleSystem();
initFormInteractions();
});
// --- Theme Manager ---
function initThemeManager() {
const htmlElement = document.documentElement;
const themeBtn = document.getElementById('themeToggleBtn');
const themeIcon = document.getElementById('themeIcon');
// Check local storage or system preference
const savedTheme = localStorage.getItem('auth_theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
let currentTheme = savedTheme || (prefersDark ? 'dark' : 'light');
applyTheme(currentTheme);
if (themeBtn) {
themeBtn.addEventListener('click', () => {
currentTheme = currentTheme === 'light' ? 'dark' : 'light';
applyTheme(currentTheme);
});
}
function applyTheme(theme) {
htmlElement.setAttribute('data-bs-theme', theme);
localStorage.setItem('auth_theme', theme);
if (themeIcon) {
themeIcon.className = theme === 'light' ? 'fa-regular fa-moon' : 'fa-regular fa-sun';
}
}
}
// --- Particle System (Lightweight Canvas) ---
function initParticleSystem() {
const canvas = document.getElementById('particleCanvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
let width, height, particles;
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
class Particle {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.size = Math.random() * 2 + 0.5;
this.speedX = (Math.random() - 0.5) * 0.5;
this.speedY = (Math.random() - 0.5) * 0.5;
this.opacity = Math.random() * 0.5 + 0.1;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
if (this.x < 0 || this.x > width || this.y < 0 || this.y > height) {
this.reset();
}
}
draw() {
// Read current theme to adjust particle color slightly
const theme = document.documentElement.getAttribute('data-bs-theme');
const color = theme === 'dark' ? `rgba(255, 255, 255, ${this.opacity})` : `rgba(0, 0, 0, ${this.opacity * 0.5})`;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
}
function init() {
resize();
particles = [];
const particleCount = Math.min(window.innerWidth / 15, 100); // Responsive count
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
}
function animate() {
ctx.clearRect(0, 0, width, height);
particles.forEach(p => {
p.update();
p.draw();
});
requestAnimationFrame(animate);
}
window.addEventListener('resize', () => {
resize();
init();
});
init();
animate();
}
// --- Form Interactions & Loading Overlay ---
function initFormInteractions() {
const loginForm = document.getElementById('loginForm');
const loadingOverlay = document.getElementById('loadingOverlay');
const submitBtn = document.getElementById('submitBtn');
if (loginForm && loadingOverlay) {
loginForm.addEventListener('submit', (e) => {
// Prevent multiple submissions
if (loginForm.classList.contains('is-submitting')) {
e.preventDefault();
return;
}
loginForm.classList.add('is-submitting');
loadingOverlay.classList.add('active');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> กำลังเข้าสู่ระบบ...';
}
// Allow form to submit normally after showing UI
});
}
}
@@ -0,0 +1,108 @@
document.addEventListener('DOMContentLoaded', () => {
// Inject Canvas for background if it doesn't exist
if (!document.getElementById('premiumParticleCanvas')) {
const canvas = document.createElement('canvas');
canvas.id = 'premiumParticleCanvas';
document.body.prepend(canvas);
// Initialize particle system if premium theme is active
if (document.body.getAttribute('data-premium-theme') === 'true') {
initPremiumParticleSystem();
}
}
});
// Global function to toggle theme, callable from Settings page
window.togglePremiumTheme = function(enable) {
const isEnabled = enable === true;
localStorage.setItem('app_premium_theme', isEnabled ? 'true' : 'false');
document.body.setAttribute('data-premium-theme', isEnabled ? 'true' : 'false');
if (isEnabled) {
initPremiumParticleSystem();
}
};
// --- Particle System (Lightweight Canvas) ---
let premiumParticlesInitialized = false;
function initPremiumParticleSystem() {
if (premiumParticlesInitialized) return;
premiumParticlesInitialized = true;
const canvas = document.getElementById('premiumParticleCanvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
let width, height, particles;
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
class Particle {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.size = Math.random() * 2 + 0.5;
this.speedX = (Math.random() - 0.5) * 0.5;
this.speedY = (Math.random() - 0.5) * 0.5;
this.opacity = Math.random() * 0.4 + 0.1;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
if (this.x < 0 || this.x > width || this.y < 0 || this.y > height) {
this.reset();
}
}
draw() {
// Check if theme is still active before drawing
if (document.body.getAttribute('data-premium-theme') !== 'true') return;
// Simple white/grey particles for the bright premium theme
ctx.fillStyle = `rgba(0, 0, 0, ${this.opacity * 0.4})`;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
}
function init() {
resize();
particles = [];
const particleCount = Math.min(window.innerWidth / 15, 80);
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
}
function animate() {
if (document.body.getAttribute('data-premium-theme') === 'true') {
ctx.clearRect(0, 0, width, height);
particles.forEach(p => {
p.update();
p.draw();
});
}
requestAnimationFrame(animate);
}
window.addEventListener('resize', () => {
if (document.body.getAttribute('data-premium-theme') === 'true') {
resize();
init();
}
});
init();
animate();
}
@@ -0,0 +1,139 @@
<?php
session_start();
$scriptName = $_SERVER['SCRIPT_NAME'];
$baseDir = dirname(dirname($scriptName));
if ($baseDir === '/' || $baseDir === '\\') $baseDir = '';
define('BASE_URL', $baseDir);
// Simple PSR-4 Autoloader
spl_autoload_register(function ($class) {
// Project-specific namespace prefix
$prefix = 'App\\';
// Base directory for the namespace prefix
$base_dir = __DIR__ . '/../src/';
// Does the class use the namespace prefix?
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
// No, move to the next registered autoloader
return;
}
// Get the relative class name
$relative_class = substr($class, $len);
// Replace the namespace prefix with the base directory, replace namespace
// separators with directory separators in the relative class name, append
// with .php
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
// If the file exists, require it
if (file_exists($file)) {
require $file;
}
});
// Setup Global Error & Exception Handling
set_error_handler(function($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
return;
}
throw new \ErrorException($message, 0, $severity, $file, $line);
});
set_exception_handler(function($exception) {
error_log($exception->getMessage() . " in " . $exception->getFile() . " on line " . $exception->getLine());
$isAjax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
if ($isAjax || (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)) {
http_response_code(500);
header('Content-Type: application/json');
echo json_encode([
'status' => 'error',
'message' => 'เกิดข้อผิดพลาดในระบบ โปรดติดต่อผู้ดูแลระบบ'
]);
exit;
}
http_response_code(500);
$error_message = 'เกิดข้อผิดพลาด: ' . $exception->getMessage() . ' ในไฟล์ ' . $exception->getFile() . ' บรรทัดที่ ' . $exception->getLine();
// Check if error view exists
$errorView = __DIR__ . '/../views/error.php';
if (file_exists($errorView)) {
require $errorView;
} else {
echo "<h1>500 Internal Server Error</h1><p>{$error_message}</p>";
}
exit;
});
// Load Router
use App\Core\Router;
$router = new Router();
// Routes definition
$router->add('GET', '/', 'DashboardController', 'index');
$router->add('GET', '/login', 'AuthController', 'login');
$router->add('POST', '/login', 'AuthController', 'authenticate');
$router->add('GET', '/logout', 'AuthController', 'logout');
$router->add('GET', '/trip/create', 'TripController', 'create');
$router->add('POST', '/trip/create', 'TripController', 'store');
$router->add('GET', '/trip/search-docs', 'TripController', 'searchDocs');
$router->add('GET', '/trip/edit', 'TripController', 'edit');
$router->add('POST', '/trip/edit', 'TripController', 'update');
$router->add('POST', '/trip/delete', 'TripController', 'delete');
$router->add('POST', '/trip/request-cancel', 'TripController', 'requestCancel');
$router->add('POST', '/trip/request-revise', 'TripController', 'requestRevise');
$router->add('GET', '/trip/approvals', 'ApprovalController', 'index');
$router->add('POST', '/trip/approvals/process', 'ApprovalController', 'process');
$router->add('GET', '/report', 'ReportController', 'index');
$router->add('GET', '/report/export', 'ReportController', 'export');
$router->add('GET', '/settings', 'SettingsController', 'index');
$router->add('GET', '/settings/google-sheets', 'SettingsController', 'googleSheets');
$router->add('POST', '/settings/google-sheets/save', 'SettingsController', 'googleSheetsSave');
$router->add('GET', '/settings/google-sheets/sync', 'SettingsController', 'googleSheetsSync');
$router->add('POST', '/settings/google-sheets/test', 'SettingsController', 'googleSheetsTest');
$router->add('GET', '/settings/hierarchy', 'SettingsController', 'approvalHierarchy');
$router->add('GET', '/settings/search-user', 'SettingsController', 'searchUser');
$router->add('POST', '/settings/hierarchy/add', 'SettingsController', 'addHierarchy');
$router->add('POST', '/settings/hierarchy/remove', 'SettingsController', 'removeHierarchy');
$router->add('GET', '/settings/line-notify', 'SettingsController', 'lineNotify');
$router->add('POST', '/settings/line-notify/save', 'SettingsController', 'saveLineNotify');
$router->add('POST', '/settings/line-notify/test', 'SettingsController', 'lineNotifyTest');
$router->add('GET', '/settings/telegram-notify', 'SettingsController', 'telegramNotify');
$router->add('POST', '/settings/telegram-notify/save', 'SettingsController', 'saveTelegramNotify');
$router->add('POST', '/settings/telegram-notify/test', 'SettingsController', 'telegramNotifyTest');
$router->add('GET', '/settings/pdf-designer', 'SettingsController', 'pdfDesigner');
$router->add('GET', '/settings/pdf-designer/preview', 'SettingsController', 'previewPdf');
$router->add('POST', '/settings/pdf-designer/upload', 'SettingsController', 'uploadPdfTemplate');
$router->add('POST', '/settings/pdf-designer/save', 'SettingsController', 'savePdfLayout');
$router->add('POST', '/settings/pdf-designer/delete', 'SettingsController', 'deletePdfTemplate');
$router->add('GET', '/trip/print', 'TripController', 'printPdf');
// Roles
$router->add('GET', '/roles', 'RoleController', 'index');
$router->add('GET', '/roles/search', 'RoleController', 'search');
$router->add('POST', '/roles/assign', 'RoleController', 'assign');
$router->add('POST', '/roles/remove', 'RoleController', 'remove');
$router->add('GET', '/reports', 'ReportController', 'index');
$router->add('GET', '/reports/export', 'ReportController', 'export');
$router->add('GET', '/audit', 'AuditController', 'index');
$router->add('GET', '/manual', 'ManualController', 'index');
$router->dispatch($_SERVER['REQUEST_URI'], $_SERVER['REQUEST_METHOD']);