feat: add Open Graph meta tags for social preview

This commit is contained in:
Novy San87
2026-07-23 11:59:12 +07:00
parent 9e1b5c34e5
commit fb5e349ae6
6 changed files with 269 additions and 52 deletions
+166 -14
View File
@@ -364,6 +364,7 @@ class AttendanceController extends Controller
$students = collect(); $students = collect();
$recap = collect(); $recap = collect();
$totals = [];
if ($className) { if ($className) {
$students = Student::where('is_active', true) $students = Student::where('is_active', true)
->where('class_name', $className) ->where('class_name', $className)
@@ -378,35 +379,74 @@ class AttendanceController extends Controller
foreach ($weeks as $weekNum => $week) { foreach ($weeks as $weekNum => $week) {
$weekAtt = $allAttendances->filter(fn($a) => $weekAtt = $allAttendances->filter(fn($a) =>
$a->date >= $week['start'] && $a->date <= $week['end'] $a->date >= $week['start'] && $a->date <= $week['end']
)->groupBy('student_id'); );
// Ambil tanggal unik dalam minggu ini
$datesInWeek = $weekAtt->map(fn($a) => \Illuminate\Support\Carbon::parse($a->date)->toDateString())->unique();
$totalDays = $datesInWeek->count();
$weekByDay = $weekAtt->groupBy(fn($a) => \Illuminate\Support\Carbon::parse($a->date)->toDateString());
$row = ['label' => $week['label'], 'start' => $week['start'], 'end' => $week['end'], 'students' => []]; $row = ['label' => $week['label'], 'start' => $week['start'], 'end' => $week['end'], 'students' => []];
foreach ($students as $student) { foreach ($students as $student) {
$studentAtt = $weekAtt->get($student->id, collect()); $hCount = 0; $sCount = 0; $iCount = 0; $aCount = 0; $tCount = 0; $totalDaysForStudent = 0;
$hadir = $studentAtt->where('status', 'hadir')->count(); foreach ($datesInWeek as $dateStr) {
$alpha = $studentAtt->where('status', 'alpha')->count(); $dayAtt = $weekByDay->get($dateStr, collect())->where('student_id', $student->id);
$sakit = $studentAtt->where('status', 'sakit')->count(); if ($dayAtt->isEmpty()) continue;
$izin = $studentAtt->where('status', 'izin')->count(); $totalDaysForStudent++;
$terlambat = $studentAtt->where('status', 'terlambat')->count();
$hasHadir = $dayAtt->where('status', 'hadir')->isNotEmpty();
$hasSakit = $dayAtt->where('status', 'sakit')->isNotEmpty();
$hasIzin = $dayAtt->where('status', 'izin')->isNotEmpty();
$hasAlpha = $dayAtt->where('status', 'alpha')->isNotEmpty();
$hasTerlambat = $dayAtt->where('status', 'terlambat')->isNotEmpty();
if ($hasHadir) $hCount++;
if ($hasSakit) $sCount++;
if ($hasIzin) $iCount++;
if ($hasTerlambat) $tCount++;
// Alpha proporsional: jika semua jam di hari itu alpha
$totalJam = $dayAtt->count();
$alphaJam = $dayAtt->where('status', 'alpha')->count();
if ($totalJam > 0) {
$aCount += round($alphaJam / $totalJam, 1);
}
}
$row['students'][$student->id] = [ $row['students'][$student->id] = [
'name' => $student->full_name, 'name' => $student->full_name,
'hadir' => $hadir, 'hadir' => $hCount,
'alpha' => $alpha, 'alpha' => $aCount,
'sakit' => $sakit, 'sakit' => $sCount,
'izin' => $izin, 'izin' => $iCount,
'terlambat' => $terlambat, 'terlambat' => $tCount,
'total' => $studentAtt->count(), 'total' => $totalDaysForStudent,
]; ];
} }
$recap->push($row); $recap->push($row);
} }
// Hitung total per siswa selama sebulan
$totals = [];
foreach ($students as $student) {
$totals[$student->id] = ['hadir' => 0, 'sakit' => 0, 'izin' => 0, 'alpha' => 0, 'terlambat' => 0];
}
foreach ($recap as $row) {
foreach ($row['students'] as $sid => $s) {
$totals[$sid]['hadir'] += $s['hadir'];
$totals[$sid]['sakit'] += $s['sakit'];
$totals[$sid]['izin'] += $s['izin'];
$totals[$sid]['alpha'] += $s['alpha'];
$totals[$sid]['terlambat'] += $s['terlambat'];
}
}
} }
return view('attendances.recap', compact( return view('attendances.recap', compact(
'type', 'month', 'year', 'className', 'classNames', 'type', 'month', 'year', 'className', 'classNames',
'students', 'recap', 'weeks' 'students', 'recap', 'weeks', 'totals'
)); ));
} }
@@ -555,6 +595,118 @@ class AttendanceController extends Controller
]); ]);
} }
public function exportMonthly(Request $request)
{
$classNames = Student::where('is_active', true)
->whereNotNull('class_name')
->distinct()
->orderBy('class_name')
->pluck('class_name');
$view = $this->recapMonthly($request, $classNames);
$data = $view->getData();
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// Title
$title = 'Rekap Presensi Bulanan';
if ($data['className']) {
$title .= ' - ' . $data['className'];
}
$title .= ' (' . \Carbon\Carbon::create()->month($data['month'])->format('F') . ' ' . $data['year'] . ')';
$sheet->setCellValue('A1', $title);
$totalCols = 1 + $data['recap']->count() * 4 + 4; // +4 untuk kolom TOTAL
$sheet->mergeCells('A1:' . $this->getColLetter($totalCols - 1) . '1');
$sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);
// Header row 2: week names + TOTAL
$sheet->setCellValue('A2', 'Siswa');
$sheet->getStyle('A2')->getFont()->setBold(true);
$col = 2;
foreach ($data['recap'] as $row) {
$cell = $this->getColLetter($col) . '2';
$sheet->setCellValue($cell, $row['label'] . ' ' . \Carbon\Carbon::parse($row['start'])->format('d/m') . '-' . \Carbon\Carbon::parse($row['end'])->format('d/m'));
$endCell = $this->getColLetter($col + 3) . '2';
$sheet->mergeCells($cell . ':' . $endCell);
$sheet->getStyle($cell)->getFont()->setBold(true);
$col += 4;
}
// Kolom TOTAL
$totalCell = $this->getColLetter($col) . '2';
$sheet->setCellValue($totalCell, 'TOTAL');
$sheet->mergeCells($totalCell . ':' . $this->getColLetter($col + 3) . '2');
$sheet->getStyle($totalCell)->getFont()->setBold(true);
// Header row 3: H, S, I, A per week + TOTAL
$sheet->setCellValue('A3', '');
$col = 2;
foreach ($data['recap'] as $row) {
$sheet->setCellValue($this->getColLetter($col) . '3', 'H');
$sheet->setCellValue($this->getColLetter($col + 1) . '3', 'S');
$sheet->setCellValue($this->getColLetter($col + 2) . '3', 'I');
$sheet->setCellValue($this->getColLetter($col + 3) . '3', 'A');
foreach (range(0, 3) as $i) {
$sheet->getStyle($this->getColLetter($col + $i) . '3')->getFont()->setBold(true);
}
$col += 4;
}
// Sub-header TOTAL
$sheet->setCellValue($this->getColLetter($col) . '3', 'H');
$sheet->setCellValue($this->getColLetter($col + 1) . '3', 'S');
$sheet->setCellValue($this->getColLetter($col + 2) . '3', 'I');
$sheet->setCellValue($this->getColLetter($col + 3) . '3', 'A');
foreach (range(0, 3) as $i) {
$sheet->getStyle($this->getColLetter($col + $i) . '3')->getFont()->setBold(true);
}
// Data rows
$rowIdx = 4;
foreach ($data['students'] as $student) {
$sheet->setCellValue('A' . $rowIdx, $student->full_name);
$col = 2;
foreach ($data['recap'] as $row) {
$s = $row['students'][$student->id] ?? null;
if ($s) {
$sheet->setCellValue($this->getColLetter($col) . $rowIdx, $s['hadir']);
$sheet->setCellValue($this->getColLetter($col + 1) . $rowIdx, $s['sakit']);
$sheet->setCellValue($this->getColLetter($col + 2) . $rowIdx, $s['izin']);
$sheet->setCellValue($this->getColLetter($col + 3) . $rowIdx, $s['alpha']);
} else {
foreach (range(0, 3) as $i) {
$sheet->setCellValue($this->getColLetter($col + $i) . $rowIdx, '-');
}
}
$col += 4;
}
// TOTAL per siswa
$t = $data['totals'][$student->id] ?? ['hadir' => 0, 'sakit' => 0, 'izin' => 0, 'alpha' => 0];
$sheet->setCellValue($this->getColLetter($col) . $rowIdx, $t['hadir']);
$sheet->setCellValue($this->getColLetter($col + 1) . $rowIdx, $t['sakit']);
$sheet->setCellValue($this->getColLetter($col + 2) . $rowIdx, $t['izin']);
$sheet->setCellValue($this->getColLetter($col + 3) . $rowIdx, $t['alpha']);
$rowIdx++;
}
// Auto width
for ($c = 1; $c < $totalCols; $c++) {
$sheet->getColumnDimension($this->getColLetter($c))->setAutoSize(true);
}
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$filename = 'rekap-bulanan' . ($data['className'] ? '-' . $data['className'] : '') . '.xlsx';
$filename = str_replace(['/', '\\', ' '], '-', $filename);
return response()->streamDownload(function () use ($writer) {
$writer->save('php://output');
}, $filename, [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
]);
}
private function getColLetter(int $col): string private function getColLetter(int $col): string
{ {
$letter = ''; $letter = '';
+1
View File
@@ -12,6 +12,7 @@ return Application::configure(basePath: dirname(__DIR__))
health: '/up', health: '/up',
) )
->withMiddleware(function (Middleware $middleware): void { ->withMiddleware(function (Middleware $middleware): void {
$middleware->trustProxies(at: '*');
$middleware->alias([ $middleware->alias([
'role' => \App\Http\Middleware\CheckRole::class, 'role' => \App\Http\Middleware\CheckRole::class,
'permission' => \App\Http\Middleware\PermissionMiddleware::class, 'permission' => \App\Http\Middleware\PermissionMiddleware::class,
+65 -38
View File
@@ -304,16 +304,23 @@
@else @else
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100"> <div class="px-6 py-4 border-b border-gray-100">
<div class="flex items-center gap-3"> <div class="flex items-center justify-between gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-violet-500 to-violet-600 flex items-center justify-center shadow-sm"> <div class="flex items-center gap-3">
<i class="fa-solid fa-calendar-alt text-white text-sm"></i> <div class="w-9 h-9 rounded-xl bg-gradient-to-br from-violet-500 to-violet-600 flex items-center justify-center shadow-sm">
</div> <i class="fa-solid fa-calendar-alt text-white text-sm"></i>
<div> </div>
<h3 class="text-sm font-semibold text-gray-900"> <div>
Rekap Bulanan — {{ \Carbon\Carbon::create()->month($month)->format('F') }} {{ $year }} <h3 class="text-sm font-semibold text-gray-900">
</h3> Rekap Bulanan — {{ \Carbon\Carbon::create()->month($month)->format('F') }} {{ $year }}
<p class="text-xs text-gray-400">{{ $students->count() }} siswa @if($className) • Kelas {{ $className }} @endif</p> </h3>
<p class="text-xs text-gray-400">{{ $students->count() }} siswa @if($className) • Kelas {{ $className }} @endif</p>
</div>
</div> </div>
<a href="{{ route('attendances.export-monthly', ['class_name' => $className, 'month' => $month, 'year' => $year]) }}"
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-semibold text-white bg-emerald-600 rounded-xl hover:bg-emerald-700 transition shadow-sm">
<i class="fa-solid fa-download text-xs"></i>
Export
</a>
</div> </div>
</div> </div>
@@ -322,58 +329,78 @@
<table class="min-w-full divide-y divide-gray-100"> <table class="min-w-full divide-y divide-gray-100">
<thead> <thead>
<tr class="bg-gray-50/80"> <tr class="bg-gray-50/80">
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider sticky left-0 bg-gray-50/80 z-10">Siswa</th> <th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider sticky left-0 bg-gray-50/80 border-r-2 border-gray-300 z-10" rowspan="2">Siswa</th>
@foreach($recap as $row) @foreach($recap as $row)
<th class="px-3 py-3 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider"> @php
$sep = !$loop->last ? 'border-r-2 border-gray-300' : '';
@endphp
<th colspan="4" class="px-3 py-2 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider border-b border-gray-200 {{ $sep }}">
{{ $row['label'] }}<br> {{ $row['label'] }}<br>
<span class="text-[10px] text-gray-400 font-normal">{{ \Carbon\Carbon::parse($row['start'])->format('d/m') }}-{{ \Carbon\Carbon::parse($row['end'])->format('d/m') }}</span> <span class="text-[10px] text-gray-400 font-normal">{{ \Carbon\Carbon::parse($row['start'])->format('d/m') }}-{{ \Carbon\Carbon::parse($row['end'])->format('d/m') }}</span>
</th> </th>
@endforeach @endforeach
<th colspan="4" class="px-3 py-2 text-center text-xs font-semibold text-gray-800 uppercase tracking-wider border-b border-gray-200 border-l-2 border-gray-400 bg-gray-100/80">
TOTAL<br>
<span class="text-[10px] text-gray-500 font-normal">Hari</span>
</th>
</tr>
<tr class="bg-gray-50/80">
@foreach($recap as $row)
@php
$sep = !$loop->last ? 'border-r-2 border-gray-300' : '';
@endphp
<th class="px-2 py-1.5 text-center text-[10px] font-bold text-emerald-600 uppercase tracking-wider">H</th>
<th class="px-2 py-1.5 text-center text-[10px] font-bold text-purple-600 uppercase tracking-wider">S</th>
<th class="px-2 py-1.5 text-center text-[10px] font-bold text-blue-600 uppercase tracking-wider">I</th>
<th class="px-2 py-1.5 text-center text-[10px] font-bold text-red-600 uppercase tracking-wider {{ $sep }}">A</th>
@endforeach
<th class="px-2 py-1.5 text-center text-[10px] font-bold text-emerald-700 uppercase tracking-wider bg-gray-100/80 border-l-2 border-gray-400">H</th>
<th class="px-2 py-1.5 text-center text-[10px] font-bold text-purple-700 uppercase tracking-wider bg-gray-100/80">S</th>
<th class="px-2 py-1.5 text-center text-[10px] font-bold text-blue-700 uppercase tracking-wider bg-gray-100/80">I</th>
<th class="px-2 py-1.5 text-center text-[10px] font-bold text-red-700 uppercase tracking-wider bg-gray-100/80">A</th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-gray-50"> <tbody class="divide-y divide-gray-50">
@foreach($students as $student) @foreach($students as $student)
<tr class="hover:bg-gray-50/50 transition"> <tr class="hover:bg-gray-50/50 transition">
<td class="px-4 py-2.5 whitespace-nowrap sticky left-0 bg-white z-10"> <td class="px-4 py-2.5 whitespace-nowrap sticky left-0 bg-white border-r-2 border-gray-200 z-10">
<p class="text-sm font-semibold text-gray-900">{{ $student->full_name }}</p> <p class="text-sm font-semibold text-gray-900">{{ $student->full_name }}</p>
</td> </td>
@foreach($recap as $row) @foreach($recap as $row)
@php @php
$s = $row['students'][$student->id] ?? null; $s = $row['students'][$student->id] ?? null;
$sep = !$loop->last ? 'border-r-2 border-gray-200' : '';
@endphp @endphp
<td class="px-3 py-2.5 text-center"> @if($s)
@if($s) <td class="px-2 py-2.5 text-center text-xs font-bold text-emerald-600">{{ $s['hadir'] }}</td>
<div class="flex flex-col items-center gap-0.5 text-xs"> <td class="px-2 py-2.5 text-center text-xs font-bold text-purple-600">{{ $s['sakit'] }}</td>
<div class="flex items-center gap-1.5"> <td class="px-2 py-2.5 text-center text-xs font-bold text-blue-600">{{ $s['izin'] }}</td>
<span class="text-emerald-600 font-bold">{{ $s['hadir'] }}</span> <td class="px-2 py-2.5 text-center text-xs font-bold {{ $s['alpha'] > 0 ? 'text-red-600' : 'text-gray-400' }} {{ $sep }}">{{ $s['alpha'] }}</td>
@if($s['alpha'] > 0) @else
<span class="text-red-600 font-bold">A{{ $s['alpha'] }}</span> <td class="px-2 py-2.5 text-center text-xs text-gray-300">—</td>
@endif <td class="px-2 py-2.5 text-center text-xs text-gray-300">—</td>
</div> <td class="px-2 py-2.5 text-center text-xs text-gray-300">—</td>
@if($s['sakit'] > 0 || $s['izin'] > 0 || $s['terlambat'] > 0) <td class="px-2 py-2.5 text-center text-xs text-gray-300 {{ $sep }}">—</td>
<div class="flex items-center gap-1.5 text-[10px]"> @endif
@if($s['sakit'] > 0)<span class="text-purple-500">S{{ $s['sakit'] }}</span>@endif
@if($s['izin'] > 0)<span class="text-blue-500">I{{ $s['izin'] }}</span>@endif
@if($s['terlambat'] > 0)<span class="text-yellow-500">T{{ $s['terlambat'] }}</span>@endif
</div>
@endif
</div>
@else
<span class="text-gray-300 text-xs">—</span>
@endif
</td>
@endforeach @endforeach
{{-- TOTAL --}}
@php
$t = $totals[$student->id] ?? ['hadir' => 0, 'sakit' => 0, 'izin' => 0, 'alpha' => 0];
@endphp
<td class="px-2 py-2.5 text-center text-xs font-bold text-emerald-700 bg-gray-50 border-l-2 border-gray-300">{{ $t['hadir'] }}</td>
<td class="px-2 py-2.5 text-center text-xs font-bold text-purple-700 bg-gray-50">{{ $t['sakit'] }}</td>
<td class="px-2 py-2.5 text-center text-xs font-bold text-blue-700 bg-gray-50">{{ $t['izin'] }}</td>
<td class="px-2 py-2.5 text-center text-xs font-bold {{ $t['alpha'] > 0 ? 'text-red-700' : 'text-gray-400' }} bg-gray-50">{{ $t['alpha'] }}</td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50/50 text-xs text-gray-400 text-center flex items-center justify-center gap-4"> <div class="px-5 py-3 border-t border-gray-100 bg-gray-50/50 text-xs text-gray-400 text-center flex items-center justify-center gap-4">
<span class="text-emerald-600">H Hadir</span> <span class="text-emerald-600 font-bold">H</span> Hadir
<span class="text-red-600">A Alpha</span> <span class="text-purple-600 font-bold">S</span> Sakit
<span class="text-purple-500">S Sakit</span> <span class="text-blue-600 font-bold">I</span> Izin
<span class="text-blue-500">I Izin</span> <span class="text-red-600 font-bold">A</span> Alpha
<span class="text-yellow-500">T Terlambat</span>
</div> </div>
@else @else
<div class="px-5 py-12 text-center"> <div class="px-5 py-12 text-center">
+18
View File
@@ -11,6 +11,24 @@
$bgPath = App\Models\Setting::getValue('login_background'); $bgPath = App\Models\Setting::getValue('login_background');
@endphp @endphp
<title>Login - {{ $appName }}</title> <title>Login - {{ $appName }}</title>
{{-- Open Graph / Social Preview --}}
<meta property="og:title" content="{{ $appName }} - {{ $schoolName }}">
<meta property="og:description" content="Aplikasi Ketertiban Siswa {{ $schoolName }} — Manajemen pelanggaran, presensi, dan surat peringatan siswa.">
<meta property="og:site_name" content="{{ $appName }}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ url()->current() }}">
@if($logoPath)
<meta property="og:image" content="{{ asset('storage/' . $logoPath) }}">
<meta property="og:image:width" content="256">
<meta property="og:image:height" content="256">
@else
<meta property="og:image" content="{{ asset('favicon.ico') }}">
@endif
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{ $appName }} - {{ $schoolName }}">
<meta name="twitter:description" content="Aplikasi Ketertiban Siswa {{ $schoolName }}.">
@vite(['resources/css/app.css']) @vite(['resources/css/app.css'])
</head> </head>
<body class="min-h-screen bg-slate-100 px-3 py-3 sm:px-6 sm:py-6 antialiased"> <body class="min-h-screen bg-slate-100 px-3 py-3 sm:px-6 sm:py-6 antialiased">
+18
View File
@@ -10,6 +10,24 @@
$logoPath = App\Models\Setting::getValue('school_logo'); $logoPath = App\Models\Setting::getValue('school_logo');
@endphp @endphp
<title>{{ $schoolName }} - @yield('title', $appName)</title> <title>{{ $schoolName }} - @yield('title', $appName)</title>
{{-- Open Graph / Social Preview --}}
<meta property="og:title" content="@yield('title', $appName) - {{ $schoolName }}">
<meta property="og:description" content="Aplikasi Ketertiban Siswa {{ $schoolName }} — Manajemen pelanggaran, presensi, dan surat peringatan siswa.">
<meta property="og:site_name" content="{{ $appName }}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ url()->current() }}">
@if($logoPath)
<meta property="og:image" content="{{ asset('storage/' . $logoPath) }}">
<meta property="og:image:width" content="256">
<meta property="og:image:height" content="256">
@else
<meta property="og:image" content="{{ asset('favicon.ico') }}">
@endif
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="@yield('title', $appName) - {{ $schoolName }}">
<meta name="twitter:description" content="Aplikasi Ketertiban Siswa {{ $schoolName }}.">
<link rel="icon" type="image/png" href="{{ $logoPath ? asset('storage/' . $logoPath) : asset('favicon.ico') }}"> <link rel="icon" type="image/png" href="{{ $logoPath ? asset('storage/' . $logoPath) : asset('favicon.ico') }}">
<link rel="shortcut icon" type="image/png" href="{{ $logoPath ? asset('storage/' . $logoPath) : asset('favicon.ico') }}"> <link rel="shortcut icon" type="image/png" href="{{ $logoPath ? asset('storage/' . $logoPath) : asset('favicon.ico') }}">
+1
View File
@@ -69,6 +69,7 @@ Route::middleware('auth')->group(function () {
Route::get('/attendances/recap', [\App\Http\Controllers\AttendanceController::class, 'recap'])->name('attendances.recap'); Route::get('/attendances/recap', [\App\Http\Controllers\AttendanceController::class, 'recap'])->name('attendances.recap');
Route::get('/attendances/calendar-data', [\App\Http\Controllers\AttendanceController::class, 'calendarData'])->name('attendances.calendar-data'); Route::get('/attendances/calendar-data', [\App\Http\Controllers\AttendanceController::class, 'calendarData'])->name('attendances.calendar-data');
Route::get('/attendances/export-weekly', [\App\Http\Controllers\AttendanceController::class, 'exportWeekly'])->name('attendances.export-weekly'); Route::get('/attendances/export-weekly', [\App\Http\Controllers\AttendanceController::class, 'exportWeekly'])->name('attendances.export-weekly');
Route::get('/attendances/export-monthly', [\App\Http\Controllers\AttendanceController::class, 'exportMonthly'])->name('attendances.export-monthly');
}); });
Route::middleware('permission:view-sp-letters')->group(function () { Route::middleware('permission:view-sp-letters')->group(function () {