add projet

This commit is contained in:
root
2026-03-05 12:29:37 -05:00
parent 8d1eef8ba8
commit 23b7db1107
9109 changed files with 1106501 additions and 73 deletions
+331
View File
@@ -0,0 +1,331 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Calendar;
use App\Models\Configuration;
use Symfony\Component\HttpFoundation\Response;
class SchoolCalendarController extends BaseApiController
{
protected Calendar $calendar;
protected Configuration $config;
protected string $schoolYear;
protected string $semester;
public function __construct()
{
parent::__construct();
$this->calendar = model(Calendar::class);
$this->config = model(Configuration::class);
$this->schoolYear = (string) ($this->config->getConfig('school_year') ?? '');
$this->semester = (string) ($this->config->getConfig('semester') ?? '');
}
/**
* GET /api/v1/school-calendar
* Get calendar events filtered by school_year and optional semester
*/
public function index()
{
$schoolYear = (string) ($this->request->getGet('school_year') ?? $this->schoolYear);
$semester = $this->request->getGet('semester');
try {
$query = $this->calendar->newQuery();
if ($schoolYear !== '') {
$query->where('school_year', $schoolYear);
}
if ($semester !== null && $semester !== '') {
$events = $query->where('semester', $semester)->orderBy('date', 'ASC')->get()->toArray();
} else {
$events = $query->orderBy('date', 'ASC')->get()->toArray();
}
return $this->success([
'events' => $events,
'school_year' => $schoolYear,
'semester' => $semester,
], 'Calendar events retrieved successfully');
} catch (\Throwable $e) {
log_message('error', 'School calendar error: ' . $e->getMessage());
return $this->respondError('Failed to retrieve calendar events', Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* GET /api/v1/school-calendar/{id}
* Get a single calendar event
*/
public function show($id = null)
{
$event = $this->calendar->find($id);
if (!$event) {
return $this->error('Event not found', Response::HTTP_NOT_FOUND);
}
return $this->success($event, 'Event retrieved successfully');
}
/**
* POST /api/v1/school-calendar
* Create a new calendar event
*/
public function store()
{
$payload = $this->payloadData();
if (empty($payload)) {
return $this->error('Invalid request data', Response::HTTP_BAD_REQUEST);
}
$rules = [
'title' => 'required|min_length[3]',
'start' => 'required|valid_date[Y-m-d]',
];
$errors = $this->validateRequest($payload, $rules);
if (!empty($errors)) {
return $this->respondValidationError($errors);
}
$data = [
'title' => trim((string) ($payload['title'] ?? '')),
'description' => trim((string) ($payload['description'] ?? '')),
'date' => trim((string) ($payload['start'] ?? '')),
'notify_parent' => isset($payload['notify_parent']) && $payload['notify_parent'] ? 1 : 0,
'notify_teacher' => isset($payload['notify_teacher']) && $payload['notify_teacher'] ? 1 : 0,
'notify_admin' => isset($payload['notify_admin']) && $payload['notify_admin'] ? 1 : 0,
'no_school' => isset($payload['no_school']) && $payload['no_school'] ? 1 : 0,
'school_year' => trim((string) ($payload['school_year'] ?? $this->schoolYear)),
'semester' => trim((string) ($payload['semester'] ?? $this->semester)),
];
try {
$event = $this->calendar->create($data);
log_message('info', 'Event saved successfully: ' . print_r($data, true));
return $this->success($event, 'Event added successfully', Response::HTTP_CREATED);
} catch (\Throwable $e) {
log_message('error', 'Failed to save event: ' . $e->getMessage());
return $this->respondError('Failed to save the event', Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* PATCH /api/v1/school-calendar/{id}
* Update a calendar event
*/
public function update($id = null)
{
$event = $this->calendar->find($id);
if (!$event) {
return $this->error('Event not found', Response::HTTP_NOT_FOUND);
}
$payload = $this->payloadData();
if (empty($payload)) {
return $this->error('Invalid request data', Response::HTTP_BAD_REQUEST);
}
$rules = [
'title' => 'permit_empty|min_length[3]',
'start' => 'permit_empty|valid_date[Y-m-d]',
];
$errors = $this->validateRequest($payload, $rules);
if (!empty($errors)) {
return $this->respondValidationError($errors);
}
$data = [];
if (isset($payload['title'])) {
$data['title'] = trim((string) $payload['title']);
}
if (isset($payload['description'])) {
$data['description'] = trim((string) $payload['description']);
}
if (isset($payload['start'])) {
$data['date'] = trim((string) $payload['start']);
}
if (isset($payload['notify_parent'])) {
$data['notify_parent'] = $payload['notify_parent'] ? 1 : 0;
}
if (isset($payload['notify_teacher'])) {
$data['notify_teacher'] = $payload['notify_teacher'] ? 1 : 0;
}
if (isset($payload['notify_admin'])) {
$data['notify_admin'] = $payload['notify_admin'] ? 1 : 0;
}
if (isset($payload['no_school'])) {
$data['no_school'] = $payload['no_school'] ? 1 : 0;
}
if (isset($payload['school_year'])) {
$data['school_year'] = trim((string) $payload['school_year']);
}
if (isset($payload['semester'])) {
$data['semester'] = trim((string) $payload['semester']);
}
try {
$this->calendar->update($id, $data);
$updatedEvent = $this->calendar->find($id);
return $this->success($updatedEvent, 'Event updated successfully');
} catch (\Throwable $e) {
log_message('error', 'Failed to update event: ' . $e->getMessage());
return $this->respondError('Failed to update the event', Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* DELETE /api/v1/school-calendar/{id}
* Delete a calendar event
*/
public function delete($id = null)
{
$event = $this->calendar->find($id);
if (!$event) {
return $this->error('Event not found', Response::HTTP_NOT_FOUND);
}
try {
$this->calendar->delete($id);
return $this->success(null, 'Event deleted successfully');
} catch (\Throwable $e) {
log_message('error', 'Failed to delete event: ' . $e->getMessage());
return $this->respondError('Failed to delete the event', Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* GET /api/v1/school-calendar/view/parent
* Get calendar events formatted for parent view
*/
public function calendarParentView()
{
$formattedEvents = $this->calendarView('parent');
return $this->success(['events' => $formattedEvents], 'Parent calendar view retrieved successfully');
}
/**
* GET /api/v1/school-calendar/view/teacher
* Get calendar events formatted for teacher view
*/
public function calendarTeacherView()
{
$formattedEvents = $this->calendarView('teacher');
return $this->success(['events' => $formattedEvents], 'Teacher calendar view retrieved successfully');
}
/**
* GET /api/v1/school-calendar/view/admin
* Get calendar events formatted for admin view
*/
public function calendarAdminView()
{
$formattedEvents = $this->calendarView('admin');
return $this->success(['events' => $formattedEvents], 'Admin calendar view retrieved successfully');
}
/**
* Private method to format calendar events based on audience visibility rules
*/
private function calendarView(?string $audience = null): array
{
// Filter events by school year
$query = $this->calendar->newQuery();
if ($this->schoolYear !== '') {
$query->where('school_year', $this->schoolYear);
}
$events = $query->orderBy('date', 'ASC')->get()->toArray();
// Apply audience visibility rules:
// - If no checkboxes selected (all notify_* = 0) => visible to everyone
// - Otherwise visible only to the selected audience
if (in_array($audience, ['parent', 'teacher', 'admin'], true)) {
$events = array_values(array_filter($events, function ($event) use ($audience) {
$p = (int) ($event['notify_parent'] ?? 0);
$t = (int) ($event['notify_teacher'] ?? 0);
$a = (int) ($event['notify_admin'] ?? 0);
$ns = (int) ($event['no_school'] ?? 0);
$hasAny = ($p + $t + $a) > 0;
// If marked as no_school, it should be visible to all audiences
if ($ns === 1) {
return true;
}
if (!$hasAny) {
// No audience explicitly selected: show to everyone
return true;
}
// Audience explicitly selected: restrict to matching audience
if ($audience === 'parent') {
return $p === 1;
}
if ($audience === 'teacher') {
return $t === 1;
}
if ($audience === 'admin') {
return $a === 1;
}
return true;
}));
}
// Format for FullCalendar
$aud = $audience; // capture for closure
$formattedEvents = array_map(function ($event) use ($aud) {
$p = (int) ($event['notify_parent'] ?? 0);
$t = (int) ($event['notify_teacher'] ?? 0);
$a = (int) ($event['notify_admin'] ?? 0);
$ns = (int) ($event['no_school'] ?? 0);
$backgroundColor = null;
// Color no-school events yellow on teacher and parent calendars
if (in_array($aud, ['teacher', 'parent'], true) && $ns === 1) {
$backgroundColor = '#ffc107'; // yellow
}
// Highlight events dedicated to staff (teachers/admins) only in green
// Definition: visible to staff but not to parents, and not a no-school marker
if (in_array($aud, ['teacher', 'admin'], true) && $backgroundColor === null) {
$isStaffOnly = ($p === 0 && ($t === 1 || $a === 1) && $ns === 0);
if ($isStaffOnly) {
// Use a distinct green to differentiate from the default blue
$backgroundColor = '#28a745';
}
}
// Title-casing for "no school" phrase when marked as no_school
$title = $event['title'] ?? 'Untitled';
if ($ns === 1 && is_string($title)) {
// Replace common variants with Title Case
$title = preg_replace('/\bno\s*[- ]\s*school\b/i', 'No School', $title);
}
return [
'id' => $event['id'],
'title' => $title,
'start' => $event['date'] ?? '', // FullCalendar expects 'start'
'description' => $event['description'] ?? '',
'extendedProps' => [
'semester' => $event['semester'] ?? '',
'school_year' => $event['school_year'] ?? '',
'notify_parent' => $p,
'notify_teacher' => $t,
'notify_admin' => $a,
'no_school' => $ns,
'backgroundColor' => $backgroundColor,
]
];
}, $events);
return $formattedEvents;
}
}