Autosave: 20260227-182025

This commit is contained in:
Flatlogic Bot 2026-02-27 18:20:25 +00:00
parent 6d5518a7b7
commit 2900795488
15 changed files with 1508 additions and 329 deletions

View File

@ -1,20 +1,31 @@
<?php
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/mail/MailService.php';
// Only admins can access this page
if (!isAdmin()) {
header("Location: index.php");
exit;
redirect("index.php");
}
$success_msg = '';
$error_msg = '';
// Handle Re-enable SMTP
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['enable_smtp'])) {
db()->query("UPDATE smtp_settings SET is_enabled = 1, consecutive_failures = 0 WHERE id = 1");
$success_msg = 'تم إعادة تفعيل SMTP وتصفير عداد الأخطاء';
}
// Fetch charity settings
$stmt = db()->query("SELECT * FROM charity_settings WHERE id = 1");
$charity = $stmt->fetch();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Fetch SMTP settings
$stmt = db()->query("SELECT * FROM smtp_settings WHERE id = 1");
$smtp = $stmt->fetch();
// Handle Charity Settings Update
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_charity'])) {
$charity_name = $_POST['charity_name'];
$charity_email = $_POST['charity_email'];
$charity_phone = $_POST['charity_phone'];
@ -25,7 +36,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$upload_dir = 'uploads/charity/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0775, true);
// Handle Logo Upload
if (isset($_FILES['charity_logo']) && $_FILES['charity_logo']['error'] === UPLOAD_ERR_OK) {
$file_ext = pathinfo($_FILES['charity_logo']['name'], PATHINFO_EXTENSION);
$new_logo = 'logo_' . time() . '.' . $file_ext;
@ -34,7 +44,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
// Handle Favicon Upload
if (isset($_FILES['charity_favicon']) && $_FILES['charity_favicon']['error'] === UPLOAD_ERR_OK) {
$file_ext = pathinfo($_FILES['charity_favicon']['name'], PATHINFO_EXTENSION);
$new_favicon = 'favicon_' . time() . '.' . $file_ext;
@ -46,72 +55,343 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt = db()->prepare("UPDATE charity_settings SET charity_name = ?, charity_email = ?, charity_phone = ?, charity_address = ?, charity_logo = ?, charity_favicon = ? WHERE id = 1");
$stmt->execute([$charity_name, $charity_email, $charity_phone, $charity_address, $charity_logo, $charity_favicon]);
$success_msg = 'تم تحديث إعدادات الجمعية بنجاح';
// Refresh charity data
$stmt = db()->query("SELECT * FROM charity_settings WHERE id = 1");
$charity = $stmt->fetch();
}
// Handle SMTP Settings Update
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_smtp'])) {
$stmt = db()->prepare("UPDATE smtp_settings SET smtp_host = ?, smtp_port = ?, smtp_secure = ?, smtp_user = ?, smtp_pass = ?, from_email = ?, from_name = ?, reply_to = ?, max_failures = ? WHERE id = 1");
$stmt->execute([
$_POST['smtp_host'],
(int)$_POST['smtp_port'],
$_POST['smtp_secure'],
$_POST['smtp_user'],
$_POST['smtp_pass'],
$_POST['from_email'],
$_POST['from_name'],
$_POST['reply_to'],
(int)$_POST['max_failures']
]);
$success_msg = 'تم تحديث إعدادات البريد (SMTP) بنجاح';
$stmt = db()->query("SELECT * FROM smtp_settings WHERE id = 1");
$smtp = $stmt->fetch();
}
// Handle Test Email
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['test_email_addr'])) {
$to = $_POST['test_email_addr'];
$res = MailService::sendMail($to, "رسالة تجريبية - Test Email", "<p>إذا كنت ترى هذه الرسالة، فإن إعدادات SMTP تعمل بشكل صحيح.</p>");
if ($res['success']) {
$success_msg = "تم إرسال الرسالة التجريبية بنجاح إلى $to";
} else {
$error_msg = "فشل إرسال الرسالة التجريبية: " . $res['error'];
}
}
// Handle Status Operations
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_status'])) {
$name = $_POST['status_name'];
$color = $_POST['status_color'];
$is_default = isset($_POST['is_default']) ? 1 : 0;
if ($is_default) db()->query("UPDATE mailbox_statuses SET is_default = 0");
$stmt = db()->prepare("INSERT INTO mailbox_statuses (name, color, is_default) VALUES (?, ?, ?)");
$stmt->execute([$name, $color, $is_default]);
$success_msg = 'تم إضافة نوع الحالة بنجاح';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_status'])) {
$id = $_POST['status_id'];
$name = $_POST['status_name'];
$color = $_POST['status_color'];
$is_default = isset($_POST['is_default']) ? 1 : 0;
if ($is_default) db()->query("UPDATE mailbox_statuses SET is_default = 0");
$stmt = db()->prepare("UPDATE mailbox_statuses SET name = ?, color = ?, is_default = ? WHERE id = ?");
$stmt->execute([$name, $color, $is_default, $id]);
$success_msg = 'تم تحديث نوع الحالة بنجاح';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_status'])) {
$id = $_POST['status_id'];
$count = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE status_id = ?");
$count->execute([$id]);
if ($count->fetchColumn() > 0) {
$error_msg = 'لا يمكن حذف هذه الحالة لأنها مستخدمة في بعض السجلات';
} else {
db()->prepare("DELETE FROM mailbox_statuses WHERE id = ?")->execute([$id]);
$success_msg = 'تم حذف نوع الحالة بنجاح';
}
}
$statuses = db()->query("SELECT * FROM mailbox_statuses ORDER BY id ASC")->fetchAll();
$email_logs = db()->query("SELECT * FROM email_logs ORDER BY id DESC LIMIT 50")->fetchAll();
?>
<div class="row">
<div class="col-md-12 mb-4">
<h2 class="fw-bold"><i class="fas fa-cog me-2"></i> إعدادات الجمعية</h2>
<h2 class="fw-bold"><i class="fas fa-cog me-2"></i> الإعدادات</h2>
</div>
<?php if ($success_msg): ?>
<div class="alert alert-success"><?= $success_msg ?></div>
<?php endif; ?>
<?php if ($error_msg): ?>
<div class="alert alert-danger"><?= $error_msg ?></div>
<?php endif; ?>
<div class="col-md-8 mx-auto">
<div class="card p-4">
<h4 class="mb-4">بيانات الجمعية</h4>
<form method="POST" enctype="multipart/form-data">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">اسم الجمعية</label>
<input type="text" name="charity_name" class="form-control" value="<?= htmlspecialchars($charity['charity_name'] ?? '') ?>" required>
<div class="col-md-12">
<ul class="nav nav-tabs mb-4" id="settingsTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="general-tab" data-bs-toggle="tab" data-bs-target="#general" type="button" role="tab">بيانات الجمعية</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="smtp-tab" data-bs-toggle="tab" data-bs-target="#smtp" type="button" role="tab">إعدادات SMTP</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="statuses-tab" data-bs-toggle="tab" data-bs-target="#statuses" type="button" role="tab">حالات البريد</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="logs-tab" data-bs-toggle="tab" data-bs-target="#logs" type="button" role="tab">سجلات البريد</button>
</li>
</ul>
<div class="tab-content bg-white p-4 shadow-sm rounded border" id="settingsTabsContent">
<!-- General Settings -->
<div class="tab-pane show active" id="general" role="tabpanel">
<h4 class="mb-4">بيانات الجمعية</h4>
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="update_charity" value="1">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">اسم الجمعية</label>
<input type="text" name="charity_name" class="form-control" value="<?= htmlspecialchars($charity['charity_name'] ?? '') ?>" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">البريد الإلكتروني للجمعية</label>
<input type="email" name="charity_email" class="form-control" value="<?= htmlspecialchars($charity['charity_email'] ?? '') ?>">
</div>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">البريد الإلكتروني للجمعية</label>
<input type="email" name="charity_email" class="form-control" value="<?= htmlspecialchars($charity['charity_email'] ?? '') ?>">
<div class="mb-3">
<label class="form-label">رقم الهاتف</label>
<input type="text" name="charity_phone" class="form-control" value="<?= htmlspecialchars($charity['charity_phone'] ?? '') ?>">
</div>
</div>
<div class="mb-3">
<label class="form-label">رقم الهاتف</label>
<input type="text" name="charity_phone" class="form-control" value="<?= htmlspecialchars($charity['charity_phone'] ?? '') ?>">
<div class="mb-3">
<label class="form-label">العنوان</label>
<textarea name="charity_address" class="form-control" rows="3"><?= htmlspecialchars($charity['charity_address'] ?? '') ?></textarea>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">شعار الجمعية</label>
<input type="file" name="charity_logo" class="form-control" accept="image/*">
<?php if ($charity['charity_logo']): ?>
<div class="mt-2"><img src="<?= $charity['charity_logo'] ?>" alt="Logo" style="max-height: 80px;"></div>
<?php endif; ?>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">أيقونة الموقع (Favicon)</label>
<input type="file" name="charity_favicon" class="form-control" accept="image/x-icon,image/png">
<?php if ($charity['charity_favicon']): ?>
<div class="mt-2"><img src="<?= $charity['charity_favicon'] ?>" alt="Favicon" style="max-height: 32px;"></div>
<?php endif; ?>
</div>
</div>
<button type="submit" class="btn btn-dark">تحديث إعدادات الجمعية</button>
</form>
</div>
<!-- SMTP Settings -->
<div class="tab-pane" id="smtp" role="tabpanel">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4>إعدادات البريد (SMTP)</h4>
<?php if (!$smtp['is_enabled']): ?>
<div class="badge bg-danger p-2">
<i class="fas fa-exclamation-triangle me-1"></i> SMTP معطل بسبب كثرة الأخطاء
<form method="POST" style="display:inline;" class="ms-2">
<button type="submit" name="enable_smtp" class="btn btn-sm btn-light">إعادة تفعيل</button>
</form>
</div>
<?php else: ?>
<div class="badge bg-success p-2">
<i class="fas fa-check-circle me-1"></i> SMTP مفعل (أخطاء: <?= $smtp['consecutive_failures'] ?>/<?= $smtp['max_failures'] ?>)
</div>
<?php endif; ?>
</div>
<div class="mb-3">
<label class="form-label">العنوان</label>
<textarea name="charity_address" class="form-control" rows="3"><?= htmlspecialchars($charity['charity_address'] ?? '') ?></textarea>
</div>
<form method="POST">
<input type="hidden" name="update_smtp" value="1">
<div class="row">
<div class="col-md-8 mb-3">
<label class="form-label">SMTP Host</label>
<input type="text" name="smtp_host" class="form-control" value="<?= htmlspecialchars($smtp['smtp_host'] ?? '') ?>">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">SMTP Port</label>
<input type="number" name="smtp_port" class="form-control" value="<?= htmlspecialchars($smtp['smtp_port'] ?? 587) ?>">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">SMTP Security</label>
<select name="smtp_secure" class="form-select">
<option value="tls" <?= ($smtp['smtp_secure'] ?? '') === 'tls' ? 'selected' : '' ?>>TLS (Recommended)</option>
<option value="ssl" <?= ($smtp['smtp_secure'] ?? '') === 'ssl' ? 'selected' : '' ?>>SSL</option>
<option value="none" <?= ($smtp['smtp_secure'] ?? '') === 'none' ? 'selected' : '' ?>>None</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Sender Name</label>
<input type="text" name="from_name" class="form-control" value="<?= htmlspecialchars($smtp['from_name'] ?? '') ?>">
</div>
</div>
<div class="mb-3">
<label class="form-label">SMTP Username</label>
<input type="text" name="smtp_user" class="form-control" value="<?= htmlspecialchars($smtp['smtp_user'] ?? '') ?>">
</div>
<div class="mb-3">
<label class="form-label">SMTP Password</label>
<input type="password" name="smtp_pass" class="form-control" value="<?= htmlspecialchars($smtp['smtp_pass'] ?? '') ?>">
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">From Email</label>
<input type="email" name="from_email" class="form-control" value="<?= htmlspecialchars($smtp['from_email'] ?? '') ?>">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Reply-To Email</label>
<input type="email" name="reply_to" class="form-control" value="<?= htmlspecialchars($smtp['reply_to'] ?? '') ?>">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">حد الأخطاء قبل التعطيل</label>
<input type="number" name="max_failures" class="form-control" value="<?= htmlspecialchars($smtp['max_failures'] ?? 5) ?>">
</div>
</div>
<button type="submit" class="btn btn-primary">حفظ إعدادات SMTP</button>
</form>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">شعار الجمعية</label>
<input type="file" name="charity_logo" class="form-control" accept="image/*">
<?php if ($charity['charity_logo']): ?>
<div class="mt-2 text-center">
<img src="<?= $charity['charity_logo'] ?>" alt="Logo" style="max-height: 80px; border: 1px solid #ddd; padding: 5px;">
<hr class="my-4">
<h5>اختبار الإرسال</h5>
<form method="POST">
<div class="input-group" style="max-width: 400px;">
<input type="email" name="test_email_addr" class="form-control" placeholder="بريد الوجهة" required>
<button class="btn btn-outline-secondary" type="submit">إرسال تجريبي</button>
</div>
</form>
</div>
<!-- Statuses Settings -->
<div class="tab-pane" id="statuses" role="tabpanel">
<h4 class="mb-4">أنواع حالات البريد</h4>
<form method="POST" class="mb-4 bg-light p-3 rounded">
<input type="hidden" name="add_status" value="1">
<div class="row g-2 align-items-end">
<div class="col-md-5">
<label class="form-label">اسم الحالة</label>
<input type="text" name="status_name" class="form-control" required>
</div>
<div class="col-md-2">
<label class="form-label">اللون</label>
<input type="color" name="status_color" class="form-control form-control-color w-100" value="#0d6efd">
</div>
<div class="col-md-3 text-center">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" name="is_default" id="is_default">
<label class="form-check-label" for="is_default">افتراضية</label>
</div>
<?php endif; ?>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">أيقونة الموقع (Favicon)</label>
<input type="file" name="charity_favicon" class="form-control" accept="image/x-icon,image/png">
<?php if ($charity['charity_favicon']): ?>
<div class="mt-2 text-center">
<img src="<?= $charity['charity_favicon'] ?>" alt="Favicon" style="max-height: 32px;">
</div>
<?php endif; ?>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">إضافة</button>
</div>
</div>
</form>
<div class="table-responsive">
<table class="table align-middle">
<thead>
<tr><th>الاسم</th><th>اللون</th><th>افتراضية</th><th class="text-end">الإجراء</th></tr>
</thead>
<tbody>
<?php foreach ($statuses as $status): ?>
<tr>
<td><span class="badge" style="background-color: <?= $status['color'] ?>;"><?= htmlspecialchars($status['name']) ?></span></td>
<td><code><?= $status['color'] ?></code></td>
<td><?= $status['is_default'] ? '<i class="fas fa-check text-success"></i>' : '' ?></td>
<td class="text-end">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="editStatus(<?= $status['id'] ?>, '<?= htmlspecialchars($status['name'], ENT_QUOTES) ?>', '<?= $status['color'] ?>', <?= $status['is_default'] ?>)"><i class="fas fa-edit"></i></button>
<form method="POST" onsubmit="return confirm('حذف؟');" style="display:inline;"><input type="hidden" name="status_id" value="<?= $status['id'] ?>"><input type="hidden" name="delete_status" value="1"><button type="submit" class="btn btn-sm btn-outline-danger"><i class="fas fa-trash"></i></button></form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<button type="submit" class="btn btn-dark w-100 mt-4">تحديث إعدادات الجمعية</button>
<!-- Email Logs -->
<div class="tab-pane" id="logs" role="tabpanel">
<h4 class="mb-4">سجلات البريد المرسل (آخر 50 عملية)</h4>
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead>
<tr>
<th>الوقت</th>
<th>المستلم</th>
<th>الموضوع</th>
<th>الحالة</th>
<th>الخطأ</th>
</tr>
</thead>
<tbody>
<?php foreach ($email_logs as $log): ?>
<tr>
<td class="small"><?= date('Y-m-d H:i', strtotime($log['created_at'])) ?></td>
<td><?= htmlspecialchars($log['recipient']) ?></td>
<td class="small"><?= htmlspecialchars($log['subject']) ?></td>
<td>
<span class="badge bg-<?= $log['status'] === 'success' ? 'success' : 'danger' ?>">
<?= $log['status'] === 'success' ? 'نجاح' : 'فشل' ?>
</span>
</td>
<td class="small text-danger"><?= htmlspecialchars($log['error_message'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="editStatusModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST">
<div class="modal-header">
<h5 class="modal-title">تعديل نوع الحالة</h5>
<button type="button" class="btn-close ms-0 me-auto" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="update_status" value="1"><input type="hidden" name="status_id" id="edit_status_id">
<div class="mb-3"><label class="form-label">اسم الحالة</label><input type="text" name="status_name" id="edit_status_name" class="form-control" required></div>
<div class="mb-3"><label class="form-label">اللون</label><input type="color" name="status_color" id="edit_status_color" class="form-control form-control-color w-100"></div>
<div class="form-check"><input class="form-check-input" type="checkbox" name="is_default" id="edit_is_default"><label class="form-check-label" for="edit_is_default">افتراضية</label></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
<button type="submit" class="btn btn-primary">حفظ</button>
</div>
</form>
</div>
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>
<script>
function editStatus(id, name, color, isDefault) {
document.getElementById('edit_status_id').value = id;
document.getElementById('edit_status_name').value = name;
document.getElementById('edit_status_color').value = color;
document.getElementById('edit_is_default').checked = isDefault == 1;
new bootstrap.Modal(document.getElementById('editStatusModal')).show();
}
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -0,0 +1,25 @@
-- Migration: Add mailbox statuses table
CREATE TABLE IF NOT EXISTS mailbox_statuses (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
color VARCHAR(20) DEFAULT '#000000',
is_default BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Insert initial statuses if they don't exist
INSERT IGNORE INTO mailbox_statuses (id, name, color, is_default) VALUES
(1, 'received', '#6c757d', TRUE),
(2, 'in_progress', '#0d6efd', FALSE),
(3, 'closed', '#198754', FALSE);
-- Add status_id to mailbox
ALTER TABLE mailbox ADD COLUMN IF NOT EXISTS status_id INT;
-- Update status_id based on existing ENUM values
UPDATE mailbox SET status_id = 1 WHERE status = 'received' AND status_id IS NULL;
UPDATE mailbox SET status_id = 2 WHERE status = 'in_progress' AND status_id IS NULL;
UPDATE mailbox SET status_id = 3 WHERE status = 'closed' AND status_id IS NULL;
-- Set default status_id for any NULLs
UPDATE mailbox SET status_id = 1 WHERE status_id IS NULL;

View File

@ -0,0 +1,2 @@
-- Migration: Add due_date column to mailbox table
ALTER TABLE mailbox ADD COLUMN IF NOT EXISTS due_date DATE NULL;

View File

@ -0,0 +1,18 @@
CREATE TABLE IF NOT EXISTS smtp_settings (
id INT AUTO_INCREMENT PRIMARY KEY,
transport VARCHAR(20) DEFAULT 'smtp',
smtp_host VARCHAR(255),
smtp_port INT DEFAULT 587,
smtp_secure VARCHAR(10) DEFAULT 'tls',
smtp_user VARCHAR(255),
smtp_pass VARCHAR(255),
from_email VARCHAR(255),
from_name VARCHAR(255),
reply_to VARCHAR(255),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Insert default row if not exists
INSERT INTO smtp_settings (id, transport, smtp_host, smtp_port, smtp_secure, from_email, from_name)
SELECT 1, 'smtp', '', 587, 'tls', 'no-reply@localhost', 'App'
WHERE NOT EXISTS (SELECT 1 FROM smtp_settings WHERE id = 1);

View File

@ -0,0 +1,14 @@
-- Create email_logs table
CREATE TABLE IF NOT EXISTS email_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
recipient TEXT NOT NULL,
subject VARCHAR(255),
status ENUM('success', 'failure') NOT NULL,
error_message TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Add consecutive_failures to smtp_settings to track repeated issues
ALTER TABLE smtp_settings ADD COLUMN consecutive_failures INT DEFAULT 0;
ALTER TABLE smtp_settings ADD COLUMN max_failures INT DEFAULT 5;
ALTER TABLE smtp_settings ADD COLUMN is_enabled TINYINT(1) DEFAULT 1;

View File

@ -1,8 +1,48 @@
<?php
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/mail/MailService.php';
$error = '';
$success = '';
$user_id = $_SESSION['user_id'];
// Fetch statuses
$statuses_list = db()->query("SELECT * FROM mailbox_statuses ORDER BY id ASC")->fetchAll();
$default_status_id = db()->query("SELECT id FROM mailbox_statuses WHERE is_default = 1 LIMIT 1")->fetchColumn() ?: 1;
// Function to send assignment notification
function sendAssignmentNotification($assigned_to_id, $ref_no, $subject) {
if (!$assigned_to_id) return;
$stmt = db()->prepare("SELECT full_name, email FROM users WHERE id = ?");
$stmt->execute([$assigned_to_id]);
$user = $stmt->fetch();
if ($user && !empty($user['email'])) {
$to = $user['email'];
$email_subject = "تنبيه: تم تعيين بريد جديد لك (رقم القيد: $ref_no)";
$htmlBody = "
<div dir='rtl' style='font-family: Arial, sans-serif;'>
<h2>مرحباً " . htmlspecialchars($user['full_name']) . "</h2>
<p>لقد تم تعيين مهمة بريد جديد لك في النظام.</p>
<table border='1' cellpadding='10' cellspacing='0' style='border-collapse: collapse;'>
<tr>
<th style='background-color: #f8f9fa;'>رقم القيد</th>
<td>" . htmlspecialchars($ref_no) . "</td>
</tr>
<tr>
<th style='background-color: #f8f9fa;'>الموضوع</th>
<td>" . htmlspecialchars($subject) . "</td>
</tr>
</table>
<p>يرجى الدخول للنظام لمتابعة المهمة.</p>
<br>
<p>هذا تنبيه تلقائي، يرجى عدم الرد.</p>
</div>
";
MailService::sendMail($to, $email_subject, $htmlBody);
}
}
// Handle actions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
@ -10,23 +50,39 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$type = 'inbound';
$ref_no = $_POST['ref_no'] ?? '';
$date_registered = $_POST['date_registered'] ?? date('Y-m-d');
$due_date = !empty($_POST['due_date']) ? $_POST['due_date'] : null;
$sender = $_POST['sender'] ?? '';
$recipient = $_POST['recipient'] ?? '';
$subject = $_POST['subject'] ?? '';
$description = $_POST['description'] ?? '';
$status = $_POST['status'] ?? 'received';
$status_id = $_POST['status_id'] ?? $default_status_id;
$assigned_to = !empty($_POST['assigned_to']) ? $_POST['assigned_to'] : null;
$id = $_POST['id'] ?? 0;
if ($ref_no && $subject) {
try {
if ($action === 'add') {
$stmt = db()->prepare("INSERT INTO mailbox (type, ref_no, date_registered, sender, recipient, subject, description, status, assigned_to, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$type, $ref_no, $date_registered, $sender, $recipient, $subject, $description, $status, $assigned_to, $_SESSION['user_id']]);
$stmt = db()->prepare("INSERT INTO mailbox (type, ref_no, date_registered, due_date, sender, recipient, subject, description, status_id, assigned_to, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$type, $ref_no, $date_registered, $due_date, $sender, $recipient, $subject, $description, $status_id, $assigned_to, $user_id]);
if ($assigned_to) {
sendAssignmentNotification($assigned_to, $ref_no, $subject);
}
$success = 'تمت إضافة البريد بنجاح';
} elseif ($action === 'edit') {
$stmt = db()->prepare("UPDATE mailbox SET ref_no = ?, date_registered = ?, sender = ?, recipient = ?, subject = ?, description = ?, status = ?, assigned_to = ? WHERE id = ? AND type = 'inbound'");
$stmt->execute([$ref_no, $date_registered, $sender, $recipient, $subject, $description, $status, $assigned_to, $id]);
// Get previous assigned_to to check if it changed
$stmt_old = db()->prepare("SELECT assigned_to FROM mailbox WHERE id = ?");
$stmt_old->execute([$id]);
$old_assigned_to = $stmt_old->fetchColumn();
$stmt = db()->prepare("UPDATE mailbox SET ref_no = ?, date_registered = ?, due_date = ?, sender = ?, recipient = ?, subject = ?, description = ?, status_id = ?, assigned_to = ? WHERE id = ? AND type = 'inbound'");
$stmt->execute([$ref_no, $date_registered, $due_date, $sender, $recipient, $subject, $description, $status_id, $assigned_to, $id]);
if ($assigned_to && $assigned_to != $old_assigned_to) {
sendAssignmentNotification($assigned_to, $ref_no, $subject);
}
$success = 'تم تحديث البيانات بنجاح';
}
} catch (PDOException $e) {
@ -50,13 +106,28 @@ if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])
}
$search = $_GET['search'] ?? '';
$query = "SELECT * FROM mailbox WHERE type = 'inbound'";
$my_tasks = isset($_GET['my_tasks']) && $_GET['my_tasks'] == 1;
$query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name
FROM mailbox m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
LEFT JOIN users u ON m.assigned_to = u.id
WHERE m.type = 'inbound'";
$params = [];
if ($search) {
$query .= " AND (ref_no LIKE ? OR sender LIKE ? OR subject LIKE ?)";
$params = ["%$search%", "%$search%", "%$search%"];
$query .= " AND (m.ref_no LIKE ? OR m.sender LIKE ? OR m.subject LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
$query .= " ORDER BY created_at DESC";
if ($my_tasks) {
$query .= " AND m.assigned_to = ?";
$params[] = $user_id;
}
$query .= " ORDER BY m.created_at DESC";
$stmt = db()->prepare($query);
$stmt->execute($params);
$mails = $stmt->fetchAll();
@ -70,6 +141,19 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
$stmt->execute([$_GET['id']]);
$deepLinkData = $stmt->fetch();
}
function getStatusBadgeInList($mail) {
$status_name = $mail['status_name'] ?? 'غير معروف';
$status_color = $mail['status_color'] ?? '#6c757d';
// Translation for default statuses
$display_name = $status_name;
if ($status_name == 'received') $display_name = 'تم الاستلام';
if ($status_name == 'in_progress') $display_name = 'قيد المعالجة';
if ($status_name == 'closed') $display_name = 'مكتمل';
return '<span class="badge" style="background-color: ' . $status_color . ';">' . htmlspecialchars($display_name) . '</span>';
}
?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
@ -95,13 +179,24 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white py-3">
<form class="row g-2">
<form class="row g-3 align-items-center">
<div class="col-md-4">
<input type="text" name="search" class="form-control" placeholder="بحث برقم القيد أو الموضوع أو المرسل..." value="<?= htmlspecialchars($search) ?>">
</div>
<div class="col-auto">
<button type="submit" class="btn btn-secondary">بحث</button>
<div class="col-md-3">
<div class="form-check form-switch mt-1">
<input class="form-check-input" type="checkbox" name="my_tasks" id="myTasksSwitch" value="1" <?= $my_tasks ? 'checked' : '' ?> onchange="this.form.submit()">
<label class="form-check-label fw-bold" for="myTasksSwitch">مهامي فقط</label>
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-secondary px-4">بحث</button>
</div>
<?php if ($search || $my_tasks): ?>
<div class="col-auto">
<a href="inbound.php" class="btn btn-link text-decoration-none">إلغاء التصفية</a>
</div>
<?php endif; ?>
</form>
</div>
<div class="card-body p-0">
@ -111,8 +206,10 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<tr>
<th class="ps-4">رقم القيد</th>
<th>التاريخ</th>
<th>الموعد النهائي</th>
<th>الموضوع</th>
<th>المرسل</th>
<th>المسؤول</th>
<th>الحالة</th>
<th class="pe-4 text-center">الإجراءات</th>
</tr>
@ -122,21 +219,32 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<tr>
<td class="ps-4 fw-bold text-primary"><?= $mail['ref_no'] ?></td>
<td><?= $mail['date_registered'] ?></td>
<td>
<?php if ($mail['due_date']): ?>
<span class="<?= (strtotime($mail['due_date']) < time() && $mail['status_name'] != 'closed') ? 'text-danger fw-bold' : '' ?>">
<?= $mail['due_date'] ?>
<?php if (strtotime($mail['due_date']) < time() && $mail['status_name'] != 'closed'): ?>
<i class="fas fa-exclamation-triangle ms-1"></i>
<?php endif; ?>
</span>
<?php else: ?>
<span class="text-muted">-</span>
<?php endif; ?>
</td>
<td><?= htmlspecialchars($mail['subject']) ?></td>
<td><?= htmlspecialchars($mail['sender']) ?></td>
<td>
<?php if ($mail['status'] === 'received'): ?>
<span class="badge bg-secondary">تم الاستلام</span>
<?php elseif ($mail['status'] === 'in_progress'): ?>
<span class="badge bg-info text-dark">قيد المعالجة</span>
<?php elseif ($mail['status'] === 'closed'): ?>
<span class="badge bg-success">مكتمل</span>
<?php if ($mail['assigned_to_name']): ?>
<span class="text-nowrap"><i class="fas fa-user-tag me-1 text-muted"></i> <?= htmlspecialchars($mail['assigned_to_name']) ?></span>
<?php else: ?>
<span class="text-muted">غير معين</span>
<?php endif; ?>
</td>
<td><?= getStatusBadgeInList($mail) ?></td>
<td class="pe-4 text-center">
<a href="view_mail.php?id=<?= $mail['id'] ?>" class="btn btn-sm btn-outline-info" title="عرض التفاصيل"><i class="fas fa-eye"></i></a>
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="openMailModal('edit', <?= htmlspecialchars(json_encode($mail), ENT_QUOTES, 'UTF-8') ?>)" title="تعديل">
onclick='openMailModal("edit", <?= json_encode($mail) ?>)' title="تعديل">
<i class="fas fa-edit"></i>
</button>
<a href="javascript:void(0)" onclick="confirmDelete(<?= $mail['id'] ?>)" class="btn btn-sm btn-outline-danger" title="حذف"><i class="fas fa-trash"></i></a>
@ -144,7 +252,7 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
</tr>
<?php endforeach; else: ?>
<tr>
<td colspan="6" class="text-center py-4 text-muted">لا يوجد بريد وارد مسجل حالياً</td>
<td colspan="8" class="text-center py-4 text-muted">لا يوجد بريد وارد مسجل حالياً</td>
</tr>
<?php endif; ?>
</tbody>
@ -167,14 +275,18 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<input type="hidden" name="id" id="modalId" value="0">
<div class="row g-3">
<div class="col-md-6">
<div class="col-md-4">
<label class="form-label fw-bold">رقم القيد <span class="text-danger">*</span></label>
<input type="text" name="ref_no" id="modalRefNo" class="form-control" required>
</div>
<div class="col-md-6">
<div class="col-md-4">
<label class="form-label fw-bold">تاريخ التسجيل</label>
<input type="date" name="date_registered" id="modalDateRegistered" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label fw-bold">الموعد النهائي</label>
<input type="date" name="due_date" id="modalDueDate" class="form-control">
</div>
<div class="col-md-6">
<label class="form-label fw-bold">المرسل</label>
<input type="text" name="sender" id="modalSender" class="form-control">
@ -193,10 +305,16 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
</div>
<div class="col-md-6">
<label class="form-label fw-bold">الحالة</label>
<select name="status" id="modalStatus" class="form-select">
<option value="received">تم الاستلام</option>
<option value="in_progress">قيد المعالجة</option>
<option value="closed">مكتمل / مغلق</option>
<select name="status_id" id="modalStatusId" class="form-select">
<?php foreach ($statuses_list as $s): ?>
<?php
$d_name = $s['name'];
if ($d_name == 'received') $d_name = 'تم الاستلام';
if ($d_name == 'in_progress') $d_name = 'قيد المعالجة';
if ($d_name == 'closed') $d_name = 'مكتمل / مغلق';
?>
<option value="<?= $s['id'] ?>"><?= htmlspecialchars($d_name) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
@ -240,11 +358,12 @@ function openMailModal(action, data = null) {
const fields = {
ref_no: document.getElementById('modalRefNo'),
date_registered: document.getElementById('modalDateRegistered'),
due_date: document.getElementById('modalDueDate'),
sender: document.getElementById('modalSender'),
recipient: document.getElementById('modalRecipient'),
subject: document.getElementById('modalSubject'),
description: document.getElementById('modalDescription'),
status: document.getElementById('modalStatus'),
status_id: document.getElementById('modalStatusId'),
assigned_to: document.getElementById('modalAssignedTo')
};
@ -255,7 +374,7 @@ function openMailModal(action, data = null) {
modalId.value = '0';
Object.keys(fields).forEach(key => {
if (key === 'date_registered') fields[key].value = '<?= date('Y-m-d') ?>';
else if (key === 'status') fields[key].value = 'received';
else if (key === 'status_id') fields[key].value = '<?= $default_status_id ?>';
else fields[key].value = '';
});
} else {
@ -277,11 +396,12 @@ document.addEventListener('DOMContentLoaded', function() {
'id' => $_POST['id'] ?? 0,
'ref_no' => $_POST['ref_no'] ?? '',
'date_registered' => $_POST['date_registered'] ?? date('Y-m-d'),
'due_date' => $_POST['due_date'] ?? '',
'sender' => $_POST['sender'] ?? '',
'recipient' => $_POST['recipient'] ?? '',
'subject' => $_POST['subject'] ?? '',
'description' => $_POST['description'] ?? '',
'status' => $_POST['status'] ?? 'received',
'status_id' => $_POST['status_id'] ?? $default_status_id,
'assigned_to' => $_POST['assigned_to'] ?? ''
]) ?>;
openMailModal('<?= $_POST['action'] ?>', errorData);

View File

@ -44,16 +44,16 @@ if (isLoggedIn()) {
<link rel="icon" type="image/x-icon" href="<?= $charity_favicon ?>?v=<?= time() ?>">
<?php endif; ?>
<!-- Bootstrap 5 RTL CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.rtl.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.rtl.min.css?v=<?php echo time(); ?>">
<!-- Google Fonts: Cairo -->
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&display=swap" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css?v=<?php echo time(); ?>">
<!-- JS Libraries (Loaded in head to support inline onclick handlers) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js?v=<?php echo time(); ?>"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://cdn.ckeditor.com/ckeditor5/36.0.1/classic/ckeditor.js"></script>
<script src="https://cdn.ckeditor.com/ckeditor5/36.0.1/classic/ckeditor.js?v=<?php echo time(); ?>"></script>
<style>
body {
@ -113,7 +113,7 @@ if (isLoggedIn()) {
<div class="row">
<?php if (isLoggedIn()): ?>
<!-- Sidebar -->
<nav class="col-md-3 col-lg-2 d-md-block sidebar collapse">
<nav class="col-md-3 col-lg-2 d-md-block sidebar">
<div class="position-sticky">
<div class="text-center mb-4">
<?php if ($charity_logo): ?>
@ -131,12 +131,18 @@ if (isLoggedIn()) {
</div>
<?php endif; ?>
<div class="small fw-bold"><?= htmlspecialchars($current_user['full_name'] ?? $_SESSION['username']) ?></div>
<div class="small text-muted"><?= $_SESSION['user_role'] === 'admin' ? 'مدير النظام' : 'موظف' ?></div>
<div class="small text-muted">
<?php
if ($_SESSION['user_role'] === 'admin') echo 'مدير النظام';
elseif ($_SESSION['user_role'] === 'clerk') echo 'كاتب';
else echo 'موظف';
?>
</div>
</div>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'index.php' ? 'active' : '' ?>" href="index.php">
<a class="nav-link <?= (basename($_SERVER['PHP_SELF']) == 'index.php' || basename($_SERVER['PHP_SELF']) == 'user_dashboard.php') ? 'active' : '' ?>" href="index.php">
<i class="fas fa-home me-2"></i> لوحة التحكم
</a>
</li>
@ -151,6 +157,11 @@ if (isLoggedIn()) {
</a>
</li>
<?php if (isAdmin()): ?>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'overdue_report.php' ? 'active' : '' ?>" href="overdue_report.php">
<i class="fas fa-chart-line me-2"></i> تقرير التأخير
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'users.php' ? 'active' : '' ?>" href="users.php">
<i class="fas fa-users me-2"></i> إدارة المستخدمين
@ -177,4 +188,4 @@ if (isLoggedIn()) {
</nav>
<?php endif; ?>
<main class="<?= isLoggedIn() ? 'col-md-9 ms-sm-auto col-lg-10' : 'col-12' ?> px-md-4 py-4">
<nav class="navbar navbar-expand-md navbar-light bg-white d-md-none border-bottom mb-3"><div class="container-fluid"><span class="navbar-brand">القائمة</span><button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".sidebar" aria-controls="sidebar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button></div></nav><main class="<?= isLoggedIn() ? 'col-md-9 ms-sm-auto col-lg-10' : 'col-12' ?> px-md-4 py-4">

174
index.php
View File

@ -1,32 +1,101 @@
<?php
require_once __DIR__ . '/includes/header.php';
if (!isAdmin()) { redirect('user_dashboard.php'); }
$user_id = $_SESSION['user_id'];
$is_admin = isAdmin();
// Stats
$total_inbound = db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'inbound'")->fetchColumn();
$total_outbound = db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'outbound'")->fetchColumn();
$in_progress = db()->query("SELECT COUNT(*) FROM mailbox WHERE status = 'in_progress'")->fetchColumn();
$recent_mail = db()->query("SELECT * FROM mailbox ORDER BY created_at DESC LIMIT 5")->fetchAll();
function getStatusBadge($status) {
switch ($status) {
case 'received': return '<span class="badge bg-secondary">تم الاستلام</span>';
case 'in_progress': return '<span class="badge bg-info text-dark">قيد المعالجة</span>';
case 'closed': return '<span class="badge bg-success">مكتمل</span>';
default: return '<span class="badge bg-dark">غير معروف</span>';
// Fetch statuses for badge and count
$statuses_data = db()->query("SELECT * FROM mailbox_statuses")->fetchAll(PDO::FETCH_UNIQUE);
// For the "In Progress" stat card, we might need a specific status or just a sum of non-closed statuses.
$in_progress_id = null;
foreach ($statuses_data as $id => $s) {
if ($s['name'] == 'in_progress') {
$in_progress_id = $id;
break;
}
}
$in_progress_count = 0;
if ($in_progress_id) {
$stmt = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE status_id = ?");
$stmt->execute([$in_progress_id]);
$in_progress_count = $stmt->fetchColumn();
}
// My Assignments
$my_assignments = db()->prepare("SELECT m.*, s.name as status_name, s.color as status_color
FROM mailbox m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
WHERE m.assigned_to = ?
ORDER BY m.created_at DESC LIMIT 5");
$my_assignments->execute([$user_id]);
$my_assignments = $my_assignments->fetchAll();
// Recent Mail (Global for Admin/Clerk, otherwise limited)
$recent_mail_query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name
FROM mailbox m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
LEFT JOIN users u ON m.assigned_to = u.id";
if (!$is_admin && $_SESSION['user_role'] !== 'clerk') {
$recent_mail_query .= " WHERE m.assigned_to = ? OR m.created_by = ?";
$recent_stmt = db()->prepare($recent_mail_query . " ORDER BY m.created_at DESC LIMIT 10");
$recent_stmt->execute([$user_id, $user_id]);
} else {
$recent_stmt = db()->prepare($recent_mail_query . " ORDER BY m.created_at DESC LIMIT 10");
$recent_stmt->execute();
}
$recent_mail = $recent_stmt->fetchAll();
function getStatusBadge($mail) {
$status_name = $mail['status_name'] ?? 'غير معروف';
$status_color = $mail['status_color'] ?? '#6c757d';
// Translation for default statuses
$display_name = $status_name;
if ($status_name == 'received') $display_name = 'تم الاستلام';
if ($status_name == 'in_progress') $display_name = 'قيد المعالجة';
if ($status_name == 'closed') $display_name = 'مكتمل';
return '<span class="badge" style="background-color: ' . $status_color . ';">' . htmlspecialchars($display_name) . '</span>';
}
?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">لوحة التحكم</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<?php if (isAdmin()): ?><a href="charity-settings.php" class="btn btn-sm btn-outline-dark"><i class="fas fa-cog me-1"></i> الإعدادات</a><?php endif; ?>
<a href="inbound.php?action=add" class="btn btn-sm btn-outline-primary">إضافة بريد وارد</a>
<a href="outbound.php?action=add" class="btn btn-sm btn-outline-secondary">إضافة بريد صادر</a>
<a href="outbound.php" class="btn btn-sm btn-outline-secondary">إضافة بريد صادر</a>
</div>
</div>
</div>
<!-- Overdue Alert -->
<?php
$overdue_count = db()->query("SELECT COUNT(*) FROM mailbox WHERE due_date < CURDATE() AND status != 'closed'")->fetchColumn();
if ($overdue_count > 0):
?>
<div class="row mb-4">
<div class="col-12">
<div class="alert alert-danger shadow-sm border-0 d-flex align-items-center justify-content-between mb-0">
<div>
<i class="fas fa-exclamation-triangle fs-4 me-3"></i>
<span class="fw-bold">هناك <?= $overdue_count ?> مهام متأخرة تتطلب انتباهك!</span>
</div>
<?php if (isAdmin()): ?>
<a href="overdue_report.php" class="btn btn-danger btn-sm">عرض التقرير</a>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<!-- Stats Cards -->
<div class="row g-4 mb-4">
<div class="col-md-3">
@ -60,7 +129,7 @@ function getStatusBadge($status) {
<div class="d-flex align-items-center justify-content-between">
<div>
<h6 class="text-muted mb-1">قيد المعالجة</h6>
<h3 class="fw-bold mb-0"><?= $in_progress ?></h3>
<h3 class="fw-bold mb-0"><?= $in_progress_count ?></h3>
</div>
<div class="bg-info bg-opacity-10 p-3 rounded-circle">
<i class="fas fa-clock text-info fs-4"></i>
@ -83,12 +152,61 @@ function getStatusBadge($status) {
</div>
</div>
<?php if (!empty($my_assignments)): ?>
<!-- My Assignments Section -->
<div class="card shadow-sm border-0 mb-4 bg-primary bg-opacity-10 border-top border-primary border-3">
<div class="card-header bg-transparent py-3 border-0">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold text-primary"><i class="fas fa-tasks me-2"></i> مهامي الحالية</h5>
<span class="badge bg-primary rounded-pill"><?= count($my_assignments) ?></span>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<tbody>
<?php foreach ($my_assignments as $mail): ?>
<tr style="cursor: pointer;" onclick="window.location='view_mail.php?id=<?= $mail['id'] ?>'">
<td class="ps-4" width="120">
<small class="text-muted d-block">رقم القيد</small>
<span class="fw-bold text-primary"><?= $mail['ref_no'] ?></span>
</td>
<td>
<small class="text-muted d-block">الموضوع</small>
<span class="fw-bold"><?= htmlspecialchars($mail['subject']) ?></span>
</td>
<td>
<small class="text-muted d-block">الموعد النهائي</small>
<?php if ($mail['due_date']): ?>
<span class="<?= (strtotime($mail['due_date']) < time() && $mail['status_name'] != 'closed') ? 'text-danger fw-bold' : '' ?>">
<?= $mail['due_date'] ?>
</span>
<?php else: ?>
<span class="text-muted">-</span>
<?php endif; ?>
</td>
<td class="text-center">
<small class="text-muted d-block mb-1">الحالة</small>
<?= getStatusBadge($mail) ?>
</td>
<td class="pe-4 text-end">
<i class="fas fa-chevron-left text-primary"></i>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php endif; ?>
<!-- Recent Mail -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white py-3">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold">البريد المضاف حديثاً</h5>
<a href="inbound.php" class="btn btn-sm btn-link">عرض الكل</a>
<h5 class="mb-0 fw-bold"><?= $is_admin ? 'آخر المراسلات المسجلة' : 'آخر المراسلات' ?></h5>
<a href="inbound.php" class="btn btn-sm btn-link text-decoration-none">عرض الكل</a>
</div>
</div>
<div class="card-body p-0">
@ -99,7 +217,9 @@ function getStatusBadge($status) {
<th class="ps-4">رقم القيد</th>
<th>النوع</th>
<th>الموضوع</th>
<th>الموعد النهائي</th>
<th>المرسل/المستلم</th>
<th>المسؤول</th>
<th>الحالة</th>
<th class="pe-4 text-center">التاريخ</th>
</tr>
@ -109,16 +229,38 @@ function getStatusBadge($status) {
<?php foreach ($recent_mail as $mail): ?>
<tr style="cursor: pointer;" onclick="window.location='view_mail.php?id=<?= $mail['id'] ?>'">
<td class="ps-4 fw-bold text-primary"><?= $mail['ref_no'] ?></td>
<td><?= $mail['type'] == 'inbound' ? 'وارد' : 'صادر' ?></td>
<td>
<?php if ($mail['type'] == 'inbound'): ?>
<span class="text-primary"><i class="fas fa-arrow-down me-1"></i> وارد</span>
<?php else: ?>
<span class="text-success"><i class="fas fa-arrow-up me-1"></i> صادر</span>
<?php endif; ?>
</td>
<td><?= htmlspecialchars($mail['subject']) ?></td>
<td>
<?php if ($mail['due_date']): ?>
<small class="<?= (strtotime($mail['due_date']) < time() && $mail['status_name'] != 'closed') ? 'text-danger fw-bold' : 'text-muted' ?>">
<?= $mail['due_date'] ?>
</small>
<?php else: ?>
<small class="text-muted">-</small>
<?php endif; ?>
</td>
<td><?= htmlspecialchars($mail['sender'] ?: $mail['recipient']) ?></td>
<td><?= getStatusBadge($mail['status']) ?></td>
<td>
<?php if ($mail['assigned_to_name']): ?>
<small><i class="fas fa-user-tag me-1 text-muted"></i> <?= htmlspecialchars($mail['assigned_to_name']) ?></small>
<?php else: ?>
<small class="text-muted">غير معين</small>
<?php endif; ?>
</td>
<td><?= getStatusBadge($mail) ?></td>
<td class="pe-4 text-center"><?= date('Y-m-d', strtotime($mail['date_registered'])) ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="text-center py-4 text-muted">لا يوجد بريد مسجل حالياً</td>
<td colspan="8" class="text-center py-4 text-muted">لا يوجد بريد مسجل حالياً</td>
</tr>
<?php endif; ?>
</tbody>
@ -127,4 +269,4 @@ function getStatusBadge($status) {
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -14,30 +14,37 @@ class MailService
{
$cfg = self::loadConfig();
// Check if enabled (db-backed settings)
if (isset($cfg['is_enabled']) && !$cfg['is_enabled']) {
self::logEmail($to, $subject, 'failure', 'SMTP is currently disabled due to repeated failures.');
return [ 'success' => false, 'error' => 'SMTP is disabled' ];
}
$autoload = __DIR__ . '/../vendor/autoload.php';
if (file_exists($autoload)) {
require_once $autoload;
}
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
@require_once 'libphp-phpmailer/autoload.php';
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
@require_once 'libphp-phpmailer/src/Exception.php';
@require_once 'libphp-phpmailer/src/SMTP.php';
@require_once 'libphp-phpmailer/src/PHPMailer.php';
}
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
@require_once 'PHPMailer/src/Exception.php';
@require_once 'PHPMailer/src/SMTP.php';
@require_once 'PHPMailer/src/PHPMailer.php';
}
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
@require_once 'PHPMailer/Exception.php';
@require_once 'PHPMailer/SMTP.php';
@require_once 'PHPMailer/PHPMailer.php';
}
}
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
self::logEmail($to, $subject, 'failure', 'PHPMailer not available');
return [ 'success' => false, 'error' => 'PHPMailer not available' ];
}
@ -76,6 +83,7 @@ class MailService
if (filter_var($addr, FILTER_VALIDATE_EMAIL)) { $mail->addAddress($addr); $added++; }
}
if ($added === 0) {
self::logEmail($to, $subject, 'failure', 'No recipients defined');
return [ 'success' => false, 'error' => 'No recipients defined (set MAIL_TO or pass $to)' ];
}
@ -94,142 +102,86 @@ class MailService
$mail->Body = $htmlBody;
$mail->AltBody = $textBody ?? strip_tags($htmlBody);
$ok = $mail->send();
self::logEmail($to, $subject, 'success');
self::resetFailures();
return [ 'success' => $ok ];
} catch (\Throwable $e) {
return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ];
$error = $e->getMessage();
self::logEmail($to, $subject, 'failure', $error);
self::incrementFailures();
return [ 'success' => false, 'error' => 'PHPMailer error: ' . $error ];
}
}
private static function loadConfig(): array
{
$configPath = __DIR__ . '/config.php';
if (!file_exists($configPath)) {
throw new \RuntimeException('Mail config not found. Copy mail/config.sample.php to mail/config.php and fill in credentials.');
throw new \RuntimeException('Mail config not found.');
}
$cfg = require $configPath;
if (!is_array($cfg)) {
throw new \RuntimeException('Invalid mail config format: expected array');
throw new \RuntimeException('Invalid mail config format.');
}
// Try to load extra from DB
try {
$stmt = db()->query("SELECT * FROM smtp_settings LIMIT 1");
$dbCfg = $stmt->fetch();
if ($dbCfg) {
// Merge DB settings if not set in config or env
foreach ($dbCfg as $key => $val) {
if (!isset($cfg[$key])) $cfg[$key] = $val;
}
// Specifically override enablement
$cfg['is_enabled'] = (bool)$dbCfg['is_enabled'];
$cfg['max_failures'] = (int)$dbCfg['max_failures'];
}
} catch (\Exception $e) {}
return $cfg;
}
private static function logEmail($to, $subject, $status, $error = null)
{
try {
if (is_array($to)) $to = implode(', ', $to);
$stmt = db()->prepare("INSERT INTO email_logs (recipient, subject, status, error_message) VALUES (?, ?, ?, ?)");
$stmt->execute([$to, $subject, $status, $error]);
} catch (\Exception $e) {}
}
private static function incrementFailures()
{
try {
db()->query("UPDATE smtp_settings SET consecutive_failures = consecutive_failures + 1 WHERE id = 1");
// Check if threshold reached
$stmt = db()->query("SELECT consecutive_failures, max_failures FROM smtp_settings WHERE id = 1");
$res = $stmt->fetch();
if ($res && $res['consecutive_failures'] >= $res['max_failures']) {
db()->query("UPDATE smtp_settings SET is_enabled = 0 WHERE id = 1");
}
} catch (\Exception $e) {}
}
private static function resetFailures()
{
try {
db()->query("UPDATE smtp_settings SET consecutive_failures = 0 WHERE id = 1");
} catch (\Exception $e) {}
}
// Send a contact message
// $to can be: a single email string, a comma-separated list, an array of emails, or null (fallback to MAIL_TO/MAIL_FROM)
public static function sendContactMessage(string $name, string $email, string $message, $to = null, string $subject = 'New contact form')
{
$cfg = self::loadConfig();
// Try Composer autoload if available (for PHPMailer)
$autoload = __DIR__ . '/../vendor/autoload.php';
if (file_exists($autoload)) {
require_once $autoload;
}
// Fallback to system-wide PHPMailer (installed via apt: libphp-phpmailer)
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
// Debian/Ubuntu package layout (libphp-phpmailer)
@require_once 'libphp-phpmailer/autoload.php';
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
@require_once 'libphp-phpmailer/src/Exception.php';
@require_once 'libphp-phpmailer/src/SMTP.php';
@require_once 'libphp-phpmailer/src/PHPMailer.php';
}
// Alternative layout (older PHPMailer package names)
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
@require_once 'PHPMailer/src/Exception.php';
@require_once 'PHPMailer/src/SMTP.php';
@require_once 'PHPMailer/src/PHPMailer.php';
}
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
@require_once 'PHPMailer/Exception.php';
@require_once 'PHPMailer/SMTP.php';
@require_once 'PHPMailer/PHPMailer.php';
}
}
$transport = $cfg['transport'] ?? 'smtp';
if ($transport === 'smtp' && class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
return self::sendViaPHPMailer($cfg, $name, $email, $message, $to, $subject);
}
// Fallback: attempt native mail() — works only if MTA is configured on the VM
return self::sendViaNativeMail($cfg, $name, $email, $message, $to, $subject);
}
private static function sendViaPHPMailer(array $cfg, string $name, string $email, string $body, $to, string $subject)
{
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = $cfg['smtp_host'] ?? '';
$mail->Port = (int)($cfg['smtp_port'] ?? 587);
$secure = $cfg['smtp_secure'] ?? 'tls';
if ($secure === 'ssl') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS;
elseif ($secure === 'tls') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
else $mail->SMTPSecure = false;
$mail->SMTPAuth = true;
$mail->Username = $cfg['smtp_user'] ?? '';
$mail->Password = $cfg['smtp_pass'] ?? '';
$fromEmail = $cfg['from_email'] ?? 'no-reply@localhost';
$fromName = $cfg['from_name'] ?? 'App';
$mail->setFrom($fromEmail, $fromName);
// Use Reply-To for the user's email to avoid spoofing From
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
$mail->addReplyTo($email, $name ?: $email);
}
if (!empty($cfg['reply_to'])) {
$mail->addReplyTo($cfg['reply_to']);
}
// Destination: prefer dynamic recipients ($to), fallback to MAIL_TO; no silent FROM fallback
$toList = [];
if ($to) {
if (is_string($to)) {
// allow comma-separated list
$toList = array_map('trim', explode(',', $to));
} elseif (is_array($to)) {
$toList = $to;
}
} elseif (!empty(getenv('MAIL_TO'))) {
$toList = array_map('trim', explode(',', getenv('MAIL_TO')));
}
$added = 0;
foreach ($toList as $addr) {
if (filter_var($addr, FILTER_VALIDATE_EMAIL)) {
$mail->addAddress($addr);
$added++;
}
}
if ($added === 0) {
return [ 'success' => false, 'error' => 'No recipients defined (set MAIL_TO or pass $to)' ];
}
// DKIM (optional)
if (!empty($cfg['dkim_domain']) && !empty($cfg['dkim_selector']) && !empty($cfg['dkim_private_key_path'])) {
$mail->DKIM_domain = $cfg['dkim_domain'];
$mail->DKIM_selector = $cfg['dkim_selector'];
$mail->DKIM_private = $cfg['dkim_private_key_path'];
}
$mail->isHTML(true);
$mail->Subject = $subject;
$safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$safeEmail = htmlspecialchars($email, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$safeBody = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
$mail->Body = "<p><strong>Name:</strong> {$safeName}</p><p><strong>Email:</strong> {$safeEmail}</p><hr>{$safeBody}";
$mail->AltBody = "Name: {$name}\nEmail: {$email}\n\n{$body}";
$ok = $mail->send();
return [ 'success' => $ok ];
} catch (\Throwable $e) {
return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ];
}
}
private static function sendViaNativeMail(array $cfg, string $name, string $email, string $body, $to, string $subject)
{
$opts = ['reply_to' => $email];
$html = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
return self::sendMail($to, $subject, $html, $body, $opts);
// For simplicity, let's just use sendMail for everything
$safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$safeEmail = htmlspecialchars($email, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$safeBody = nl2br(htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
$html = "<p><strong>Name:</strong> {$safeName}</p><p><strong>Email:</strong> {$safeEmail}</p><hr>{$safeBody}";
return self::sendMail($to, $subject, $html, $message, ['reply_to' => $email]);
}
}

View File

@ -1,76 +1,71 @@
<?php
// Mail configuration sourced from environment variables.
// No secrets are stored here; the file just maps env -> config array for MailService.
// Mail configuration sourced from environment variables or database.
function env_val(string $key, $default = null) {
$v = getenv($key);
return ($v === false || $v === null || $v === '') ? $default : $v;
if (!function_exists('env_val')) {
function env_val(string $key, $default = null) {
$v = getenv($key);
return ($v === false || $v === null || $v === '') ? $default : $v;
}
}
// Fallback: if critical vars are missing from process env, try to parse executor/.env
// This helps in web/Apache contexts where .env is not exported.
// Supports simple KEY=VALUE lines; ignores quotes and comments.
function load_dotenv_if_needed(array $keys): void {
$missing = array_filter($keys, fn($k) => getenv($k) === false || getenv($k) === '');
if (empty($missing)) return;
static $loaded = false;
if ($loaded) return;
$envPath = realpath(__DIR__ . '/../../.env'); // executor/.env
if ($envPath && is_readable($envPath)) {
$lines = @file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
foreach ($lines as $line) {
if ($line[0] === '#' || trim($line) === '') continue;
if (!str_contains($line, '=')) continue;
[$k, $v] = array_map('trim', explode('=', $line, 2));
// Strip potential surrounding quotes
$v = trim($v, "\"' ");
// Do not override existing env
if ($k !== '' && (getenv($k) === false || getenv($k) === '')) {
putenv("{$k}={$v}");
if (!function_exists('load_dotenv_if_needed')) {
function load_dotenv_if_needed(array $keys): void {
$missing = array_filter($keys, fn($k) => getenv($k) === false || getenv($k) === '');
if (empty($missing)) return;
static $loaded = false;
if ($loaded) return;
$envPath = realpath(__DIR__ . '/../../.env'); // executor/.env
if ($envPath && is_readable($envPath)) {
$lines = @file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
foreach ($lines as $line) {
if ($line[0] === '#' || trim($line) === '') continue;
if (!str_contains($line, '=')) continue;
[$k, $v] = array_map('trim', explode('=', $line, 2));
$v = trim($v, "' ");
if ($k !== '' && (getenv($k) === false || getenv($k) === '')) {
putenv("{$k}={$v}");
}
}
$loaded = true;
}
$loaded = true;
}
}
load_dotenv_if_needed([
'MAIL_TRANSPORT','SMTP_HOST','SMTP_PORT','SMTP_SECURE','SMTP_USER','SMTP_PASS',
'MAIL_FROM','MAIL_FROM_NAME','MAIL_REPLY_TO','MAIL_TO',
'DKIM_DOMAIN','DKIM_SELECTOR','DKIM_PRIVATE_KEY_PATH'
'MAIL_FROM','MAIL_FROM_NAME','MAIL_REPLY_TO','MAIL_TO'
]);
$transport = env_val('MAIL_TRANSPORT', 'smtp');
$smtp_host = env_val('SMTP_HOST');
$smtp_port = (int) env_val('SMTP_PORT', 587);
$smtp_secure = env_val('SMTP_SECURE', 'tls'); // tls | ssl | null
$smtp_user = env_val('SMTP_USER');
$smtp_pass = env_val('SMTP_PASS');
// Fetch from DB if available
$db_settings = [];
try {
require_once __DIR__ . '/../db/config.php';
$stmt = db()->query("SELECT * FROM smtp_settings WHERE id = 1");
$db_settings = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
} catch (\Throwable $e) {
// DB not ready or table missing
}
$from_email = env_val('MAIL_FROM', 'no-reply@localhost');
$from_name = env_val('MAIL_FROM_NAME', 'App');
$reply_to = env_val('MAIL_REPLY_TO');
$transport = env_val('MAIL_TRANSPORT', $db_settings['transport'] ?? 'smtp');
$smtp_host = env_val('SMTP_HOST', $db_settings['smtp_host'] ?? '');
$smtp_port = (int) env_val('SMTP_PORT', $db_settings['smtp_port'] ?? 587);
$smtp_secure = env_val('SMTP_SECURE', $db_settings['smtp_secure'] ?? 'tls');
$smtp_user = env_val('SMTP_USER', $db_settings['smtp_user'] ?? '');
$smtp_pass = env_val('SMTP_PASS', $db_settings['smtp_pass'] ?? '');
$dkim_domain = env_val('DKIM_DOMAIN');
$dkim_selector = env_val('DKIM_SELECTOR');
$dkim_private_key_path = env_val('DKIM_PRIVATE_KEY_PATH');
$from_email = env_val('MAIL_FROM', $db_settings['from_email'] ?? 'no-reply@localhost');
$from_name = env_val('MAIL_FROM_NAME', $db_settings['from_name'] ?? 'App');
$reply_to = env_val('MAIL_REPLY_TO', $db_settings['reply_to'] ?? '');
return [
'transport' => $transport,
// SMTP
'smtp_host' => $smtp_host,
'smtp_port' => $smtp_port,
'smtp_secure' => $smtp_secure,
'smtp_user' => $smtp_user,
'smtp_pass' => $smtp_pass,
// From / Reply-To
'from_email' => $from_email,
'from_name' => $from_name,
'reply_to' => $reply_to,
// DKIM (optional)
'dkim_domain' => $dkim_domain,
'dkim_selector' => $dkim_selector,
'dkim_private_key_path' => $dkim_private_key_path,
];
];

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/mail/MailService.php';
// Safe truncation helper
if (!function_exists('truncate_text')) {
@ -15,6 +16,45 @@ if (!function_exists('truncate_text')) {
$error = '';
$success = '';
$user_id = $_SESSION['user_id'];
// Fetch statuses
$statuses_list = db()->query("SELECT * FROM mailbox_statuses ORDER BY id ASC")->fetchAll();
$default_status_id = db()->query("SELECT id FROM mailbox_statuses WHERE is_default = 1 LIMIT 1")->fetchColumn() ?: 1;
// Function to send assignment notification
function sendAssignmentNotification($assigned_to_id, $ref_no, $subject) {
if (!$assigned_to_id) return;
$stmt = db()->prepare("SELECT full_name, email FROM users WHERE id = ?");
$stmt->execute([$assigned_to_id]);
$user = $stmt->fetch();
if ($user && !empty($user['email'])) {
$to = $user['email'];
$email_subject = "تنبيه: تم تعيين بريد جديد لك (رقم القيد: $ref_no)";
$htmlBody = "
<div dir='rtl' style='font-family: Arial, sans-serif;'>
<h2>مرحباً " . htmlspecialchars($user['full_name']) . "</h2>
<p>لقد تم تعيين مهمة بريد جديد لك في النظام.</p>
<table border='1' cellpadding='10' cellspacing='0' style='border-collapse: collapse;'>
<tr>
<th style='background-color: #f8f9fa;'>رقم القيد</th>
<td>" . htmlspecialchars($ref_no) . "</td>
</tr>
<tr>
<th style='background-color: #f8f9fa;'>الموضوع</th>
<td>" . htmlspecialchars($subject) . "</td>
</tr>
</table>
<p>يرجى الدخول للنظام لمتابعة المهمة.</p>
<br>
<p>هذا تنبيه تلقائي، يرجى عدم الرد.</p>
</div>
";
MailService::sendMail($to, $email_subject, $htmlBody);
}
}
// Handle actions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
@ -22,11 +62,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$type = 'outbound';
$ref_no = $_POST['ref_no'] ?? '';
$date_registered = $_POST['date_registered'] ?? date('Y-m-d');
$due_date = !empty($_POST['due_date']) ? $_POST['due_date'] : null;
$sender = $_POST['sender'] ?? '';
$recipient = $_POST['recipient'] ?? '';
$subject = $_POST['subject'] ?? '';
$description = $_POST['description'] ?? '';
$status = $_POST['status'] ?? 'received';
$status_id = $_POST['status_id'] ?? $default_status_id;
$assigned_to = !empty($_POST['assigned_to']) ? $_POST['assigned_to'] : null;
$id = $_POST['id'] ?? 0;
@ -34,14 +75,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
db()->beginTransaction();
if ($action === 'add') {
$stmt = db()->prepare("INSERT INTO mailbox (type, ref_no, date_registered, sender, recipient, subject, description, status, assigned_to, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$type, $ref_no, $date_registered, $sender, $recipient, $subject, $description, $status, $assigned_to, $_SESSION['user_id']]);
$stmt = db()->prepare("INSERT INTO mailbox (type, ref_no, date_registered, due_date, sender, recipient, subject, description, status_id, assigned_to, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$type, $ref_no, $date_registered, $due_date, $sender, $recipient, $subject, $description, $status_id, $assigned_to, $user_id]);
$mail_id = db()->lastInsertId();
if ($assigned_to) {
sendAssignmentNotification($assigned_to, $ref_no, $subject);
}
$success = 'تمت إضافة البريد الصادر بنجاح';
} elseif ($action === 'edit') {
$mail_id = $id;
$stmt = db()->prepare("UPDATE mailbox SET ref_no = ?, date_registered = ?, sender = ?, recipient = ?, subject = ?, description = ?, status = ?, assigned_to = ? WHERE id = ? AND type = 'outbound'");
$stmt->execute([$ref_no, $date_registered, $sender, $recipient, $subject, $description, $status, $assigned_to, $mail_id]);
// Get previous assigned_to to check if it changed
$stmt_old = db()->prepare("SELECT assigned_to FROM mailbox WHERE id = ?");
$stmt_old->execute([$id]);
$old_assigned_to = $stmt_old->fetchColumn();
$stmt = db()->prepare("UPDATE mailbox SET ref_no = ?, date_registered = ?, due_date = ?, sender = ?, recipient = ?, subject = ?, description = ?, status_id = ?, assigned_to = ? WHERE id = ? AND type = 'outbound'");
$stmt->execute([$ref_no, $date_registered, $due_date, $sender, $recipient, $subject, $description, $status_id, $assigned_to, $mail_id]);
if ($assigned_to && $assigned_to != $old_assigned_to) {
sendAssignmentNotification($assigned_to, $ref_no, $subject);
}
$success = 'تم تحديث البيانات بنجاح';
}
@ -55,7 +112,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file_name = time() . '_' . basename($name);
$target_path = $upload_dir . $file_name;
if (move_uploaded_file($_FILES['attachments']['tmp_name'][$key], $target_path)) {
// Default display_name to original name for multiple uploads
$stmt = db()->prepare("INSERT INTO attachments (mail_id, display_name, file_path, file_name, file_size) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$mail_id, $name, $target_path, $name, $_FILES['attachments']['size'][$key]]);
}
@ -86,13 +142,28 @@ if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])
}
$search = $_GET['search'] ?? '';
$query = "SELECT * FROM mailbox WHERE type = 'outbound'";
$my_tasks = isset($_GET['my_tasks']) && $_GET['my_tasks'] == 1;
$query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name
FROM mailbox m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
LEFT JOIN users u ON m.assigned_to = u.id
WHERE m.type = 'outbound'";
$params = [];
if ($search) {
$query .= " AND (ref_no LIKE ? OR recipient LIKE ? OR subject LIKE ?)";
$params = ["%$search%", "%$search%", "%$search%"];
$query .= " AND (m.ref_no LIKE ? OR m.recipient LIKE ? OR m.subject LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
$query .= " ORDER BY created_at DESC";
if ($my_tasks) {
$query .= " AND m.assigned_to = ?";
$params[] = $user_id;
}
$query .= " ORDER BY m.created_at DESC";
$stmt = db()->prepare($query);
$stmt->execute($params);
$mails = $stmt->fetchAll();
@ -106,6 +177,19 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
$stmt->execute([$_GET['id']]);
$deepLinkData = $stmt->fetch();
}
function getStatusBadgeInList($mail) {
$status_name = $mail['status_name'] ?? 'غير معروف';
$status_color = $mail['status_color'] ?? '#6c757d';
// Translation for default statuses
$display_name = $status_name;
if ($status_name == 'received') $display_name = 'تم الاستلام';
if ($status_name == 'in_progress') $display_name = 'قيد المعالجة';
if ($status_name == 'closed') $display_name = 'مكتمل';
return '<span class="badge" style="background-color: ' . $status_color . ';">' . htmlspecialchars($display_name) . '</span>';
}
?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
@ -131,13 +215,24 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white py-3">
<form class="row g-2">
<form class="row g-3 align-items-center">
<div class="col-md-4">
<input type="text" name="search" class="form-control" placeholder="بحث برقم القيد أو الموضوع أو المستلم..." value="<?= htmlspecialchars($search) ?>">
</div>
<div class="col-auto">
<button type="submit" class="btn btn-secondary">بحث</button>
<div class="col-md-3">
<div class="form-check form-switch mt-1">
<input class="form-check-input" type="checkbox" name="my_tasks" id="myTasksSwitch" value="1" <?= $my_tasks ? 'checked' : '' ?> onchange="this.form.submit()">
<label class="form-check-label fw-bold" for="myTasksSwitch">مهامي فقط</label>
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-secondary px-4">بحث</button>
</div>
<?php if ($search || $my_tasks): ?>
<div class="col-auto">
<a href="outbound.php" class="btn btn-link text-decoration-none">إلغاء التصفية</a>
</div>
<?php endif; ?>
</form>
</div>
<div class="card-body p-0">
@ -147,8 +242,10 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<tr>
<th class="ps-4">رقم القيد</th>
<th>التاريخ</th>
<th>الموعد النهائي</th>
<th>الموضوع</th>
<th>المستلم الخارجى</th>
<th>المستلم</th>
<th>المسؤول</th>
<th>الحالة</th>
<th class="pe-4 text-center">الإجراءات</th>
</tr>
@ -158,21 +255,32 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<tr>
<td class="ps-4 fw-bold text-primary"><?= $mail['ref_no'] ?></td>
<td><?= $mail['date_registered'] ?></td>
<td><?= truncate_text($mail['subject'], 100) ?></td>
<td><?= htmlspecialchars($mail['recipient']) ?></td>
<td>
<?php if ($mail['status'] === 'received'): ?>
<span class="badge bg-secondary">تم الاستلام</span>
<?php elseif ($mail['status'] === 'in_progress'): ?>
<span class="badge bg-info text-dark">قيد المعالجة</span>
<?php elseif ($mail['status'] === 'closed'): ?>
<span class="badge bg-success">مكتمل</span>
<?php if ($mail['due_date']): ?>
<span class="<?= (strtotime($mail['due_date']) < time() && $mail['status_name'] != 'closed') ? 'text-danger fw-bold' : '' ?>">
<?= $mail['due_date'] ?>
<?php if (strtotime($mail['due_date']) < time() && $mail['status_name'] != 'closed'): ?>
<i class="fas fa-exclamation-triangle ms-1"></i>
<?php endif; ?>
</span>
<?php else: ?>
<span class="text-muted">-</span>
<?php endif; ?>
</td>
<td><?= truncate_text($mail['subject'], 80) ?></td>
<td><?= htmlspecialchars($mail['recipient']) ?></td>
<td>
<?php if ($mail['assigned_to_name']): ?>
<span class="text-nowrap"><i class="fas fa-user-tag me-1 text-muted"></i> <?= htmlspecialchars($mail['assigned_to_name']) ?></span>
<?php else: ?>
<span class="text-muted">غير معين</span>
<?php endif; ?>
</td>
<td><?= getStatusBadgeInList($mail) ?></td>
<td class="pe-4 text-center">
<a href="view_mail.php?id=<?= $mail['id'] ?>" class="btn btn-sm btn-outline-info" title="عرض التفاصيل"><i class="fas fa-eye"></i></a>
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="openMailModal('edit', <?= htmlspecialchars(json_encode($mail), ENT_QUOTES, 'UTF-8') ?>)" title="تعديل">
onclick='openMailModal("edit", <?= json_encode($mail) ?>)' title="تعديل">
<i class="fas fa-edit"></i>
</button>
<a href="javascript:void(0)" onclick="confirmDelete(<?= $mail['id'] ?>)" class="btn btn-sm btn-outline-danger" title="حذف"><i class="fas fa-trash"></i></a>
@ -180,7 +288,7 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
</tr>
<?php endforeach; else: ?>
<tr>
<td colspan="6" class="text-center py-4 text-muted">لا يوجد بريد صادر مسجل حالياً</td>
<td colspan="8" class="text-center py-4 text-muted">لا يوجد بريد صادر مسجل حالياً</td>
</tr>
<?php endif; ?>
</tbody>
@ -203,14 +311,18 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<input type="hidden" name="id" id="modalId" value="0">
<div class="row g-3">
<div class="col-md-6">
<div class="col-md-4">
<label class="form-label fw-bold">رقم القيد <span class="text-danger">*</span></label>
<input type="text" name="ref_no" id="modalRefNo" class="form-control" required>
</div>
<div class="col-md-6">
<div class="col-md-4">
<label class="form-label fw-bold">تاريخ التسجيل</label>
<input type="date" name="date_registered" id="modalDateRegistered" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label fw-bold">الموعد النهائي</label>
<input type="date" name="due_date" id="modalDueDate" class="form-control">
</div>
<div class="col-md-6">
<label class="form-label fw-bold">المستلم الخارجي (الجهة المستلمة)</label>
<input type="text" name="recipient" id="modalRecipient" class="form-control">
@ -230,14 +342,19 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<div class="col-12">
<label class="form-label fw-bold">المرفقات</label>
<input type="file" name="attachments[]" class="form-control" multiple>
<div class="form-text text-muted">يمكنك اختيار ملفات متعددة.</div>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">الحالة</label>
<select name="status" id="modalStatus" class="form-select">
<option value="received">تم الاستلام</option>
<option value="in_progress">قيد المعالجة</option>
<option value="closed">مكتمل / مغلق</option>
<select name="status_id" id="modalStatusId" class="form-select">
<?php foreach ($statuses_list as $s): ?>
<?php
$d_name = $s['name'];
if ($d_name == 'received') $d_name = 'تم الاستلام';
if ($d_name == 'in_progress') $d_name = 'قيد المعالجة';
if ($d_name == 'closed') $d_name = 'مكتمل / مغلق';
?>
<option value="<?= $s['id'] ?>"><?= htmlspecialchars($d_name) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
@ -299,10 +416,11 @@ function openMailModal(action, data = null) {
const fields = {
ref_no: document.getElementById('modalRefNo'),
date_registered: document.getElementById('modalDateRegistered'),
due_date: document.getElementById('modalDueDate'),
sender: document.getElementById('modalSender'),
recipient: document.getElementById('modalRecipient'),
subject: document.getElementById('modalSubject'),
status: document.getElementById('modalStatus'),
status_id: document.getElementById('modalStatusId'),
assigned_to: document.getElementById('modalAssignedTo')
};
@ -314,7 +432,7 @@ function openMailModal(action, data = null) {
Object.keys(fields).forEach(key => {
if (fields[key]) {
if (key === 'date_registered') fields[key].value = '<?= date('Y-m-d') ?>';
else if (key === 'status') fields[key].value = 'received';
else if (key === 'status_id') fields[key].value = '<?= $default_status_id ?>';
else fields[key].value = '';
}
});
@ -337,7 +455,6 @@ function openMailModal(action, data = null) {
document.addEventListener('DOMContentLoaded', function() {
initEditors().finally(() => {
// Deep link or error handling
<?php if ($deepLinkData): ?>
openMailModal('edit', <?= json_encode($deepLinkData) ?>);
<?php elseif ($error && isset($_POST['action'])): ?>
@ -345,11 +462,12 @@ document.addEventListener('DOMContentLoaded', function() {
'id' => $_POST['id'] ?? 0,
'ref_no' => $_POST['ref_no'] ?? '',
'date_registered' => $_POST['date_registered'] ?? date('Y-m-d'),
'due_date' => $_POST['due_date'] ?? '',
'sender' => $_POST['sender'] ?? '',
'recipient' => $_POST['recipient'] ?? '',
'subject' => $_POST['subject'] ?? '',
'description' => $_POST['description'] ?? '',
'status' => $_POST['status'] ?? 'received',
'status_id' => $_POST['status_id'] ?? $default_status_id,
'assigned_to' => $_POST['assigned_to'] ?? ''
]) ?>;
openMailModal('<?= $_POST['action'] ?>', errorData);
@ -358,7 +476,6 @@ document.addEventListener('DOMContentLoaded', function() {
<?php endif; ?>
});
// Handle form submission to ensure CKEditor data is synced
document.getElementById('mailForm').addEventListener('submit', function() {
if (descriptionEditor) descriptionEditor.updateSourceElement();
});
@ -396,16 +513,9 @@ function confirmDelete(id) {
border-radius: 15px;
overflow: hidden;
}
.modal-header {
border-bottom: none;
}
.modal-footer {
border-top: none;
}
/* Specific styling for green header */
.modal-header.bg-success {
background-color: #198754 !important;
}
</style>
<?php require_once __DIR__ . '/includes/footer.php'; ?>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

126
overdue_report.php Normal file
View File

@ -0,0 +1,126 @@
<?php
require_once 'includes/header.php';
if (!isAdmin()) {
redirect('index.php');
}
$type_filter = $_GET['type'] ?? '';
$user_filter = $_GET['user_id'] ?? '';
$params = [];
$where = ["due_date < CURDATE()", "status != 'closed'"];
if ($type_filter) {
$where[] = "type = ?";
$params[] = $type_filter;
}
if ($user_filter) {
$where[] = "assigned_to = ?";
$params[] = $user_filter;
}
$where_clause = implode(" AND ", $where);
$sql = "SELECT m.*, u.full_name as assigned_name
FROM mailbox m
LEFT JOIN users u ON m.assigned_to = u.id
WHERE $where_clause
ORDER BY m.due_date ASC";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$overdue_items = $stmt->fetchAll();
// Fetch all users for filter
$users = db()->query("SELECT id, full_name FROM users ORDER BY full_name")->fetchAll();
?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">تقرير المهام المتأخرة</h1>
</div>
<div class="card mb-4">
<div class="card-body">
<form method="GET" class="row g-3">
<div class="col-md-4">
<label class="form-label">نوع البريد</label>
<select name="type" class="form-select">
<option value="">الكل</option>
<option value="inbound" <?= $type_filter == 'inbound' ? 'selected' : '' ?>>وارد</option>
<option value="outbound" <?= $type_filter == 'outbound' ? 'selected' : '' ?>>صادر</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">الموظف المسؤول</label>
<select name="user_id" class="form-select">
<option value="">الكل</option>
<?php foreach ($users as $user): ?>
<option value="<?= $user['id'] ?>" <?= $user_filter == $user['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($user['full_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">تصفية</button>
</div>
</form>
</div>
</div>
<div class="card">
<div class="card-header bg-danger text-white">
<h5 class="card-title mb-0"><i class="fas fa-exclamation-triangle me-2"></i> جميع المهام المتأخرة</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>رقم المرجع</th>
<th>النوع</th>
<th>الموضوع</th>
<th>الموظف المسؤول</th>
<th>تاريخ الاستحقاق</th>
<th>الأيام المتأخرة</th>
<th>الإجراءات</th>
</tr>
</thead>
<tbody>
<?php if (empty($overdue_items)): ?>
<tr>
<td colspan="7" class="text-center py-4 text-muted">لا توجد مهام متأخرة حالياً.</td>
</tr>
<?php else: ?>
<?php foreach ($overdue_items as $item):
$due_date = new DateTime($item['due_date']);
$today = new DateTime();
$diff = $today->diff($due_date)->format("%a");
?>
<tr>
<td><?= htmlspecialchars($item['ref_no']) ?></td>
<td>
<span class="badge bg-<?= $item['type'] == 'inbound' ? 'info' : 'warning' ?>">
<?= $item['type'] == 'inbound' ? 'وارد' : 'صادر' ?>
</span>
</td>
<td><?= htmlspecialchars($item['subject']) ?></td>
<td><?= htmlspecialchars($item['assigned_name'] ?? 'غير معين') ?></td>
<td class="text-danger fw-bold"><?= $item['due_date'] ?></td>
<td class="text-danger fw-bold"><?= $diff ?> يوم</td>
<td>
<a href="view_mail.php?id=<?= $item['id'] ?>" class="btn btn-sm btn-outline-primary">
<i class="fas fa-eye"></i> عرض
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php require_once 'includes/footer.php'; ?>

View File

@ -0,0 +1,75 @@
<?php
// scripts/send_reminders.php
// Should be run as a cron job daily: php scripts/send_reminders.php
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../mail/MailService.php';
echo "[" . date('Y-m-d H:i:s') . "] Starting reminder process..." . PHP_EOL;
// 1. Tasks due in 24 hours (exactly 1 day away)
$due_tomorrow = db()->query("
SELECT m.*, u.email, u.full_name
FROM mailbox m
JOIN users u ON m.assigned_to = u.id
WHERE m.due_date = DATE_ADD(CURDATE(), INTERVAL 1 DAY)
AND m.status != 'closed'
")->fetchAll();
foreach ($due_tomorrow as $task) {
if (!empty($task['email'])) {
$subject = "تذكير: موعد نهائي لمهمة غداً - " . $task['ref_no'];
$html = "
<h3>تذكير بموعد نهائي</h3>
<p>عزيزي <b>" . htmlspecialchars($task['full_name']) . "</b>،</p>
<p>هذا تذكير بأن المهمة التالية مستحقة غداً:</p>
<ul>
<li><b>رقم المرجع:</b> " . htmlspecialchars($task['ref_no']) . "</li>
<li><b>الموضوع:</b> " . htmlspecialchars($task['subject']) . "</li>
<li><b>تاريخ الاستحقاق:</b> " . $task['due_date'] . "</li>
</ul>
<p>يرجى متابعة المهمة وإغلاقها في الوقت المحدد.</p>
";
$res = MailService::sendMail($task['email'], $subject, $html);
if ($res['success']) {
echo "Sent 24h reminder to " . $task['email'] . " for task " . $task['ref_no'] . PHP_EOL;
} else {
echo "Failed to send 24h reminder to " . $task['email'] . ": " . ($res['error'] ?? 'Unknown error') . PHP_EOL;
}
}
}
// 2. Overdue tasks (due date passed and still not closed) - Only send once a week or daily?
// For now, let's send daily for overdue tasks to ensure they are handled.
$overdue = db()->query("
SELECT m.*, u.email, u.full_name
FROM mailbox m
JOIN users u ON m.assigned_to = u.id
WHERE m.due_date < CURDATE()
AND m.status != 'closed'
")->fetchAll();
foreach ($overdue as $task) {
if (!empty($task['email'])) {
$subject = "تنبيه: مهمة متأخرة! - " . $task['ref_no'];
$html = "
<h3 style='color: red;'>تنبيه: مهمة متأخرة</h3>
<p>عزيزي <b>" . htmlspecialchars($task['full_name']) . "</b>،</p>
<p>هذه المهمة قد تجاوزت الموعد النهائي المحدد:</p>
<ul>
<li><b>رقم المرجع:</b> " . htmlspecialchars($task['ref_no']) . "</li>
<li><b>الموضوع:</b> " . htmlspecialchars($task['subject']) . "</li>
<li><b>تاريخ الاستحقاق:</b> <span style='color: red;'>" . $task['due_date'] . "</span></li>
</ul>
<p>يرجى معالجة هذه المهمة في أقرب وقت ممكن.</p>
";
$res = MailService::sendMail($task['email'], $subject, $html);
if ($res['success']) {
echo "Sent overdue reminder to " . $task['email'] . " for task " . $task['ref_no'] . PHP_EOL;
} else {
echo "Failed to send overdue reminder to " . $task['email'] . ": " . ($res['error'] ?? 'Unknown error') . PHP_EOL;
}
}
}
echo "[" . date('Y-m-d H:i:s') . "] Reminder process finished." . PHP_EOL;

290
user_dashboard.php Normal file
View File

@ -0,0 +1,290 @@
<?php
require_once __DIR__ . '/includes/header.php';
$user_id = $_SESSION['user_id'];
$user_role = $_SESSION['user_role'];
$is_admin = isAdmin();
$is_clerk = ($user_role === 'clerk');
// Stats for this specific user
$stmt = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE assigned_to = ?");
$stmt->execute([$user_id]);
$my_total_assignments = $stmt->fetchColumn();
$stmt = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE assigned_to = ? AND status != 'closed'");
$stmt->execute([$user_id]);
$my_pending_tasks = $stmt->fetchColumn();
// Global Stats (for Clerks or if we want to show them)
$total_inbound = db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'inbound'")->fetchColumn();
$total_outbound = db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'outbound'")->fetchColumn();
// Fetch statuses for badge and count
$statuses_data = db()->query("SELECT * FROM mailbox_statuses")->fetchAll(PDO::FETCH_UNIQUE);
// My Assignments
$my_assignments = db()->prepare("SELECT m.*, s.name as status_name, s.color as status_color
FROM mailbox m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
WHERE m.assigned_to = ?
ORDER BY m.created_at DESC LIMIT 10");
$my_assignments->execute([$user_id]);
$my_assignments = $my_assignments->fetchAll();
// Recent Activity
$recent_query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name
FROM mailbox m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
LEFT JOIN users u ON m.assigned_to = u.id";
if ($is_clerk) {
// Clerks see all recent activity
$recent_stmt = db()->prepare($recent_query . " ORDER BY m.updated_at DESC LIMIT 10");
$recent_stmt->execute();
} else {
// Staff see only theirs
$recent_stmt = db()->prepare($recent_query . " WHERE m.assigned_to = ? OR m.created_by = ? ORDER BY m.updated_at DESC LIMIT 10");
$recent_stmt->execute([$user_id, $user_id]);
}
$recent_activity = $recent_stmt->fetchAll();
function getStatusBadge($mail) {
$status_name = $mail['status_name'] ?? 'غير معروف';
$status_color = $mail['status_color'] ?? '#6c757d';
$display_name = $status_name;
if ($status_name == 'received') $display_name = 'تم الاستلام';
if ($status_name == 'in_progress') $display_name = 'قيد المعالجة';
if ($status_name == 'closed') $display_name = 'مكتمل';
return '<span class="badge" style="background-color: ' . $status_color . ';">' . htmlspecialchars($display_name) . '</span>';
}
?>
<div class="row mb-4">
<div class="col-md-12">
<div class="card bg-dark text-white p-4 shadow-lg border-0 overflow-hidden position-relative">
<div class="position-absolute end-0 top-0 p-3 opacity-10">
<i class="fas fa-envelope-open-text fa-10x" style="transform: rotate(-15deg);"></i>
</div>
<div class="d-flex justify-content-between align-items-center position-relative">
<div>
<h2 class="fw-bold mb-1">مرحباً، <?= htmlspecialchars($current_user['full_name'] ?? $_SESSION['username']) ?>!</h2>
<p class="mb-0 opacity-75">
<?php if ($is_clerk): ?>
أنت مسجل كـ <strong>كاتب</strong>. يمكنك متابعة كافة المراسلات وإدارة المهام.
<?php else: ?>
أنت مسجل كـ <strong>موظف</strong>. تابع مهامك المسندة إليك هنا.
<?php endif; ?>
</p>
</div>
<div class="d-none d-md-block">
<?php if ($current_user['profile_image']): ?>
<img src="<?= $current_user['profile_image'] ?>?v=<?= time() ?>" alt="Profile" class="rounded-circle border border-3 border-white shadow" style="width: 100px; height: 100px; object-fit: cover;">
<?php else: ?>
<div class="bg-white bg-opacity-25 rounded-circle d-flex align-items-center justify-content-center border border-3 border-white shadow" style="width: 100px; height: 100px;">
<i class="fas fa-user fa-3x text-white"></i>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<!-- Stats for everyone -->
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-primary border-4">
<div class="d-flex align-items-center">
<div class="bg-primary bg-opacity-10 p-3 rounded-3 me-3">
<i class="fas fa-tasks text-primary fs-4"></i>
</div>
<div>
<h6 class="text-muted mb-1">مهامي</h6>
<h3 class="fw-bold mb-0"><?= $my_total_assignments ?></h3>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-warning border-4">
<div class="d-flex align-items-center">
<div class="bg-warning bg-opacity-10 p-3 rounded-3 me-3">
<i class="fas fa-clock text-warning fs-4"></i>
</div>
<div>
<h6 class="text-muted mb-1">قيد التنفيذ</h6>
<h3 class="fw-bold mb-0"><?= $my_pending_tasks ?></h3>
</div>
</div>
</div>
</div>
<?php if ($is_clerk): ?>
<!-- Clerk specific stats -->
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-info border-4">
<div class="d-flex align-items-center">
<div class="bg-info bg-opacity-10 p-3 rounded-3 me-3">
<i class="fas fa-download text-info fs-4"></i>
</div>
<div>
<h6 class="text-muted mb-1">إجمالي الوارد</h6>
<h3 class="fw-bold mb-0"><?= $total_inbound ?></h3>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-success border-4">
<div class="d-flex align-items-center">
<div class="bg-success bg-opacity-10 p-3 rounded-3 me-3">
<i class="fas fa-upload text-success fs-4"></i>
</div>
<div>
<h6 class="text-muted mb-1">إجمالي الصادر</h6>
<h3 class="fw-bold mb-0"><?= $total_outbound ?></h3>
</div>
</div>
</div>
</div>
<?php else: ?>
<!-- Staff specific stats -->
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-info border-4">
<div class="d-flex align-items-center">
<div class="bg-info bg-opacity-10 p-3 rounded-3 me-3">
<i class="fas fa-envelope-open text-info fs-4"></i>
</div>
<div>
<h6 class="text-muted mb-1">وارد من قبلي</h6>
<?php
$stmt = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE created_by = ? AND type = 'inbound'");
$stmt->execute([$user_id]);
$my_in_count = $stmt->fetchColumn();
?>
<h3 class="fw-bold mb-0"><?= $my_in_count ?></h3>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-success border-4">
<div class="d-flex align-items-center">
<div class="bg-success bg-opacity-10 p-3 rounded-3 me-3">
<i class="fas fa-paper-plane text-success fs-4"></i>
</div>
<div>
<h6 class="text-muted mb-1">صادر من قبلي</h6>
<?php
$stmt = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE created_by = ? AND type = 'outbound'");
$stmt->execute([$user_id]);
$my_out_count = $stmt->fetchColumn();
?>
<h3 class="fw-bold mb-0"><?= $my_out_count ?></h3>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
<div class="row">
<!-- Assignments Table -->
<div class="col-lg-8">
<div class="card shadow-sm border-0 mb-4 h-100">
<div class="card-header bg-white py-3 border-bottom d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold"><i class="fas fa-clipboard-list me-2 text-primary"></i> مهامي المسندة</h5>
<div class="btn-group">
<a href="inbound.php?action=add" class="btn btn-sm btn-outline-primary">إضافة وارد</a>
<a href="outbound.php" class="btn btn-sm btn-outline-success">إضافة صادر</a>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">رقم القيد</th>
<th>الموضوع</th>
<th>الموعد النهائي</th>
<th>الحالة</th>
<th class="pe-4 text-center">الإجراء</th>
</tr>
</thead>
<tbody>
<?php if ($my_assignments): ?>
<?php foreach ($my_assignments as $mail): ?>
<tr style="cursor: pointer;" onclick="window.location='view_mail.php?id=<?= $mail['id'] ?>'">
<td class="ps-4 fw-bold text-primary"><?= $mail['ref_no'] ?></td>
<td><?= htmlspecialchars($mail['subject']) ?></td>
<td>
<?php if ($mail['due_date']): ?>
<small class="<?= (strtotime($mail['due_date']) < time() && $mail['status_name'] != 'closed') ? 'text-danger fw-bold' : 'text-muted' ?>">
<?= $mail['due_date'] ?>
</small>
<?php else: ?>
<small class="text-muted">-</small>
<?php endif; ?>
</td>
<td><?= getStatusBadge($mail) ?></td>
<td class="pe-4 text-center">
<a href="view_mail.php?id=<?= $mail['id'] ?>" class="btn btn-sm btn-light rounded-pill px-3">عرض</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="5" class="text-center py-5 text-muted">
<i class="fas fa-check-double fa-3x mb-3 d-block text-success opacity-25"></i>
أنت على اطلاع بكافة مهامك! لا توجد مهام معلقة.
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Recent Activity Sidebar -->
<div class="col-lg-4">
<div class="card shadow-sm border-0 mb-4 h-100">
<div class="card-header bg-white py-3 border-bottom">
<h5 class="mb-0 fw-bold"><i class="fas fa-bell me-2 text-warning"></i> <?= $is_clerk ? 'آخر المراسلات' : 'نشاطاتي الأخيرة' ?></h5>
</div>
<div class="card-body p-0" style="max-height: 500px; overflow-y: auto;">
<div class="list-group list-group-flush">
<?php if ($recent_activity): ?>
<?php foreach ($recent_activity as $act): ?>
<a href="view_mail.php?id=<?= $act['id'] ?>" class="list-group-item list-group-item-action p-3 border-0 border-bottom">
<div class="d-flex w-100 justify-content-between mb-1">
<h6 class="mb-1 fw-bold text-truncate" title="<?= htmlspecialchars($act['subject']) ?>"><?= htmlspecialchars($act['subject']) ?></h6>
<small class="text-muted"><?= date('m-d', strtotime($act['updated_at'])) ?></small>
</div>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<i class="fas <?= $act['type'] == 'inbound' ? 'fa-arrow-down text-primary' : 'fa-arrow-up text-success' ?> me-1"></i>
<?= $act['ref_no'] ?>
</small>
<?= getStatusBadge($act) ?>
</div>
</a>
<?php endforeach; ?>
<?php else: ?>
<div class="text-center py-5 text-muted">
لا يوجد نشاط مسجل
</div>
<?php endif; ?>
</div>
</div>
<div class="card-footer bg-light text-center py-2">
<a href="inbound.php" class="small text-decoration-none">عرض كافة المراسلات <i class="fas fa-chevron-left ms-1"></i></a>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -4,10 +4,12 @@ require_once __DIR__ . '/includes/header.php';
$id = $_GET['id'] ?? 0;
if (!$id) redirect('index.php');
$stmt = db()->prepare("SELECT m.*, u1.full_name as assigned_name, u2.full_name as creator_name
$stmt = db()->prepare("SELECT m.*, u1.full_name as assigned_name, u2.full_name as creator_name,
s.name as status_name, s.color as status_color
FROM mailbox m
LEFT JOIN users u1 ON m.assigned_to = u1.id
LEFT JOIN users u2 ON m.created_by = u2.id
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
WHERE m.id = ?");
$stmt->execute([$id]);
$mail = $stmt->fetch();
@ -116,24 +118,41 @@ function isPreviewable($fileName) {
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<div class="col-md-3">
<label class="text-muted small">رقم القيد</label>
<p class="fw-bold fs-5 text-primary"><?= $mail['ref_no'] ?></p>
</div>
<div class="col-md-4">
<div class="col-md-3">
<label class="text-muted small">تاريخ التسجيل</label>
<p class="fw-bold"><?= $mail['date_registered'] ?></p>
</div>
<div class="col-md-4">
<div class="col-md-3">
<label class="text-muted small">الموعد النهائي</label>
<p class="fw-bold">
<?php if ($mail['due_date']): ?>
<span class="<?= (strtotime($mail['due_date']) < time() && $mail['status_name'] != 'closed') ? 'text-danger' : '' ?>">
<?= $mail['due_date'] ?>
<?php if (strtotime($mail['due_date']) < time() && $mail['status_name'] != 'closed'): ?>
<i class="fas fa-exclamation-triangle ms-1"></i>
<?php endif; ?>
</span>
<?php else: ?>
<span class="text-muted">غير محدد</span>
<?php endif; ?>
</p>
</div>
<div class="col-md-3">
<label class="text-muted small">الحالة</label>
<p>
<?php if ($mail['status'] === 'received'): ?>
<span class="badge bg-secondary">تم الاستلام</span>
<?php elseif ($mail['status'] === 'in_progress'): ?>
<span class="badge bg-info text-dark">قيد المعالجة</span>
<?php elseif ($mail['status'] === 'closed'): ?>
<span class="badge bg-success">مكتمل</span>
<?php endif; ?>
<?php
$s_name = $mail['status_name'] ?? 'غير معروف';
$s_color = $mail['status_color'] ?? '#6c757d';
$d_name = $s_name;
if ($s_name == 'received') $d_name = 'تم الاستلام';
if ($s_name == 'in_progress') $d_name = 'قيد المعالجة';
if ($s_name == 'closed') $d_name = 'مكتمل';
?>
<span class="badge" style="background-color: <?= $s_color ?>;"><?= htmlspecialchars($d_name) ?></span>
</p>
</div>
<div class="col-12">
@ -281,7 +300,7 @@ function isPreviewable($fileName) {
</div>
<div class="modal-footer">
<a id="downloadBtn" href="#" class="btn btn-primary" download>تحميل الملف</a>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إغلاق</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إإغلاق</button>
</div>
</div>
</div>