add feature do not show again job for parent portal
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 48s
Tests / PHPUnit (push) Successful in 1m21s

This commit is contained in:
root
2026-09-03 00:02:07 -04:00
parent 46769e8b27
commit 02fd6e4863
6 changed files with 386 additions and 40 deletions
+259 -38
View File
@@ -14,6 +14,7 @@ use App\Models\AttendanceRecordModel;
use App\Models\AttendanceDayModel;
use App\Models\CalendarModel;
use App\Models\JobPositionModel;
use App\Models\PreferencesModel;
use \Config\Database;
use DateTimeImmutable;
use DateTimeZone;
@@ -775,39 +776,6 @@ class LandingPageController extends BaseController
}
// Fetch Notifications (only active, non-expired, non-deleted)
$notifications = $this->db->table('notifications')
->select([
'notifications.id',
'notifications.title',
'notifications.message',
'notifications.target_group',
'notifications.created_at',
'notifications.expires_at',
'user_notifications.user_id',
"CASE
WHEN user_notifications.user_id IS NOT NULL THEN 'personal'
ELSE 'broadcast'
END as notification_type"
])
->join(
'user_notifications',
'user_notifications.notification_id = notifications.id AND user_notifications.user_id = ' . (int) $parentId,
'left'
)
->groupStart()
->where('notifications.target_group', 'parent')
->orWhere('user_notifications.user_id', $parentId)
->groupEnd()
->where('notifications.deleted_at IS NULL') // Exclude soft-deleted notifications
->groupStart()
->where('notifications.expires_at IS NULL')
->orWhere('notifications.expires_at > NOW()') // Exclude expired
->groupEnd()
->orderBy('notifications.created_at', 'DESC')
->get()
->getResultArray();
// Fetch Student Information (no filtering needed by school year or semester)
$students = $this->db->table('students')
@@ -823,6 +791,8 @@ class LandingPageController extends BaseController
}
unset($student);
$notifications = $this->parentDashboardNotifications((int) $parentId, (int) session()->get('user_id'));
// Fetch Attendance Records (filtered by most recent school year and semester)
$attendanceData = $this->db->table('attendance_data')
@@ -870,12 +840,14 @@ class LandingPageController extends BaseController
$paymentBalance = (float) ($paymentRow['account_balance'] ?? 0);
$openPositions = [];
try {
if ($this->db->tableExists('job_positions')) {
$openPositions = (new JobPositionModel())->openPositions();
if (! $this->parentHidesJobOpeningsPopup($parentId)) {
try {
if ($this->db->tableExists('job_positions')) {
$openPositions = (new JobPositionModel())->openPositions();
}
} catch (\Throwable $e) {
log_message('error', 'Unable to load parent dashboard job positions: ' . $e->getMessage());
}
} catch (\Throwable $e) {
log_message('error', 'Unable to load parent dashboard job positions: ' . $e->getMessage());
}
// Pass data to the view, including the deadlines
@@ -892,6 +864,255 @@ class LandingPageController extends BaseController
]);
}
public function hideJobOpeningsPopup()
{
$userId = (int) (session()->get('user_id') ?? 0);
if ($userId <= 0) {
return $this->response->setStatusCode(401)->setJSON([
'ok' => false,
'error' => 'Please log in to update this setting.',
'csrf_token' => csrf_token(),
'csrf_hash' => csrf_hash(),
]);
}
if (
! $this->db->tableExists('user_preferences')
|| ! $this->db->fieldExists('hide_job_openings_popup', 'user_preferences')
) {
return $this->response->setStatusCode(500)->setJSON([
'ok' => false,
'error' => 'Preference storage is not ready.',
'csrf_token' => csrf_token(),
'csrf_hash' => csrf_hash(),
]);
}
$hide = (string) $this->request->getPost('hide_job_openings_popup') === '1' ? 1 : 0;
$preferencesModel = new PreferencesModel();
$existing = $preferencesModel->where('user_id', $userId)->first();
if ($existing) {
$preferencesModel->update((int) $existing['id'], [
'hide_job_openings_popup' => $hide,
]);
} else {
$preferencesModel->insert([
'user_id' => $userId,
'hide_job_openings_popup' => $hide,
]);
}
return $this->response->setJSON([
'ok' => true,
'hide_job_openings_popup' => $hide,
'csrf_token' => csrf_token(),
'csrf_hash' => csrf_hash(),
]);
}
private function parentHidesJobOpeningsPopup(int $parentId): bool
{
if ($parentId <= 0) {
return false;
}
try {
if (
! $this->db->tableExists('user_preferences')
|| ! $this->db->fieldExists('hide_job_openings_popup', 'user_preferences')
) {
return false;
}
$preferences = (new PreferencesModel())->where('user_id', $parentId)->first();
return ! empty($preferences['hide_job_openings_popup']);
} catch (\Throwable $e) {
log_message('error', 'Unable to load parent job openings popup preference: ' . $e->getMessage());
return false;
}
}
private function parentDashboardNotifications(int $parentId, int $userId): array
{
$notifications = array_merge(
$this->parentAttendanceNotifications($parentId),
$this->activeParentBroadcastNotifications($parentId, $userId)
);
usort($notifications, static function (array $a, array $b): int {
return strtotime((string) ($b['created_at'] ?? '')) <=> strtotime((string) ($a['created_at'] ?? ''));
});
return array_slice($notifications, 0, 25);
}
private function parentAttendanceNotifications(int $parentId): array
{
if (
$parentId <= 0
|| ! $this->db->tableExists('parent_notifications')
|| ! $this->db->tableExists('students')
) {
return [];
}
$rows = $this->db->table('parent_notifications pn')
->select([
'pn.id',
'pn.student_id',
'pn.code',
'pn.incident_date',
'pn.channel',
'pn.subject',
'pn.status',
'pn.response',
'pn.semester',
'pn.school_year',
'pn.created_at',
'pn.updated_at',
'students.firstname',
'students.lastname',
])
->join('students', 'students.id = pn.student_id')
->where('students.parent_id', $parentId)
->where('pn.school_year', (string) $this->schoolYear)
->where('pn.semester', (string) $this->semester)
->orderBy('COALESCE(pn.updated_at, pn.created_at)', 'DESC', false)
->orderBy('pn.id', 'DESC')
->limit(100)
->get()
->getResultArray();
$notifications = [];
$seen = [];
foreach ($rows as $row) {
$key = implode('|', [
(string) ($row['student_id'] ?? ''),
(string) ($row['code'] ?? ''),
(string) ($row['incident_date'] ?? ''),
(string) ($row['subject'] ?? ''),
]);
if (isset($seen[$key])) {
continue;
}
$seen[$key] = true;
$studentName = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? ''));
$code = strtoupper((string) ($row['code'] ?? ''));
$incidentDate = (string) ($row['incident_date'] ?? '');
$subject = trim((string) ($row['subject'] ?? ''));
$title = $subject !== '' ? $subject : $this->parentNotificationCodeLabel($code);
if ($studentName !== '') {
$title .= ' - ' . $studentName;
}
$messageParts = [];
if ($incidentDate !== '') {
$messageParts[] = 'Incident date: ' . $incidentDate;
}
if (!empty($row['status'])) {
$messageParts[] = 'Status: ' . ucfirst((string) $row['status']);
}
$notifications[] = [
'id' => $row['id'] ?? null,
'title' => $title,
'message' => implode(' | ', $messageParts),
'created_at' => $row['updated_at'] ?: ($row['created_at'] ?? null),
'notification_type' => $this->parentNotificationCodeLabel($code),
'status' => $row['status'] ?? null,
'code' => $code,
'incident_date' => $incidentDate,
'student_name' => $studentName,
];
if (count($notifications) >= 25) {
break;
}
}
return $notifications;
}
private function activeParentBroadcastNotifications(int $parentId, int $userId): array
{
if ($parentId <= 0 || ! $this->db->tableExists('notifications')) {
return [];
}
$builder = $this->db->table('notifications')
->select([
'notifications.id',
'notifications.title',
'notifications.message',
'notifications.target_group',
'notifications.created_at',
'notifications.expires_at',
"CASE
WHEN user_notifications.user_id IS NOT NULL THEN 'personal'
ELSE 'broadcast'
END as notification_type",
])
->join(
'user_notifications',
'user_notifications.notification_id = notifications.id AND user_notifications.user_id IN (' . implode(',', array_unique([$parentId, $userId])) . ')',
'left'
)
->groupStart()
->whereIn('notifications.target_group', ['parent', 'everyone'])
->orWhere('user_notifications.user_id IS NOT NULL')
->groupEnd()
->where('notifications.deleted_at IS NULL')
->groupStart()
->where('notifications.scheduled_at IS NULL')
->orWhere('notifications.scheduled_at <=', utc_now())
->groupEnd()
->groupStart()
->where('notifications.expires_at IS NULL')
->orWhere('notifications.expires_at >', utc_now())
->groupEnd();
if ($this->db->fieldExists('school_year', 'notifications')) {
$builder->where('notifications.school_year', (string) $this->schoolYear);
}
if ($this->db->fieldExists('semester', 'notifications')) {
$builder->groupStart()
->where('notifications.semester', null)
->orWhere('notifications.semester', '')
->orWhere('notifications.semester', (string) $this->semester)
->groupEnd();
}
return $builder
->groupBy('notifications.id')
->orderBy('notifications.created_at', 'DESC')
->limit(25)
->get()
->getResultArray();
}
private function parentNotificationCodeLabel(string $code): string
{
return match ($code) {
'ABS_1' => 'Unreported absence',
'ABS_2' => 'Repeated absences',
'ABS_3' => 'Attendance warning',
'ABS_4' => 'Attendance review',
'LATE_2' => 'Repeated lateness',
'LATE_3' => 'Lateness warning',
'LATE_4' => 'Lateness review',
'MIX_L2A1' => 'Attendance warning',
default => 'Parent notice',
};
}
public function guest()
{
return view('/landing_page/guest_dashboard');