359 lines
15 KiB
PHP
359 lines
15 KiB
PHP
<?php
|
|
require_once 'includes/header.php';
|
|
|
|
if (!canView('events')) {
|
|
echo "<div class='alert alert-danger m-4'>ليس لديك صلاحية لعرض هذه الصفحة.</div>";
|
|
require_once 'includes/footer.php';
|
|
exit;
|
|
}
|
|
|
|
$can_add = canAdd('events');
|
|
$can_edit = canEdit('events');
|
|
$can_delete = canDelete('events');
|
|
|
|
// Auto-create table if missing (useful for deployed environments that haven't run migrations)
|
|
try {
|
|
db()->query("SELECT 1 FROM events LIMIT 1");
|
|
} catch (Exception $e) {
|
|
try {
|
|
$sql = file_get_contents(__DIR__ . '/db/migrations/037_add_events_module.sql');
|
|
if ($sql) {
|
|
db()->exec($sql);
|
|
}
|
|
} catch (Exception $e2) {
|
|
// Silently ignore, let the AJAX or save functions report the error
|
|
}
|
|
}
|
|
|
|
// Handle AJAX requests
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['ajax'])) {
|
|
ob_clean(); header('Content-Type: application/json');
|
|
$action = $_POST['action'] ?? '';
|
|
|
|
if ($action === 'fetch') {
|
|
$stmt = db()->query("SELECT id, title, description, event_date, start_time, end_time, location FROM events");
|
|
$events = $stmt->fetchAll();
|
|
$fc_events = [];
|
|
foreach($events as $e) {
|
|
$start = $e['event_date'];
|
|
if ($e['start_time']) $start .= 'T' . $e['start_time'];
|
|
|
|
$end = $e['event_date'];
|
|
if ($e['end_time']) $end .= 'T' . $e['end_time'];
|
|
|
|
$fc_events[] = [
|
|
'id' => $e['id'],
|
|
'title' => $e['title'],
|
|
'start' => $start,
|
|
'end' => $end,
|
|
'extendedProps' => [
|
|
'description' => $e['description'] ?? '',
|
|
'location' => $e['location'] ?? '',
|
|
'start_time' => $e['start_time'] ? substr($e['start_time'], 0, 5) : '',
|
|
'end_time' => $e['end_time'] ? substr($e['end_time'], 0, 5) : ''
|
|
]
|
|
];
|
|
}
|
|
echo json_encode($fc_events);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'save') {
|
|
$id = $_POST['id'] ?? 0;
|
|
$title = $_POST['title'] ?? '';
|
|
$date = $_POST['event_date'] ?? '';
|
|
$start_time = !empty($_POST['start_time']) ? $_POST['start_time'] : null;
|
|
$end_time = !empty($_POST['end_time']) ? $_POST['end_time'] : null;
|
|
$location = $_POST['location'] ?? '';
|
|
$description = $_POST['description'] ?? '';
|
|
|
|
if (!$title || !$date) {
|
|
echo json_encode(['success' => false, 'error' => 'البيانات الأساسية مطلوبة']);
|
|
exit;
|
|
}
|
|
|
|
try {
|
|
if ($id && $can_edit) {
|
|
$stmt = db()->prepare("UPDATE events SET title=?, description=?, event_date=?, start_time=?, end_time=?, location=? WHERE id=?");
|
|
$stmt->execute([$title, $description, $date, $start_time, $end_time, $location, $id]);
|
|
} elseif (!$id && $can_add) {
|
|
$stmt = db()->prepare("INSERT INTO events (title, description, event_date, start_time, end_time, location, created_by) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
|
$stmt->execute([$title, $description, $date, $start_time, $end_time, $location, $_SESSION['user_id']]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'error' => 'عفواً، لا تملك الصلاحيات الكافية للتقويم (إضافة/تعديل) في هذا الخادم. يرجى تفعيل الصلاحيات من صفحة إدارة المستخدمين.']);
|
|
exit;
|
|
}
|
|
|
|
echo json_encode(['success' => true]);
|
|
} catch (Exception $e) {
|
|
echo json_encode(['success' => false, 'error' => 'خطأ قاعدة البيانات: ' . $e->getMessage()]);
|
|
}
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'delete') {
|
|
if (!$can_delete) {
|
|
echo json_encode(['success' => false, 'error' => 'لا تملك صلاحية الحذف.']);
|
|
exit;
|
|
}
|
|
try {
|
|
$id = $_POST['id'] ?? 0;
|
|
db()->prepare("DELETE FROM events WHERE id=?")->execute([$id]);
|
|
echo json_encode(['success' => true]);
|
|
} catch (Exception $e) {
|
|
echo json_encode(['success' => false, 'error' => 'خطأ قاعدة البيانات: ' . $e->getMessage()]);
|
|
}
|
|
exit;
|
|
}
|
|
}
|
|
?>
|
|
|
|
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.css' rel='stylesheet' />
|
|
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.js'></script>
|
|
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/locales/ar.js'></script>
|
|
|
|
<div class="container-fluid py-4">
|
|
<div class="row mb-4 align-items-center">
|
|
<div class="col">
|
|
<h2 class="h4 mb-0"><i class="fas fa-calendar-alt text-primary me-2"></i> التقويم والأحداث</h2>
|
|
</div>
|
|
<div class="col-auto">
|
|
<a href="print_events.php" target="_blank" class="btn btn-secondary me-2">
|
|
<i class="fas fa-print me-1"></i> طباعة الأحداث
|
|
</a>
|
|
<?php if ($can_add): ?>
|
|
<button class="btn btn-primary" onclick="openEventModal()">
|
|
<i class="fas fa-plus me-1"></i> إضافة حدث جديد
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm border-0">
|
|
<div class="card-body p-4">
|
|
<div id='calendar'></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Event Modal -->
|
|
<div class="modal fade" id="eventModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="eventModalTitle">إضافة حدث</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="eventForm">
|
|
<input type="hidden" id="event_id" name="id">
|
|
<input type="hidden" name="action" value="save">
|
|
<div class="mb-3">
|
|
<label class="form-label">عنوان الحدث <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" id="event_title" name="title" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">التاريخ <span class="text-danger">*</span></label>
|
|
<input type="date" class="form-control" id="event_date" name="event_date" required>
|
|
</div>
|
|
<div class="row mb-3">
|
|
<div class="col-6">
|
|
<label class="form-label">وقت البدء</label>
|
|
<input type="time" class="form-control" id="event_start_time" name="start_time">
|
|
</div>
|
|
<div class="col-6">
|
|
<label class="form-label">وقت الانتهاء</label>
|
|
<input type="time" class="form-control" id="event_end_time" name="end_time">
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">المكان</label>
|
|
<input type="text" class="form-control" id="event_location" name="location">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">التفاصيل</label>
|
|
<textarea class="form-control" id="event_description" name="description" rows="3"></textarea>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer justify-content-between">
|
|
<div>
|
|
<?php if ($can_delete): ?>
|
|
<button type="button" class="btn btn-danger d-none" id="btnDeleteEvent" onclick="deleteEvent()">حذف</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div>
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
|
|
<button type="button" class="btn btn-primary" onclick="saveEvent()">حفظ</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let calendar;
|
|
let eventModal;
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var calendarEl = document.getElementById('calendar');
|
|
calendar = new FullCalendar.Calendar(calendarEl, {
|
|
locale: 'ar',
|
|
direction: 'rtl',
|
|
initialView: 'dayGridMonth',
|
|
headerToolbar: {
|
|
left: 'prev,next today',
|
|
center: 'title',
|
|
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
|
},
|
|
events: function(info, successCallback, failureCallback) {
|
|
let fd = new FormData();
|
|
fd.append('action', 'fetch');
|
|
fetch('events.php?ajax=1', {
|
|
method: 'POST',
|
|
body: fd
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => successCallback(data))
|
|
.catch(err => failureCallback(err));
|
|
},
|
|
dateClick: function(info) {
|
|
openEventModal({ date: info.dateStr });
|
|
},
|
|
eventClick: function(info) {
|
|
let ev = info.event;
|
|
let props = ev.extendedProps;
|
|
|
|
// Format date for <input type="date"> which expects YYYY-MM-DD
|
|
let dateStr = ev.start.getFullYear() + '-' +
|
|
String(ev.start.getMonth() + 1).padStart(2, '0') + '-' +
|
|
String(ev.start.getDate()).padStart(2, '0');
|
|
|
|
openEventModal({
|
|
id: ev.id,
|
|
title: ev.title,
|
|
date: dateStr,
|
|
start_time: props.start_time,
|
|
end_time: props.end_time,
|
|
location: props.location,
|
|
description: props.description
|
|
});
|
|
}
|
|
});
|
|
calendar.render();
|
|
eventModal = new bootstrap.Modal(document.getElementById('eventModal'));
|
|
});
|
|
|
|
function openEventModal(data = null) {
|
|
document.getElementById('eventForm').reset();
|
|
document.getElementById('event_id').value = '';
|
|
document.getElementById('eventModalTitle').innerText = 'إضافة حدث جديد';
|
|
|
|
let btnDel = document.getElementById('btnDeleteEvent');
|
|
if (btnDel) btnDel.classList.add('d-none');
|
|
|
|
if (data) {
|
|
document.getElementById('event_id').value = data.id || '';
|
|
document.getElementById('event_title').value = data.title || '';
|
|
document.getElementById('event_date').value = data.date || '';
|
|
document.getElementById('event_start_time').value = data.start_time || '';
|
|
document.getElementById('event_end_time').value = data.end_time || '';
|
|
document.getElementById('event_location').value = data.location || '';
|
|
document.getElementById('event_description').value = data.description || '';
|
|
|
|
if (data.id) {
|
|
document.getElementById('eventModalTitle').innerText = 'تعديل حدث';
|
|
if (btnDel) btnDel.classList.remove('d-none');
|
|
}
|
|
}
|
|
|
|
eventModal.show();
|
|
}
|
|
|
|
function saveEvent() {
|
|
const form = document.getElementById('eventForm');
|
|
if (!form.reportValidity()) return;
|
|
|
|
fetch('events.php?ajax=1', {
|
|
method: 'POST',
|
|
body: new FormData(form)
|
|
})
|
|
.then(r => {
|
|
if (!r.ok) throw new Error("Network Error");
|
|
return r.json();
|
|
})
|
|
.then(res => {
|
|
if (res.success) {
|
|
eventModal.hide();
|
|
calendar.refetchEvents();
|
|
Swal.fire({icon: 'success', title: 'تم الحفظ', showConfirmButton: false, timer: 1500});
|
|
} else {
|
|
Swal.fire({icon: 'error', title: 'خطأ', text: res.error || 'حدث خطأ أثناء الحفظ'});
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
Swal.fire({icon: 'error', title: 'حدث خطأ غير متوقع', text: 'إما أن جلسة تسجيل الدخول انتهت، أو لا توجد صلاحيات (راجع سجل وحدة التحكم). يرجى تحديث الصفحة والمحاولة مجدداً.'});
|
|
});
|
|
}
|
|
|
|
function deleteEvent() {
|
|
const id = document.getElementById('event_id').value;
|
|
if (!id) return;
|
|
|
|
Swal.fire({
|
|
title: 'هل أنت متأكد؟',
|
|
text: "لن تتمكن من التراجع عن هذا!",
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#d33',
|
|
cancelButtonColor: '#3085d6',
|
|
confirmButtonText: 'نعم، احذف!',
|
|
cancelButtonText: 'إلغاء'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
let fd = new FormData();
|
|
fd.append('action', 'delete');
|
|
fd.append('id', id);
|
|
fetch('events.php?ajax=1', {
|
|
method: 'POST',
|
|
body: fd
|
|
})
|
|
.then(r => {
|
|
if (!r.ok) throw new Error("Network Error");
|
|
return r.json();
|
|
})
|
|
.then(res => {
|
|
if (res.success) {
|
|
eventModal.hide();
|
|
calendar.refetchEvents();
|
|
Swal.fire({icon: 'success', title: 'تم الحذف', showConfirmButton: false, timer: 1500});
|
|
} else {
|
|
Swal.fire({icon: 'error', title: 'خطأ', text: res.error || 'حدث خطأ أثناء الحذف'});
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
Swal.fire({icon: 'error', title: 'حدث خطأ غير متوقع', text: 'إما أن جلسة تسجيل الدخول انتهت، أو لا توجد صلاحيات. يرجى تحديث الصفحة والمحاولة مجدداً.'});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
/* FullCalendar customization */
|
|
.fc-theme-standard .fc-scrollgrid { border-color: var(--bs-border-color); }
|
|
.fc-theme-standard td, .fc-theme-standard th { border-color: var(--bs-border-color); }
|
|
.fc .fc-button-primary { background-color: #0d6efd; border-color: #0d6efd; }
|
|
.fc .fc-button-primary:not(:disabled):active, .fc .fc-button-primary:not(:disabled).fc-button-active {
|
|
background-color: #0a58ca; border-color: #0a53be;
|
|
}
|
|
.fc-event { cursor: pointer; }
|
|
.fc .fc-toolbar-title { font-size: 1.25em; font-weight: bold; }
|
|
[data-bs-theme="dark"] .fc-theme-standard .fc-scrollgrid { border-color: rgba(255,255,255,0.1); }
|
|
[data-bs-theme="dark"] .fc-theme-standard td, [data-bs-theme="dark"] .fc-theme-standard th { border-color: rgba(255,255,255,0.1); }
|
|
[data-bs-theme="dark"] .fc-day-today { background-color: rgba(255,255,255,0.05) !important; }
|
|
</style>
|
|
|
|
<?php require_once 'includes/footer.php'; ?>
|