From 5e8fa682b9428dc8e6cb4e79958e09c3b70ff0cf Mon Sep 17 00:00:00 2001 From: root Date: Sat, 25 Apr 2026 21:56:33 -0400 Subject: [PATCH] fix class progress admin side --- .../ClassProgressIndexRequest.php | 2 +- .../Classes/ClassSectionIndexRequest.php | 1 + .../ClassProgressGroupCollection.php | 11 +- app/Services/Badges/BadgePdfService.php | 615 +++++++++++++----- .../ClassSectionQueryService.php | 14 + config/badges.php | 4 +- public/badges.png | Bin 0 -> 100422 bytes public/logo-circle.png | Bin 0 -> 115443 bytes public/logo.png | Bin 0 -> 115187 bytes 9 files changed, 475 insertions(+), 172 deletions(-) create mode 100644 public/badges.png create mode 100644 public/logo-circle.png create mode 100644 public/logo.png diff --git a/app/Http/Requests/ClassProgress/ClassProgressIndexRequest.php b/app/Http/Requests/ClassProgress/ClassProgressIndexRequest.php index 27b72938..5eab4020 100644 --- a/app/Http/Requests/ClassProgress/ClassProgressIndexRequest.php +++ b/app/Http/Requests/ClassProgress/ClassProgressIndexRequest.php @@ -24,7 +24,7 @@ class ClassProgressIndexRequest extends ClassProgressFormRequest 'status' => $statuses ? ['nullable', 'in:' . implode(',', $statuses)] : ['nullable', 'string'], 'group_by_week' => ['nullable', 'boolean'], 'page' => ['nullable', 'integer', 'min:1'], - 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], + 'per_page' => ['nullable', 'integer', 'min:1', 'max:200'], 'sort_by' => ['nullable', 'in:week_start,week_end,subject,status,created_at'], 'sort_dir' => ['nullable', 'in:asc,desc'], ]; diff --git a/app/Http/Requests/Classes/ClassSectionIndexRequest.php b/app/Http/Requests/Classes/ClassSectionIndexRequest.php index 6595737f..8cc03677 100644 --- a/app/Http/Requests/Classes/ClassSectionIndexRequest.php +++ b/app/Http/Requests/Classes/ClassSectionIndexRequest.php @@ -18,6 +18,7 @@ class ClassSectionIndexRequest extends ApiFormRequest 'class_id' => ['nullable', 'integer', 'min:1'], 'school_year' => ['nullable', 'string', 'max:9'], 'semester' => ['nullable', 'string', 'max:255'], + 'with_students' => ['nullable', 'boolean'], 'sort_by' => ['nullable', 'string', 'in:class_section_name,class_section_id,class_id,school_year,semester,class_name'], 'sort_dir' => ['nullable', 'string', 'in:asc,desc'], 'page' => ['nullable', 'integer', 'min:1'], diff --git a/app/Http/Resources/ClassProgress/ClassProgressGroupCollection.php b/app/Http/Resources/ClassProgress/ClassProgressGroupCollection.php index 7b4603b8..daa20591 100644 --- a/app/Http/Resources/ClassProgress/ClassProgressGroupCollection.php +++ b/app/Http/Resources/ClassProgress/ClassProgressGroupCollection.php @@ -17,16 +17,19 @@ class ClassProgressGroupCollection extends ResourceCollection { $groups = []; foreach ($paginator->items() as $report) { - $key = $report->week_start?->format('Y-m-d') ?? ''; - if ($key === '') { + $weekStart = $report->week_start?->format('Y-m-d') ?? ''; + $sectionId = (int) ($report->class_section_id ?? 0); + if ($weekStart === '' || $sectionId <= 0) { continue; } + $key = $sectionId . '|' . $weekStart; + if (!isset($groups[$key])) { $groups[$key] = [ - 'week_start' => $key, + 'week_start' => $weekStart, 'week_end' => $report->week_end?->format('Y-m-d'), - 'class_section_id' => $report->class_section_id, + 'class_section_id' => $sectionId, 'class_section_name' => $report->classSection?->class_section_name ?? '', 'reports' => [], ]; diff --git a/app/Services/Badges/BadgePdfService.php b/app/Services/Badges/BadgePdfService.php index 17f71b98..4f0e07e8 100644 --- a/app/Services/Badges/BadgePdfService.php +++ b/app/Services/Badges/BadgePdfService.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Services\Badges; use chillerlan\QRCode\Output\QRGdImagePNG; +use chillerlan\QRCode\Output\QRMarkupSVG; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; use Dompdf\Dompdf; @@ -56,19 +57,21 @@ class BadgePdfService $motto = (string) config('badges.motto', ''); $studentRoleLabel = (string) config('badges.role_label', 'Student'); $footerBlock = $this->footerBlock($schoolName); - $logoPath = $this->logoFilePath(); + $logoPath = $this->preparedLogoPath(); $rows = []; $students = $this->studentLookupService->fetchForBadges($studentIds, $schoolYear); foreach ($students as $row) { try { + $qrPayload = (string) ($row['school_id'] ?? ''); $rows[] = array_merge($row, [ '_badge_kind' => 'student', 'role_subline' => $studentRoleLabel, 'show_grade' => true, 'grade_label' => 'Grade', - '_qr_base64' => base64_encode($this->renderQrPng((string) $row['school_id'])), + '_qr_src' => $this->renderQrSvgDataUri($qrPayload), + '_qr_base64' => $this->renderQrPngBase64($qrPayload), ]); } catch (Throwable) { continue; @@ -89,23 +92,32 @@ class BadgePdfService $rows[] = array_merge($staffRow, [ '_badge_kind' => 'staff', - '_qr_base64' => base64_encode($this->renderQrPng($qrPayload)), + '_qr_src' => $this->renderQrSvgDataUri($qrPayload), + '_qr_base64' => $this->renderQrPngBase64($qrPayload), ]); } catch (Throwable) { continue; } } - // Use the fixed-coordinate FPDF renderer for badges so the output - // always stays at a strict 4x2 layout (8 badges per page). - $binary = $this->renderWithFpdf( - $rows, - $schoolName, - $motto, - $studentRoleLabel, - $footerBlock, - $logoPath - ); + try { + $binary = $this->renderWithDompdf( + $rows, + $schoolName, + $motto, + $footerBlock + ); + } catch (Throwable) { + // Keep the fixed-coordinate FPDF renderer as a fallback when Dompdf fails. + $binary = $this->renderWithFpdf( + $rows, + $schoolName, + $motto, + $studentRoleLabel, + $footerBlock, + $logoPath + ); + } $logIds = array_values(array_unique(array_merge($studentIds, $userIds))); $this->printLogService->logSafely( @@ -308,7 +320,7 @@ class BadgePdfService 'academic_year' => $year !== '' ? $year : '—', 'school_id' => $schoolId, 'role_subline' => $display, - 'show_grade' => $isTeacherish && !$isAdmin && $gradeCol !== '—', + 'show_grade' => true, 'grade_label' => 'Class / Grade', ]; } @@ -373,11 +385,16 @@ class BadgePdfService $grade = $this->escapeHtml((string) ($row['grade'] ?? '—')); $year = $this->escapeHtml((string) ($row['academic_year'] ?? '—')); $schoolId = $this->escapeHtml((string) ($row['school_id'] ?? '—')); - $qrBase64 = trim((string) ($row['_qr_base64'] ?? '')); + $qrSrc = trim((string) ($row['_qr_src'] ?? '')); $footer = nl2br($this->escapeHtml($footerBlock)); - $barcodeDigits = preg_replace('/[^A-Za-z0-9]/', '', (string) ($row['school_id'] ?? '')); - $barcodeDigits = $barcodeDigits !== '' ? str_repeat($barcodeDigits, 4) : '12345678901234567890'; + $barcodeValue = preg_replace('/[^A-Za-z0-9]/', '', (string) ($row['school_id'] ?? '')); + $barcodeValue = $barcodeValue !== '' ? $barcodeValue : 'ID0000'; + $barcodeText = $this->escapeHtml(str_repeat($barcodeValue, 4)); $showGrade = (bool) ($row['show_grade'] ?? ($kind === 'student')); + $schoolLevel = trim((string) config('badges.header_subtitle', '')); + $headerMotto = trim($motto) !== '' + ? trim($motto) + : trim((string) config('badges.motto_fallback', '')); $infoRows = ''; if ($showGrade) { @@ -419,8 +436,12 @@ class BadgePdfService '
' . $this->escapeHtml($schoolName) . '
-
High School
-
' . $this->escapeHtml($motto) . '
+ ' . ($schoolLevel !== '' + ? '
' . $this->escapeHtml($schoolLevel) . '
' + : '') . ' + ' . ($headerMotto !== '' + ? '
' . $this->escapeHtml($headerMotto) . '
' + : '') . '
@@ -444,8 +465,8 @@ class BadgePdfService
' - . ($qrBase64 !== '' - ? 'QR Code' + . ($qrSrc !== '' + ? 'QR Code' : '') . '
Scan to verify
@@ -453,8 +474,10 @@ class BadgePdfService -
-
' . $this->escapeHtml($barcodeDigits) . '
+
+
' . $this->buildBarcodeHtml($barcodeValue) . '
+
' . $barcodeText . '
+