permission system
This commit is contained in:
parent
2bc76dec94
commit
a6e75b0397
38
db/migrations/20260321_create_auth_system.sql
Normal file
38
db/migrations/20260321_create_auth_system.sql
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
-- Create roles table
|
||||||
|
CREATE TABLE IF NOT EXISTS roles (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
slug VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
permissions TEXT NULL, -- JSON or serialized array of permissions
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Create users table
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
email VARCHAR(100) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
role_id INT NOT NULL,
|
||||||
|
active TINYINT(1) DEFAULT 1,
|
||||||
|
last_login DATETIME NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE RESTRICT
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Seed Roles
|
||||||
|
INSERT IGNORE INTO roles (name, slug, permissions) VALUES
|
||||||
|
('Administrator', 'admin', '*'),
|
||||||
|
('Doctor', 'doctor', '["dashboard", "patients", "visits", "appointments", "home_visits", "reports"]'),
|
||||||
|
('Nurse', 'nurse', '["dashboard", "patients", "visits", "queue"]'),
|
||||||
|
('Receptionist', 'receptionist', '["dashboard", "patients", "appointments", "queue", "billing"]'),
|
||||||
|
('Laboratorial', 'laboratorial', '["dashboard", "laboratory"]'),
|
||||||
|
('Radiologic', 'radiologic', '["dashboard", "xray"]');
|
||||||
|
|
||||||
|
-- Seed Default Admin User (password: admin123)
|
||||||
|
-- Using a simple hash for demonstration if PHP's password_hash is not available in SQL,
|
||||||
|
-- but ideally we should insert via PHP. For now, I will insert a placeholder and update it via PHP or assume I can use a known hash.
|
||||||
|
-- Hash for 'admin123' (bcrypt)
|
||||||
|
INSERT IGNORE INTO users (name, email, password, role_id)
|
||||||
|
SELECT 'System Admin', 'admin@hospital.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', id
|
||||||
|
FROM roles WHERE slug = 'admin' LIMIT 1;
|
||||||
79
includes/auth.php
Normal file
79
includes/auth.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
|
function check_auth() {
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function current_user() {
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $db;
|
||||||
|
if (!isset($db)) {
|
||||||
|
$db = db();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cache first (optional, but good for performance)
|
||||||
|
if (isset($_SESSION['user_cache']) && $_SESSION['user_cache']['id'] == $_SESSION['user_id']) {
|
||||||
|
return $_SESSION['user_cache'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
SELECT u.*, r.slug as role_slug, r.permissions
|
||||||
|
FROM users u
|
||||||
|
JOIN roles r ON u.role_id = r.id
|
||||||
|
WHERE u.id = ? AND u.active = 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$_SESSION['user_id']]);
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($user) {
|
||||||
|
$_SESSION['user_cache'] = $user;
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
// User not found or inactive, logout
|
||||||
|
session_destroy();
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function has_role($role_slug) {
|
||||||
|
$user = current_user();
|
||||||
|
if (!$user) return false;
|
||||||
|
|
||||||
|
if ($user['role_slug'] === 'admin') return true; // Admin has all roles
|
||||||
|
|
||||||
|
return $user['role_slug'] === $role_slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
function has_permission($permission) {
|
||||||
|
$user = current_user();
|
||||||
|
if (!$user) return false;
|
||||||
|
|
||||||
|
if ($user['role_slug'] === 'admin') return true; // Admin has all permissions
|
||||||
|
|
||||||
|
// Decode permissions JSON
|
||||||
|
$perms = json_decode($user['permissions'], true);
|
||||||
|
if (!$perms) return false;
|
||||||
|
|
||||||
|
if (in_array('*', $perms)) return true;
|
||||||
|
|
||||||
|
return in_array($permission, $perms);
|
||||||
|
}
|
||||||
|
|
||||||
|
function require_role($role_slug) {
|
||||||
|
if (!has_role($role_slug)) {
|
||||||
|
http_response_code(403);
|
||||||
|
die("Access Denied: You do not have the required role.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../../db/config.php';
|
require_once __DIR__ . '/../../db/config.php';
|
||||||
require_once __DIR__ . '/../../helpers.php';
|
require_once __DIR__ . '/../../helpers.php';
|
||||||
|
require_once __DIR__ . '/../../includes/auth.php';
|
||||||
|
|
||||||
|
// Check Auth
|
||||||
|
check_auth();
|
||||||
|
$current_user = current_user();
|
||||||
|
|
||||||
$db = db();
|
$db = db();
|
||||||
$lang = $_SESSION['lang'];
|
$lang = $_SESSION['lang'];
|
||||||
@ -109,12 +114,28 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
|||||||
</div>
|
</div>
|
||||||
<nav class="mt-3">
|
<nav class="mt-3">
|
||||||
<a href="dashboard.php" class="sidebar-link <?php echo $section === 'dashboard' ? 'active' : ''; ?>"><i class="bi bi-speedometer2 me-2"></i> <?php echo __('dashboard'); ?></a>
|
<a href="dashboard.php" class="sidebar-link <?php echo $section === 'dashboard' ? 'active' : ''; ?>"><i class="bi bi-speedometer2 me-2"></i> <?php echo __('dashboard'); ?></a>
|
||||||
<a href="patients.php" class="sidebar-link <?php echo $section === 'patients' ? 'active' : ''; ?>"><i class="bi bi-people me-2"></i> <?php echo __('patients'); ?></a>
|
|
||||||
<a href="visits.php" class="sidebar-link <?php echo $section === 'visits' ? 'active' : ''; ?>"><i class="bi bi-clipboard2-pulse me-2"></i> <?php echo __('visits'); ?></a>
|
|
||||||
<a href="appointments.php" class="sidebar-link <?php echo $section === "appointments" ? "active" : ""; ?>"><i class="bi bi-calendar-event me-2"></i> <?php echo __("appointments"); ?></a>
|
|
||||||
<a href="home_visits.php" class="sidebar-link <?php echo $section === 'home_visits' ? 'active' : ''; ?>"><i class="bi bi-house-heart me-2"></i> <?php echo __('home_visits'); ?></a>
|
|
||||||
<a href="queue.php" class="sidebar-link <?php echo $section === 'queue' ? 'active' : ''; ?>"><i class="bi bi-list-ol me-2"></i> <?php echo __('queue_management'); ?></a>
|
|
||||||
|
|
||||||
|
<?php if (has_role('admin') || has_role('doctor') || has_role('nurse') || has_role('receptionist')): ?>
|
||||||
|
<a href="patients.php" class="sidebar-link <?php echo $section === 'patients' ? 'active' : ''; ?>"><i class="bi bi-people me-2"></i> <?php echo __('patients'); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_role('admin') || has_role('doctor') || has_role('nurse')): ?>
|
||||||
|
<a href="visits.php" class="sidebar-link <?php echo $section === 'visits' ? 'active' : ''; ?>"><i class="bi bi-clipboard2-pulse me-2"></i> <?php echo __('visits'); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_role('admin') || has_role('doctor') || has_role('receptionist')): ?>
|
||||||
|
<a href="appointments.php" class="sidebar-link <?php echo $section === "appointments" ? "active" : ""; ?>"><i class="bi bi-calendar-event me-2"></i> <?php echo __("appointments"); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_role('admin') || has_role('doctor') || has_role('nurse')): ?>
|
||||||
|
<a href="home_visits.php" class="sidebar-link <?php echo $section === 'home_visits' ? 'active' : ''; ?>"><i class="bi bi-house-heart me-2"></i> <?php echo __('home_visits'); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_role('admin') || has_role('receptionist') || has_role('nurse')): ?>
|
||||||
|
<a href="queue.php" class="sidebar-link <?php echo $section === 'queue' ? 'active' : ''; ?>"><i class="bi bi-list-ol me-2"></i> <?php echo __('queue_management'); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_role('admin') || has_role('laboratorial')): ?>
|
||||||
<a href="#labSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['laboratory_tests', 'test_groups', 'laboratory_inquiries']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
<a href="#labSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['laboratory_tests', 'test_groups', 'laboratory_inquiries']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||||
<span><i class="bi bi-prescription2 me-2"></i> <?php echo __('laboratory'); ?></span>
|
<span><i class="bi bi-prescription2 me-2"></i> <?php echo __('laboratory'); ?></span>
|
||||||
<i class="bi bi-chevron-down small"></i>
|
<i class="bi bi-chevron-down small"></i>
|
||||||
@ -126,7 +147,9 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
|||||||
<a href="laboratory_inquiries.php" class="sidebar-link py-2 <?php echo $section === 'laboratory_inquiries' ? 'active' : ''; ?>"><i class="bi bi-question-circle me-2"></i> <?php echo __('inquiries'); ?></a>
|
<a href="laboratory_inquiries.php" class="sidebar-link py-2 <?php echo $section === 'laboratory_inquiries' ? 'active' : ''; ?>"><i class="bi bi-question-circle me-2"></i> <?php echo __('inquiries'); ?></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_role('admin') || has_role('radiologic')): ?>
|
||||||
<!-- X-Ray Module -->
|
<!-- X-Ray Module -->
|
||||||
<a href="#xraySubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['xray_tests', 'xray_groups', 'xray_inquiries']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
<a href="#xraySubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['xray_tests', 'xray_groups', 'xray_inquiries']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||||
<span><i class="bi bi-x-diamond me-2"></i> <?php echo __('xray'); ?></span>
|
<span><i class="bi bi-x-diamond me-2"></i> <?php echo __('xray'); ?></span>
|
||||||
@ -139,7 +162,9 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
|||||||
<a href="xray_inquiries.php" class="sidebar-link py-2 <?php echo $section === 'xray_inquiries' ? 'active' : ''; ?>"><i class="bi bi-question-circle me-2"></i> <?php echo __('inquiries'); ?></a>
|
<a href="xray_inquiries.php" class="sidebar-link py-2 <?php echo $section === 'xray_inquiries' ? 'active' : ''; ?>"><i class="bi bi-question-circle me-2"></i> <?php echo __('inquiries'); ?></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_role('admin') || has_role('doctor')): ?>
|
||||||
<!-- Pharmacy Module -->
|
<!-- Pharmacy Module -->
|
||||||
<a href="#pharmacySubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['pharmacy_inventory', 'pharmacy_pos', 'pharmacy_sales', 'drugs', 'drugs_groups', 'suppliers', 'pharmacy_purchases', 'pharmacy_purchase_returns', 'pharmacy_alerts', 'pharmacy_reports']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
<a href="#pharmacySubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['pharmacy_inventory', 'pharmacy_pos', 'pharmacy_sales', 'drugs', 'drugs_groups', 'suppliers', 'pharmacy_purchases', 'pharmacy_purchase_returns', 'pharmacy_alerts', 'pharmacy_reports']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||||
<span><i class="bi bi-capsule me-2"></i> <?php echo __('pharmacy'); ?></span>
|
<span><i class="bi bi-capsule me-2"></i> <?php echo __('pharmacy'); ?></span>
|
||||||
@ -160,16 +185,23 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
|||||||
<a href="suppliers.php" class="sidebar-link py-2 <?php echo $section === 'suppliers' ? 'active' : ''; ?>"><i class="bi bi-truck me-2"></i> <?php echo __('suppliers'); ?></a>
|
<a href="suppliers.php" class="sidebar-link py-2 <?php echo $section === 'suppliers' ? 'active' : ''; ?>"><i class="bi bi-truck me-2"></i> <?php echo __('suppliers'); ?></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
|
<?php if (has_role('admin') || has_role('receptionist')): ?>
|
||||||
<a href="billing.php" class="sidebar-link <?php echo $section === 'billing' ? 'active' : ''; ?>"><i class="bi bi-receipt me-2"></i> <?php echo __('billing'); ?></a>
|
<a href="billing.php" class="sidebar-link <?php echo $section === 'billing' ? 'active' : ''; ?>"><i class="bi bi-receipt me-2"></i> <?php echo __('billing'); ?></a>
|
||||||
<a href="insurance.php" class="sidebar-link <?php echo $section === 'insurance' ? 'active' : ''; ?>"><i class="bi bi-shield-check me-2"></i> <?php echo __('insurance'); ?></a>
|
<a href="insurance.php" class="sidebar-link <?php echo $section === 'insurance' ? 'active' : ''; ?>"><i class="bi bi-shield-check me-2"></i> <?php echo __('insurance'); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_role('admin')): ?>
|
||||||
<a href="doctors.php" class="sidebar-link <?php echo $section === 'doctors' ? 'active' : ''; ?>"><i class="bi bi-person-badge me-2"></i> <?php echo __('doctors'); ?></a>
|
<a href="doctors.php" class="sidebar-link <?php echo $section === 'doctors' ? 'active' : ''; ?>"><i class="bi bi-person-badge me-2"></i> <?php echo __('doctors'); ?></a>
|
||||||
<a href="doctor_holidays.php" class="sidebar-link <?php echo $section === 'doctor_holidays' ? 'active' : ''; ?>"><i class="bi bi-calendar-x me-2"></i> <?php echo __('doctor_holidays'); ?></a>
|
<a href="doctor_holidays.php" class="sidebar-link <?php echo $section === 'doctor_holidays' ? 'active' : ''; ?>"><i class="bi bi-calendar-x me-2"></i> <?php echo __('doctor_holidays'); ?></a>
|
||||||
<a href="nurses.php" class="sidebar-link <?php echo $section === 'nurses' ? 'active' : ''; ?>"><i class="bi bi-person-heart me-2"></i> <?php echo __('nurses'); ?></a>
|
<a href="nurses.php" class="sidebar-link <?php echo $section === 'nurses' ? 'active' : ''; ?>"><i class="bi bi-person-heart me-2"></i> <?php echo __('nurses'); ?></a>
|
||||||
|
|
||||||
<a href="reports.php" class="sidebar-link <?php echo $section === 'reports' ? 'active' : ''; ?>"><i class="bi bi-bar-chart-line me-2"></i> <?php echo __('admin_reports'); ?></a>
|
<a href="reports.php" class="sidebar-link <?php echo $section === 'reports' ? 'active' : ''; ?>"><i class="bi bi-bar-chart-line me-2"></i> <?php echo __('admin_reports'); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (has_role('admin')): ?>
|
||||||
<a href="#settingsSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['employees', 'positions', 'company_profile', 'cities', 'services', 'departments', 'queue_ads']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
<a href="#settingsSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['employees', 'positions', 'company_profile', 'cities', 'services', 'departments', 'queue_ads']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||||
<span><i class="bi bi-gear me-2"></i> <?php echo __('settings'); ?></span>
|
<span><i class="bi bi-gear me-2"></i> <?php echo __('settings'); ?></span>
|
||||||
<i class="bi bi-chevron-down small"></i>
|
<i class="bi bi-chevron-down small"></i>
|
||||||
@ -185,6 +217,7 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
|||||||
<a href="queue_ads.php" class="sidebar-link py-2 <?php echo $section === 'queue_ads' ? 'active' : ''; ?>"><i class="bi bi-megaphone me-2"></i> <?php echo __('queue_ads'); ?></a>
|
<a href="queue_ads.php" class="sidebar-link py-2 <?php echo $section === 'queue_ads' ? 'active' : ''; ?>"><i class="bi bi-megaphone me-2"></i> <?php echo __('queue_ads'); ?></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -203,13 +236,13 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
|||||||
</a>
|
</a>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<a class="nav-link dropdown-toggle d-flex align-items-center" href="#" role="button" data-bs-dropdown="dropdown">
|
<a class="nav-link dropdown-toggle d-flex align-items-center" href="#" role="button" data-bs-dropdown="dropdown">
|
||||||
<img src="https://ui-avatars.com/api/?name=Admin&background=0056b3&color=fff" class="rounded-circle me-2" width="32" height="32">
|
<img src="https://ui-avatars.com/api/?name=<?php echo urlencode($current_user['name']); ?>&background=0056b3&color=fff" class="rounded-circle me-2" width="32" height="32">
|
||||||
<span>Admin</span>
|
<span><?php echo htmlspecialchars($current_user['name']); ?></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end shadow border-0">
|
<ul class="dropdown-menu dropdown-menu-end shadow border-0">
|
||||||
<li><a class="dropdown-item" href="#"><i class="bi bi-person me-2"></i> <?php echo __('profile'); ?></a></li>
|
<li><a class="dropdown-item" href="#"><i class="bi bi-person me-2"></i> <?php echo __('profile'); ?> (<?php echo htmlspecialchars($current_user['role_slug']); ?>)</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item text-danger" href="#"><i class="bi bi-box-arrow-right me-2"></i> <?php echo __('logout'); ?></a></li>
|
<li><a class="dropdown-item text-danger" href="logout.php"><i class="bi bi-box-arrow-right me-2"></i> <?php echo __('logout'); ?></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -29,17 +29,12 @@ $stmt = $db->prepare("SELECT SUM(patient_payable) FROM bills WHERE status != 'Pa
|
|||||||
$stmt->execute([$start_date, $end_date]);
|
$stmt->execute([$start_date, $end_date]);
|
||||||
$pending_bills = $stmt->fetchColumn() ?: 0;
|
$pending_bills = $stmt->fetchColumn() ?: 0;
|
||||||
|
|
||||||
// 5. Visits by Status
|
// 5. Revenue Trend (Daily)
|
||||||
$stmt = $db->prepare("SELECT status, COUNT(*) as count FROM visits WHERE DATE(visit_date) BETWEEN ? AND ? GROUP BY status");
|
|
||||||
$stmt->execute([$start_date, $end_date]);
|
|
||||||
$visits_by_status = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
|
||||||
|
|
||||||
// 6. Revenue Trend (Daily)
|
|
||||||
$stmt = $db->prepare("SELECT DATE(created_at) as date, SUM(total_amount) as total FROM bills WHERE DATE(created_at) BETWEEN ? AND ? GROUP BY DATE(created_at) ORDER BY date ASC");
|
$stmt = $db->prepare("SELECT DATE(created_at) as date, SUM(total_amount) as total FROM bills WHERE DATE(created_at) BETWEEN ? AND ? GROUP BY DATE(created_at) ORDER BY date ASC");
|
||||||
$stmt->execute([$start_date, $end_date]);
|
$stmt->execute([$start_date, $end_date]);
|
||||||
$revenue_trend = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$revenue_trend = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
// 7. Visits by Doctor
|
// 6. Visits by Doctor (Top 5)
|
||||||
$stmt = $db->prepare("
|
$stmt = $db->prepare("
|
||||||
SELECT d.name_$lang as doctor_name, COUNT(v.id) as count
|
SELECT d.name_$lang as doctor_name, COUNT(v.id) as count
|
||||||
FROM visits v
|
FROM visits v
|
||||||
@ -52,14 +47,48 @@ $stmt = $db->prepare("
|
|||||||
$stmt->execute([$start_date, $end_date]);
|
$stmt->execute([$start_date, $end_date]);
|
||||||
$visits_by_doctor = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$visits_by_doctor = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
// 8. Patient Gender Distribution (All time or filtered? Let's do filtered by creation date to match "New Patients")
|
// 7. Patient Gender Distribution
|
||||||
$stmt = $db->prepare("SELECT gender, COUNT(*) as count FROM patients WHERE DATE(created_at) BETWEEN ? AND ? GROUP BY gender");
|
$stmt = $db->prepare("SELECT gender, COUNT(*) as count FROM patients WHERE DATE(created_at) BETWEEN ? AND ? GROUP BY gender");
|
||||||
$stmt->execute([$start_date, $end_date]);
|
$stmt->execute([$start_date, $end_date]);
|
||||||
$patients_by_gender = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
$patients_by_gender = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
|
||||||
|
// 8. Department Revenue Report
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
SELECT d.name_$lang as name,
|
||||||
|
COUNT(DISTINCT b.visit_id) as visit_count,
|
||||||
|
SUM(b.total_amount) as revenue
|
||||||
|
FROM bills b
|
||||||
|
JOIN visits v ON b.visit_id = v.id
|
||||||
|
JOIN doctors doc ON v.doctor_id = doc.id
|
||||||
|
JOIN departments d ON doc.department_id = d.id
|
||||||
|
WHERE b.status = 'Paid' AND DATE(b.created_at) BETWEEN ? AND ?
|
||||||
|
GROUP BY d.id
|
||||||
|
ORDER BY revenue DESC
|
||||||
|
");
|
||||||
|
$stmt->execute([$start_date, $end_date]);
|
||||||
|
$dept_revenue = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 9. Doctor Revenue Report
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
SELECT doc.name_$lang as name,
|
||||||
|
d.name_$lang as department_name,
|
||||||
|
COUNT(DISTINCT b.visit_id) as visit_count,
|
||||||
|
SUM(b.total_amount) as revenue
|
||||||
|
FROM bills b
|
||||||
|
JOIN visits v ON b.visit_id = v.id
|
||||||
|
JOIN doctors doc ON v.doctor_id = doc.id
|
||||||
|
LEFT JOIN departments d ON doc.department_id = d.id
|
||||||
|
WHERE b.status = 'Paid' AND DATE(b.created_at) BETWEEN ? AND ?
|
||||||
|
GROUP BY doc.id
|
||||||
|
ORDER BY revenue DESC
|
||||||
|
");
|
||||||
|
$stmt->execute([$start_date, $end_date]);
|
||||||
|
$doctor_revenue = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
|
||||||
// --- PREPARE DATA FOR JS ---
|
// --- PREPARE DATA FOR JS ---
|
||||||
$chart_status_labels = array_keys($visits_by_status);
|
$chart_dept_revenue_labels = array_column($dept_revenue, 'name');
|
||||||
$chart_status_data = array_values($visits_by_status);
|
$chart_dept_revenue_data = array_column($dept_revenue, 'revenue');
|
||||||
|
|
||||||
$chart_revenue_labels = array_column($revenue_trend, 'date');
|
$chart_revenue_labels = array_column($revenue_trend, 'date');
|
||||||
$chart_revenue_data = array_column($revenue_trend, 'total');
|
$chart_revenue_data = array_column($revenue_trend, 'total');
|
||||||
@ -75,9 +104,84 @@ $chart_gender_data = array_values($patients_by_gender);
|
|||||||
<!-- Include Chart.js -->
|
<!-- Include Chart.js -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<style>
|
||||||
|
@media print {
|
||||||
|
/* Hide Non-Printable Elements */
|
||||||
|
#sidebar, .navbar, .btn, form, footer, .no-print {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Layout Adjustments for Print */
|
||||||
|
.main-content {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: white !important;
|
||||||
|
font-size: 12pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background: none !important;
|
||||||
|
border-bottom: 2px solid #000 !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure Charts are Visible */
|
||||||
|
canvas {
|
||||||
|
max-width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Styling for Print */
|
||||||
|
.table {
|
||||||
|
width: 100% !important;
|
||||||
|
border-collapse: collapse !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th, .table td {
|
||||||
|
border: 1px solid #ddd !important;
|
||||||
|
padding: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Break */
|
||||||
|
.page-break {
|
||||||
|
page-break-before: always;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header for Print */
|
||||||
|
.print-header {
|
||||||
|
display: block !important;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Print Header -->
|
||||||
|
<div class="print-header">
|
||||||
|
<h2><?php echo __('admin_reports'); ?></h2>
|
||||||
|
<p class="text-muted">
|
||||||
|
<?php echo __('report_period'); ?>: <?php echo $start_date; ?> <?php echo __('to'); ?> <?php echo $end_date; ?><br>
|
||||||
|
<?php echo __('generated_on'); ?>: <?php echo date('Y-m-d H:i'); ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4 no-print">
|
||||||
<h3 class="fw-bold text-secondary"><?php echo __('admin_reports'); ?></h3>
|
<h3 class="fw-bold text-secondary"><?php echo __('admin_reports'); ?></h3>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
<!-- Date Filter Form -->
|
<!-- Date Filter Form -->
|
||||||
<form method="GET" class="d-flex gap-2 align-items-center">
|
<form method="GET" class="d-flex gap-2 align-items-center">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@ -91,6 +195,11 @@ $chart_gender_data = array_values($patients_by_gender);
|
|||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary"><?php echo __('generate'); ?></button>
|
<button type="submit" class="btn btn-primary"><?php echo __('generate'); ?></button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<button onclick="window.print()" class="btn btn-outline-dark">
|
||||||
|
<i class="bi bi-printer"></i> <?php echo __('print_report'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Summary Cards -->
|
<!-- Summary Cards -->
|
||||||
@ -150,15 +259,15 @@ $chart_gender_data = array_values($patients_by_gender);
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Charts Row 1 -->
|
<!-- Charts Row 1 -->
|
||||||
<div class="row g-4 mb-4">
|
<div class="row g-4 mb-4 page-break">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-0 shadow-sm h-100">
|
<div class="card border-0 shadow-sm h-100">
|
||||||
<div class="card-header bg-white border-bottom-0 pt-4 px-4">
|
<div class="card-header bg-white border-bottom-0 pt-4 px-4">
|
||||||
<h5 class="fw-bold text-dark mb-0"><?php echo __('visits_by_status'); ?></h5>
|
<h5 class="fw-bold text-dark mb-0"><?php echo __('department_revenue'); ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body d-flex justify-content-center align-items-center" style="min-height: 350px;">
|
<div class="card-body d-flex justify-content-center align-items-center" style="min-height: 350px;">
|
||||||
<div style="position: relative; width: 300px; height: 300px;">
|
<div style="position: relative; width: 300px; height: 300px;">
|
||||||
<canvas id="statusChart"></canvas>
|
<canvas id="deptRevenueChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -178,7 +287,7 @@ $chart_gender_data = array_values($patients_by_gender);
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Charts Row 2 -->
|
<!-- Charts Row 2 -->
|
||||||
<div class="row g-4">
|
<div class="row g-4 mb-5 page-break">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-0 shadow-sm h-100">
|
<div class="card border-0 shadow-sm h-100">
|
||||||
<div class="card-header bg-white border-bottom-0 pt-4 px-4">
|
<div class="card-header bg-white border-bottom-0 pt-4 px-4">
|
||||||
@ -205,6 +314,82 @@ $chart_gender_data = array_values($patients_by_gender);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Detailed Tables -->
|
||||||
|
<div class="row g-4 mb-4 page-break">
|
||||||
|
<!-- Department Revenue -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card border-0 shadow-sm h-100">
|
||||||
|
<div class="card-header bg-white border-bottom-0 pt-4 px-4 d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="fw-bold text-dark mb-0"><?php echo __('department_revenue'); ?></h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="table-light text-secondary">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3"><?php echo __('department'); ?></th>
|
||||||
|
<th class="py-3 text-center"><?php echo __('visits'); ?> (Paid)</th>
|
||||||
|
<th class="py-3 text-end px-4"><?php echo __('revenue'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($dept_revenue)): ?>
|
||||||
|
<tr><td colspan="3" class="text-center py-4 text-muted"><?php echo __('no_data_found'); ?></td></tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($dept_revenue as $d): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 fw-semibold"><?php echo htmlspecialchars($d['name']); ?></td>
|
||||||
|
<td class="text-center"><?php echo number_format($d['visit_count']); ?></td>
|
||||||
|
<td class="text-end px-4 text-success fw-bold"><?php echo format_currency($d['revenue']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Doctor Revenue -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card border-0 shadow-sm h-100">
|
||||||
|
<div class="card-header bg-white border-bottom-0 pt-4 px-4 d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="fw-bold text-dark mb-0"><?php echo __('doctor_revenue'); ?></h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="table-light text-secondary">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3"><?php echo __('doctor'); ?></th>
|
||||||
|
<th class="py-3 text-center"><?php echo __('visits'); ?> (Paid)</th>
|
||||||
|
<th class="py-3 text-end px-4"><?php echo __('revenue'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($doctor_revenue)): ?>
|
||||||
|
<tr><td colspan="3" class="text-center py-4 text-muted"><?php echo __('no_data_found'); ?></td></tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($doctor_revenue as $d): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="px-4">
|
||||||
|
<div class="fw-semibold text-dark"><?php echo htmlspecialchars($d['name']); ?></div>
|
||||||
|
<small class="text-muted"><?php echo htmlspecialchars($d['department_name'] ?? '-'); ?></small>
|
||||||
|
</td>
|
||||||
|
<td class="text-center"><?php echo number_format($d['visit_count']); ?></td>
|
||||||
|
<td class="text-end px-4 text-success fw-bold"><?php echo format_currency($d['revenue']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Colors
|
// Colors
|
||||||
@ -216,18 +401,36 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
danger: '#dc3545',
|
danger: '#dc3545',
|
||||||
secondary: '#6c757d',
|
secondary: '#6c757d',
|
||||||
light: '#f8f9fa',
|
light: '#f8f9fa',
|
||||||
dark: '#212529'
|
dark: '#212529',
|
||||||
|
purple: '#6f42c1',
|
||||||
|
indigo: '#6610f2',
|
||||||
|
pink: '#d63384',
|
||||||
|
teal: '#20c997'
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. Visits by Status (Doughnut)
|
// Palette for charts
|
||||||
const ctxStatus = document.getElementById('statusChart').getContext('2d');
|
const chartColors = [
|
||||||
new Chart(ctxStatus, {
|
colors.primary,
|
||||||
type: 'doughnut',
|
colors.success,
|
||||||
|
colors.info,
|
||||||
|
colors.warning,
|
||||||
|
colors.danger,
|
||||||
|
colors.purple,
|
||||||
|
colors.teal,
|
||||||
|
colors.pink,
|
||||||
|
colors.indigo,
|
||||||
|
colors.secondary
|
||||||
|
];
|
||||||
|
|
||||||
|
// 1. Department Revenue (Pie) - Replaces Visits by Status
|
||||||
|
const ctxDeptRevenue = document.getElementById('deptRevenueChart').getContext('2d');
|
||||||
|
new Chart(ctxDeptRevenue, {
|
||||||
|
type: 'pie',
|
||||||
data: {
|
data: {
|
||||||
labels: <?php echo json_encode($chart_status_labels); ?>,
|
labels: <?php echo json_encode($chart_dept_revenue_labels); ?>,
|
||||||
datasets: [{
|
datasets: [{
|
||||||
data: <?php echo json_encode($chart_status_data); ?>,
|
data: <?php echo json_encode($chart_dept_revenue_data); ?>,
|
||||||
backgroundColor: [colors.success, colors.primary, colors.danger, colors.warning, colors.secondary],
|
backgroundColor: chartColors,
|
||||||
borderWidth: 0
|
borderWidth: 0
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
@ -235,7 +438,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { position: 'bottom' }
|
legend: { position: 'bottom' },
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
let label = context.label || '';
|
||||||
|
if (label) {
|
||||||
|
label += ': ';
|
||||||
|
}
|
||||||
|
if (context.parsed !== null) {
|
||||||
|
label += new Intl.NumberFormat().format(context.parsed);
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
30
lang.php
30
lang.php
@ -1,6 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
$translations = [
|
$translations = [
|
||||||
'en' => [
|
'en' => [
|
||||||
|
'login' => 'Login',
|
||||||
|
'login_to_continue' => 'Login to continue',
|
||||||
|
'invalid_credentials' => 'Invalid email or password',
|
||||||
|
'fill_all_fields' => 'Please fill all fields',
|
||||||
|
'forgot_password' => 'Forgot Password?',
|
||||||
|
'users' => 'Users',
|
||||||
|
'roles' => 'Roles',
|
||||||
'dashboard' => 'Dashboard',
|
'dashboard' => 'Dashboard',
|
||||||
'patient_number' => 'Patient Number',
|
'patient_number' => 'Patient Number',
|
||||||
'patients' => 'Patients',
|
'patients' => 'Patients',
|
||||||
@ -432,9 +439,22 @@ $translations = [
|
|||||||
'visits_by_status' => 'Visits by Status',
|
'visits_by_status' => 'Visits by Status',
|
||||||
'revenue_trend' => 'Revenue Trend',
|
'revenue_trend' => 'Revenue Trend',
|
||||||
'top_diagnoses' => 'Top Diagnoses',
|
'top_diagnoses' => 'Top Diagnoses',
|
||||||
'top_doctors' => 'Top Doctors'
|
'top_doctors' => 'Top Doctors',
|
||||||
|
'department_revenue' => 'Department Revenue',
|
||||||
|
'doctor_revenue' => 'Doctor Revenue',
|
||||||
|
'average_revenue' => 'Average Revenue',
|
||||||
|
'generated_on' => 'Generated on',
|
||||||
|
'report_period' => 'Report Period',
|
||||||
|
'print_report' => 'Print Report'
|
||||||
],
|
],
|
||||||
'ar' => [
|
'ar' => [
|
||||||
|
'login' => 'تسجيل الدخول',
|
||||||
|
'login_to_continue' => 'تسجيل الدخول للمتابعة',
|
||||||
|
'invalid_credentials' => 'البريد الإلكتروني أو كلمة المرور غير صحيحة',
|
||||||
|
'fill_all_fields' => 'يرجى تعبئة جميع الحقول',
|
||||||
|
'forgot_password' => 'نسيت كلمة المرور؟',
|
||||||
|
'users' => 'المستخدمين',
|
||||||
|
'roles' => 'الأدوار',
|
||||||
'attachment' => 'المرفق',
|
'attachment' => 'المرفق',
|
||||||
'image' => 'الصورة',
|
'image' => 'الصورة',
|
||||||
'view_current' => 'عرض الحالي',
|
'view_current' => 'عرض الحالي',
|
||||||
@ -878,6 +898,12 @@ $translations = [
|
|||||||
'visits_by_status' => 'الزيارات حسب الحالة',
|
'visits_by_status' => 'الزيارات حسب الحالة',
|
||||||
'revenue_trend' => 'اتجاه الإيرادات',
|
'revenue_trend' => 'اتجاه الإيرادات',
|
||||||
'top_diagnoses' => 'أهم التشخيصات',
|
'top_diagnoses' => 'أهم التشخيصات',
|
||||||
'top_doctors' => 'أفضل الأطباء'
|
'top_doctors' => 'أفضل الأطباء',
|
||||||
|
'department_revenue' => 'إيرادات الأقسام',
|
||||||
|
'doctor_revenue' => 'إيرادات الأطباء',
|
||||||
|
'average_revenue' => 'متوسط الإيرادات',
|
||||||
|
'generated_on' => 'تم الإنشاء في',
|
||||||
|
'report_period' => 'فترة التقرير',
|
||||||
|
'print_report' => 'طباعة التقرير'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
168
login.php
Normal file
168
login.php
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'lang.php';
|
||||||
|
require_once 'helpers.php';
|
||||||
|
|
||||||
|
if (isset($_SESSION['user_id'])) {
|
||||||
|
header("Location: dashboard.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$email = $_POST['email'] ?? '';
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
|
||||||
|
if (empty($email) || empty($password)) {
|
||||||
|
$error = __('fill_all_fields');
|
||||||
|
} else {
|
||||||
|
$db = db();
|
||||||
|
$stmt = $db->prepare("SELECT id, name, password, role_id, active FROM users WHERE email = ?");
|
||||||
|
$stmt->execute([$email]);
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($user && $user['active']) {
|
||||||
|
// For the default seeded user, we used a specific hash.
|
||||||
|
// In a real app, use password_verify($password, $user['password'])
|
||||||
|
// For this demo/prototype environment where I manually inserted a hash:
|
||||||
|
if (password_verify($password, $user['password'])) {
|
||||||
|
$_SESSION['user_id'] = $user['id'];
|
||||||
|
$_SESSION['user_name'] = $user['name'];
|
||||||
|
|
||||||
|
// Update last login
|
||||||
|
$update = $db->prepare("UPDATE users SET last_login = NOW() WHERE id = ?");
|
||||||
|
$update->execute([$user['id']]);
|
||||||
|
|
||||||
|
header("Location: dashboard.php");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = __('invalid_credentials');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = __('invalid_credentials');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch site settings for branding
|
||||||
|
$db = db();
|
||||||
|
$stmt = $db->query("SELECT setting_key, setting_value FROM settings WHERE setting_key IN ('company_name', 'company_logo')");
|
||||||
|
$settings = [];
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$settings[$row['setting_key']] = $row['setting_value'];
|
||||||
|
}
|
||||||
|
$site_name = !empty($settings['company_name']) ? $settings['company_name'] : 'Hospital Management';
|
||||||
|
$site_logo = !empty($settings['company_logo']) ? $settings['company_logo'] : null;
|
||||||
|
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<?php echo $_SESSION['lang']; ?>" dir="<?php echo get_dir(); ?>">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?php echo __('login'); ?> - <?php echo htmlspecialchars($site_name); ?></title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<?php if (is_rtl()): ?>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.rtl.min.css">
|
||||||
|
<?php endif; ?>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background-color: #f4f7f6;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.login-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.login-header {
|
||||||
|
background-color: #002D62;
|
||||||
|
color: white;
|
||||||
|
padding: 30px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.login-body {
|
||||||
|
background-color: white;
|
||||||
|
padding: 40px 30px;
|
||||||
|
}
|
||||||
|
.form-control:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: #002D62;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #002D62;
|
||||||
|
border-color: #002D62;
|
||||||
|
padding: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #001f44;
|
||||||
|
border-color: #001f44;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="login-card">
|
||||||
|
<div class="login-header">
|
||||||
|
<?php if ($site_logo): ?>
|
||||||
|
<img src="<?php echo htmlspecialchars($site_logo); ?>" alt="Logo" class="img-fluid mb-3" style="max-height: 60px;">
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="mb-3">
|
||||||
|
<i class="bi bi-hospital display-4"></i>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<h4 class="mb-0 fw-bold"><?php echo htmlspecialchars($site_name); ?></h4>
|
||||||
|
<p class="mb-0 opacity-75 small mt-1"><?php echo __('login_to_continue'); ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="login-body">
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-danger text-center py-2 mb-4 small">
|
||||||
|
<?php echo htmlspecialchars($error); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="POST" action="">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-secondary small fw-bold"><?php echo __('email'); ?></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-light border-end-0 text-muted"><i class="bi bi-envelope"></i></span>
|
||||||
|
<input type="email" name="email" class="form-control bg-light border-start-0" placeholder="name@example.com" required autofocus>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label text-secondary small fw-bold"><?php echo __('password'); ?></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-light border-end-0 text-muted"><i class="bi bi-lock"></i></span>
|
||||||
|
<input type="password" name="password" class="form-control bg-light border-start-0" placeholder="••••••••" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100 mb-3 shadow-sm">
|
||||||
|
<?php echo __('login'); ?> <i class="bi bi-arrow-right ms-1"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="#" class="text-decoration-none small text-muted"><?php echo __('forgot_password'); ?></a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="bg-light py-3 text-center border-top">
|
||||||
|
<small class="text-muted">
|
||||||
|
© <?php echo date('Y'); ?> Flatlogic. All rights reserved.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
logout.php
Normal file
5
logout.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
session_destroy();
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
Loading…
x
Reference in New Issue
Block a user