fix calendar page
API CI/CD / Validate (composer + pint) (push) Successful in 3m9s
API CI/CD / Test (PHPUnit) (push) Failing after 2m0s
API CI/CD / Build frontend assets (push) Failing after 1m55s
API CI/CD / Security audit (push) Failing after 55s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
API CI/CD / Validate (composer + pint) (push) Successful in 3m9s
API CI/CD / Test (PHPUnit) (push) Failing after 2m0s
API CI/CD / Build frontend assets (push) Failing after 1m55s
API CI/CD / Security audit (push) Failing after 55s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# ────────────────────────────────────
|
||||
# Docker Dev Environment
|
||||
# ────────────────────────────────────
|
||||
NODE_ENV=development
|
||||
|
||||
# ────────────────────────────────────
|
||||
# DATABASE (PostgreSQL via docker)
|
||||
# ────────────────────────────────────
|
||||
DATABASE_URL=postgresql://postgres:password@postgres:5432/rentaldrivego
|
||||
|
||||
# ────────────────────────────────────
|
||||
# REDIS
|
||||
# ────────────────────────────────────
|
||||
REDIS_URL=redis://redis:6379
|
||||
|
||||
# ────────────────────────────────────
|
||||
# JWT
|
||||
# ────────────────────────────────────
|
||||
JWT_SECRET=bvDokCe6RD7Jb5cxJ6P5z4rdTwS37hyUgglD8HGCE6Xwo6g3SEuZ9XNzjou53raUf6B2jMcEWxQm0Y5zcw2THw
|
||||
JWT_EXPIRY=8h
|
||||
|
||||
# ────────────────────────────────────
|
||||
# FILE STORAGE
|
||||
# ────────────────────────────────────
|
||||
FILE_STORAGE_ROOT=/var/lib/rentaldrivego/storage
|
||||
|
||||
# ────────────────────────────────────
|
||||
# URLS (docker services)
|
||||
# ────────────────────────────────────
|
||||
API_URL=http://localhost:4000
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=4000
|
||||
API_INTERNAL_URL=http://api:4000
|
||||
ADMIN_URL=http://localhost:3002
|
||||
DASHBOARD_URL=http://localhost:3001
|
||||
MARKETPLACE_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_ADMIN_URL=http://localhost:3002
|
||||
NEXT_PUBLIC_DASHBOARD_URL=http://localhost:3001
|
||||
NEXT_PUBLIC_MARKETPLACE_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_CUSTOM_DOMAIN_TARGET=http://localhost:3000
|
||||
|
||||
# ────────────────────────────────────
|
||||
# CORS
|
||||
# ────────────────────────────────────
|
||||
CORS_ORIGINS=http://localhost:3000,http://localhost:3001,http://localhost:3002
|
||||
|
||||
# ────────────────────────────────────
|
||||
# MAIL (log driver in dev)
|
||||
# ────────────────────────────────────
|
||||
MAIL_HOST=mailhog
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=
|
||||
MAIL_PASSWORD=
|
||||
MAIL_ENCRYPTION=none
|
||||
MAIL_FROM_ADDRESS=dev@rentaldrivego.local
|
||||
MAIL_FROM_NAME="RentalDriveGo Dev"
|
||||
MAIL_REPLY_TO_ADDRESS=dev@rentaldrivego.local
|
||||
MAIL_REPLY_TO_NAME="RentalDriveGo Dev"
|
||||
|
||||
# ────────────────────────────────────
|
||||
# ADMIN SEED (initial dev account)
|
||||
# ────────────────────────────────────
|
||||
ADMIN_SEED_EMAIL=admin@rentaldrivego.dev
|
||||
ADMIN_SEED_FIRST_NAME=Dev
|
||||
ADMIN_SEED_LAST_NAME=Admin
|
||||
ADMIN_SEED_PASSWORD=password
|
||||
|
||||
# ────────────────────────────────────
|
||||
# EXTERNAL SERVICES (blank in dev)
|
||||
# ────────────────────────────────────
|
||||
AMANPAY_BASE_URL=
|
||||
AMANPAY_MERCHANT_ID=
|
||||
AMANPAY_SECRET_KEY=
|
||||
AMANPAY_WEBHOOK_SECRET=
|
||||
PAYPAL_BASE_URL=
|
||||
PAYPAL_CLIENT_ID=
|
||||
PAYPAL_CLIENT_SECRET=
|
||||
PAYPAL_WEBHOOK_ID=
|
||||
TWILIO_ACCOUNT_SID=
|
||||
TWILIO_AUTH_TOKEN=
|
||||
TWILIO_PHONE_NUMBER=
|
||||
TWILIO_WHATSAPP_NUMBER=
|
||||
FIREBASE_CLIENT_EMAIL=
|
||||
FIREBASE_PRIVATE_KEY=
|
||||
FIREBASE_PROJECT_ID=
|
||||
RESEND_API_KEY=
|
||||
EMAIL_FROM=
|
||||
EMAIL_FROM_NAME=
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
APP_NAME=Alrahma_API
|
||||
APP_ENV=production
|
||||
APP_KEY=base64:CHANGE_ME_RUN_php_artisan_key_generate
|
||||
APP_KEY=base64:bvDokCe6RD7Jb5cxJ6P5z4rdTwS37hyUgglD8HGCE6Xwo6g3SEuZ9XNzjou53raUf6B2jMcEWxQm0Y5zcw2THw
|
||||
APP_DEBUG=false
|
||||
APP_TIMEZONE=America/New_York
|
||||
APP_URL=https://api.alrahmaisgl.org
|
||||
@@ -103,7 +103,7 @@ AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
# ----------------------------
|
||||
# JWT
|
||||
# ----------------------------
|
||||
JWT_SECRET=CHANGE_ME_USE_STRONG_RANDOM_SECRET
|
||||
JWT_SECRET=d7UbttsiymIIh-5nHw4tYoYqwGjZX5VhBtR-FnWaBTpXX0uP6D4EWx86fxYdLKMk0wIEV5xeNUIRjlNBG-eeHw
|
||||
JWT_ALGO=HS256
|
||||
JWT_TTL=60
|
||||
JWT_REFRESH_TTL=20160
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Web;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Settings\SchoolCalendar\SchoolCalendarContextService;
|
||||
use App\Services\Settings\SchoolCalendar\SchoolCalendarQueryService;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AdminCalendarPageController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private SchoolCalendarContextService $contextService,
|
||||
private SchoolCalendarQueryService $queryService,
|
||||
) {}
|
||||
|
||||
public function show(Request $request): View
|
||||
{
|
||||
$schoolYear = trim((string) $request->query('school_year', ''));
|
||||
if ($schoolYear === '') {
|
||||
$schoolYear = $this->contextService->defaultSchoolYear();
|
||||
}
|
||||
|
||||
$semester = trim((string) $request->query('semester', ''));
|
||||
$filters = ['school_year' => $schoolYear];
|
||||
if ($semester !== '') {
|
||||
$filters['semester'] = $semester;
|
||||
}
|
||||
|
||||
$events = $this->queryService->listEvents($filters);
|
||||
|
||||
return view('admin.calendar', [
|
||||
'events' => $events,
|
||||
'schoolYear' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'defaultSemester' => $semester !== '' ? $semester : $this->contextService->defaultSemester(),
|
||||
'schoolYears' => $this->schoolYearOptions($schoolYear),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function schoolYearOptions(string $selectedSchoolYear): array
|
||||
{
|
||||
$currentYear = (int) date('Y') + 1;
|
||||
$years = [];
|
||||
for ($year = $currentYear; $year >= 2023; $year--) {
|
||||
$years[] = ($year - 1).'-'.$year;
|
||||
}
|
||||
|
||||
if ($selectedSchoolYear !== '' && ! in_array($selectedSchoolYear, $years, true)) {
|
||||
array_unshift($years, $selectedSchoolYear);
|
||||
}
|
||||
|
||||
return array_values(array_unique($years));
|
||||
}
|
||||
}
|
||||
Vendored
-19
@@ -20,13 +20,6 @@
|
||||
0 => 'Laravel\\Sanctum\\SanctumServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/tinker' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
),
|
||||
),
|
||||
'nesbot/carbon' =>
|
||||
array (
|
||||
'providers' =>
|
||||
@@ -48,16 +41,4 @@
|
||||
0 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
),
|
||||
),
|
||||
'php-open-source-saver/jwt-auth' =>
|
||||
array (
|
||||
'aliases' =>
|
||||
array (
|
||||
'JWTAuth' => 'PHPOpenSourceSaver\\JWTAuth\\Facades\\JWTAuth',
|
||||
'JWTFactory' => 'PHPOpenSourceSaver\\JWTAuth\\Facades\\JWTFactory',
|
||||
),
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'PHPOpenSourceSaver\\JWTAuth\\Providers\\LaravelServiceProvider',
|
||||
),
|
||||
),
|
||||
);
|
||||
Vendored
+9
-16
@@ -27,14 +27,12 @@
|
||||
23 => 'Laravel\\Pail\\PailServiceProvider',
|
||||
24 => 'Laravel\\Sail\\SailServiceProvider',
|
||||
25 => 'Laravel\\Sanctum\\SanctumServiceProvider',
|
||||
26 => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
27 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
28 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
29 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
30 => 'PHPOpenSourceSaver\\JWTAuth\\Providers\\LaravelServiceProvider',
|
||||
31 => 'App\\Providers\\AppServiceProvider',
|
||||
32 => 'App\\Providers\\EventServiceProvider',
|
||||
33 => 'Laravel\\Sanctum\\SanctumServiceProvider',
|
||||
26 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
27 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
28 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
29 => 'App\\Providers\\AppServiceProvider',
|
||||
30 => 'App\\Providers\\EventServiceProvider',
|
||||
31 => 'Laravel\\Sanctum\\SanctumServiceProvider',
|
||||
),
|
||||
'eager' =>
|
||||
array (
|
||||
@@ -53,10 +51,9 @@
|
||||
12 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
13 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
14 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
15 => 'PHPOpenSourceSaver\\JWTAuth\\Providers\\LaravelServiceProvider',
|
||||
16 => 'App\\Providers\\AppServiceProvider',
|
||||
17 => 'App\\Providers\\EventServiceProvider',
|
||||
18 => 'Laravel\\Sanctum\\SanctumServiceProvider',
|
||||
15 => 'App\\Providers\\AppServiceProvider',
|
||||
16 => 'App\\Providers\\EventServiceProvider',
|
||||
17 => 'Laravel\\Sanctum\\SanctumServiceProvider',
|
||||
),
|
||||
'deferred' =>
|
||||
array (
|
||||
@@ -216,7 +213,6 @@
|
||||
'Illuminate\\Contracts\\Validation\\UncompromisedVerifier' => 'Illuminate\\Validation\\ValidationServiceProvider',
|
||||
'Laravel\\Sail\\Console\\InstallCommand' => 'Laravel\\Sail\\SailServiceProvider',
|
||||
'Laravel\\Sail\\Console\\PublishCommand' => 'Laravel\\Sail\\SailServiceProvider',
|
||||
'command.tinker' => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
),
|
||||
'when' =>
|
||||
array (
|
||||
@@ -262,8 +258,5 @@
|
||||
'Laravel\\Sail\\SailServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
'Laravel\\Tinker\\TinkerServiceProvider' =>
|
||||
array (
|
||||
),
|
||||
),
|
||||
);
|
||||
+2
-6
@@ -6,15 +6,11 @@
|
||||
"keywords": ["laravel", "framework"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"php": "^8.4",
|
||||
"chillerlan/php-qrcode": "^5.0",
|
||||
"dompdf/dompdf": "^3.1",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/sanctum": "^4.3",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"php-open-source-saver/jwt-auth": "^2.8",
|
||||
"setasign/fpdf": "^1.8",
|
||||
"tecnickcom/tcpdf": "^6.11"
|
||||
"laravel/sanctum": "^4.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
|
||||
Generated
+225
-795
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,350 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>School Calendar</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f5f7fb;
|
||||
--panel: #ffffff;
|
||||
--line: #d9e0ea;
|
||||
--ink: #172033;
|
||||
--muted: #637083;
|
||||
--success: #198754;
|
||||
--primary: #0d6efd;
|
||||
--danger: #dc3545;
|
||||
--secondary: #6c757d;
|
||||
--warning-bg: #fff3cd;
|
||||
--warning-line: #ffec99;
|
||||
--shadow: 0 12px 32px rgba(23, 32, 51, 0.08);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
color: var(--ink);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
a,
|
||||
button,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
max-width: 1380px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 18px;
|
||||
text-align: center;
|
||||
font-size: 1.9rem;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.filter-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: inline-block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
min-width: 150px;
|
||||
padding: 8px 34px 8px 10px;
|
||||
border: 1px solid #c8d1dc;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 36px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
min-height: 30px;
|
||||
padding: 5px 9px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.btn-success { background: var(--success); }
|
||||
.btn-primary { background: var(--primary); }
|
||||
.btn-danger { background: var(--danger); }
|
||||
.btn-secondary { background: var(--secondary); }
|
||||
.btn-outline-secondary {
|
||||
background: #fff;
|
||||
border-color: var(--secondary);
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin-bottom: 16px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
color: #0f5132;
|
||||
background: #d1e7dd;
|
||||
border: 1px solid #badbcc;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
color: #842029;
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c2c7;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
color: #055160;
|
||||
background: #cff4fc;
|
||||
border: 1px solid #b6effb;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #eef3f8;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) {
|
||||
background: #f9fbfd;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.small { font-size: 0.875rem; }
|
||||
.table-warning {
|
||||
background: var(--warning-bg);
|
||||
border-color: var(--warning-line);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.delete-form {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.container-fluid {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.toolbar .btn,
|
||||
.filter-form .btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="wrapper">
|
||||
<div class="content">
|
||||
<h2>School Calendar</h2>
|
||||
|
||||
@php
|
||||
$addParams = array_filter([
|
||||
'school_year' => $schoolYear ?? '',
|
||||
'semester' => $semester ?? '',
|
||||
], fn ($value) => $value !== '');
|
||||
$addQuery = $addParams ? ('?'.http_build_query($addParams)) : '';
|
||||
$resetUrl = url()->current();
|
||||
@endphp
|
||||
|
||||
<div class="toolbar">
|
||||
<a href="{{ url('/administrator/calendar_add_event') }}{{ $addQuery }}" class="btn btn-success">Add School Calendar Entry</a>
|
||||
</div>
|
||||
|
||||
<form method="get" action="{{ url()->current() }}" class="filter-form">
|
||||
<div>
|
||||
<label for="school_year" class="form-label">School Year:</label>
|
||||
<select name="school_year" id="school_year" class="form-select">
|
||||
@foreach ($schoolYears as $option)
|
||||
<option value="{{ $option }}" @selected($option === ($schoolYear ?? ''))>{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="semester" class="form-label">Semester:</label>
|
||||
<select name="semester" id="semester" class="form-select">
|
||||
<option value="">-</option>
|
||||
<option value="Fall" @selected(strcasecmp((string) ($semester ?? ''), 'Fall') === 0)>Fall</option>
|
||||
<option value="Spring" @selected(strcasecmp((string) ($semester ?? ''), 'Spring') === 0)>Spring</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-secondary">Apply</button>
|
||||
<a href="{{ $resetUrl }}" class="btn btn-outline-secondary">Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="alert alert-success">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
@php
|
||||
$emailStatus = session('email_status');
|
||||
$emailStatusClass = session('email_status_class', 'info');
|
||||
@endphp
|
||||
|
||||
@if ($emailStatus)
|
||||
<div class="alert alert-{{ $emailStatusClass }}">{{ $emailStatus }}</div>
|
||||
@endif
|
||||
|
||||
@if (session('error'))
|
||||
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="myTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Title</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Date</th>
|
||||
<th>Notify Parent</th>
|
||||
<th>Notify Teacher</th>
|
||||
<th>Notify Admin</th>
|
||||
<th>No School</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if ($events->isNotEmpty())
|
||||
@if ($emailStatus)
|
||||
<tr class="table-warning">
|
||||
<td colspan="10" class="text-center small">{{ $emailStatus }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
|
||||
@foreach ($events as $event)
|
||||
<tr>
|
||||
<td>{{ $event->id }}</td>
|
||||
<td>{{ $event->title }}</td>
|
||||
<td>{{ $event->event_type ?: '-' }}</td>
|
||||
<td>{{ $event->description }}</td>
|
||||
<td>{{ optional($event->date)->format('m-d-Y') }}</td>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" disabled @checked((bool) $event->notify_parent)>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" disabled @checked((bool) $event->notify_teacher)>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" disabled @checked((bool) $event->notify_admin)>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" disabled @checked((bool) $event->no_school)>
|
||||
</td>
|
||||
<td>
|
||||
<div class="actions">
|
||||
<a href="{{ url('/administrator/calendar_edit/'.$event->id) }}" class="btn btn-primary btn-sm">Edit</a>
|
||||
<form action="{{ url('/administrator/calendar_delete/'.$event->id) }}" method="post" class="delete-form">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@else
|
||||
<tr>
|
||||
<td colspan="10" class="text-center">No school calendar entries found</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('.delete-form').forEach((form) => {
|
||||
form.addEventListener('submit', (event) => {
|
||||
if (!window.confirm('Are you sure you want to delete this school calendar entry?')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,6 +3,7 @@
|
||||
use App\Http\Controllers\Api\Auth\AuthSessionController;
|
||||
use App\Http\Controllers\Api\Auth\SessionTimeoutController;
|
||||
use App\Http\Controllers\Api\System\DashboardRedirectController;
|
||||
use App\Http\Controllers\Web\AdminCalendarPageController;
|
||||
use App\Http\Controllers\Web\SchoolYearAdminPageController;
|
||||
use App\Http\Controllers\Web\TeacherCalendarPageController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
@@ -81,6 +82,10 @@ Route::middleware('web')->group(function () {
|
||||
->name('teacher.calendar');
|
||||
Route::middleware('auth')->get('app/teacher/calendar', [TeacherCalendarPageController::class, 'show'])
|
||||
->name('app.teacher.calendar');
|
||||
Route::middleware(['auth', 'admin.access'])->get('administrator/calendar_view', [AdminCalendarPageController::class, 'show'])
|
||||
->name('admin.calendar-view');
|
||||
Route::middleware(['auth', 'admin.access'])->get('app/administrator/calendar_view', [AdminCalendarPageController::class, 'show'])
|
||||
->name('app.admin.calendar-view');
|
||||
Route::middleware(['auth', 'admin.access'])->get('administrator/school-years', [SchoolYearAdminPageController::class, 'show'])
|
||||
->name('admin.school-years');
|
||||
Route::middleware(['auth', 'admin.access'])->get('app/administrator/school-years', [SchoolYearAdminPageController::class, 'show'])
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Web;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AdminCalendarPageTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$compiledPath = storage_path('framework/views');
|
||||
if (! is_dir($compiledPath)) {
|
||||
mkdir($compiledPath, 0777, true);
|
||||
}
|
||||
|
||||
config([
|
||||
'app.key' => 'base64:'.base64_encode(random_bytes(32)),
|
||||
'app.cipher' => 'AES-256-CBC',
|
||||
'view.compiled' => $compiledPath,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_admin_calendar_view_lists_school_calendar_events_for_selected_year(): void
|
||||
{
|
||||
$this->seedAdminContext();
|
||||
|
||||
DB::table('calendar_events')->insert([
|
||||
'title' => 'Back to School Night',
|
||||
'date' => '2025-09-14',
|
||||
'description' => 'Families meet teachers.',
|
||||
'notify_parent' => 1,
|
||||
'notify_admin' => 1,
|
||||
'notify_teacher' => 1,
|
||||
'no_school' => 0,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'notification_sent' => 0,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
DB::table('calendar_events')->insert([
|
||||
'title' => 'Other Year Event',
|
||||
'date' => '2026-09-13',
|
||||
'description' => 'Should not render for the selected year.',
|
||||
'notify_parent' => 1,
|
||||
'notify_admin' => 1,
|
||||
'notify_teacher' => 1,
|
||||
'no_school' => 0,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2026-2027',
|
||||
'notification_sent' => 0,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$response = $this->actingAs(User::query()->findOrFail(1))
|
||||
->get('/app/administrator/calendar_view?school_year=2025-2026');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertSee('School Calendar');
|
||||
$response->assertSee('Add School Calendar Entry');
|
||||
$response->assertSee('Notify Parent');
|
||||
$response->assertSee('No School');
|
||||
$response->assertSee('Back to School Night');
|
||||
$response->assertSee('Families meet teachers.');
|
||||
$response->assertDontSee('Other Year Event');
|
||||
}
|
||||
|
||||
private function seedAdminContext(): void
|
||||
{
|
||||
DB::table('configuration')->insert([
|
||||
['config_key' => 'school_year', 'config_value' => '2025-2026'],
|
||||
['config_key' => 'semester', 'config_value' => 'Fall'],
|
||||
]);
|
||||
|
||||
DB::table('roles')->insert([
|
||||
'id' => 1,
|
||||
'name' => 'administrator',
|
||||
'slug' => 'administrator',
|
||||
'is_active' => 1,
|
||||
]);
|
||||
|
||||
DB::table('users')->insert([
|
||||
'id' => 1,
|
||||
'school_id' => 1,
|
||||
'firstname' => 'Admin',
|
||||
'lastname' => 'User',
|
||||
'gender' => 'Female',
|
||||
'cellphone' => '5555555555',
|
||||
'email' => 'admin-calendar@example.com',
|
||||
'address_street' => '123 Main',
|
||||
'city' => 'City',
|
||||
'state' => 'ST',
|
||||
'zip' => '12345',
|
||||
'accept_school_policy' => 1,
|
||||
'is_verified' => 1,
|
||||
'status' => 'Active',
|
||||
'is_suspended' => 0,
|
||||
'failed_attempts' => 0,
|
||||
'password' => bcrypt('secret'),
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
DB::table('user_roles')->insert([
|
||||
'user_id' => 1,
|
||||
'role_id' => 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user