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
@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\SaveAdminNotificationSubjectsRequest;
use App\Http\Requests\Admin\SavePrintNotificationRecipientsRequest;
use App\Http\Resources\Admin\AdminNotificationAlertsResource;
use App\Http\Resources\Admin\PrintRecipientsResource;
use App\Services\Admin\AdminNotificationService;
use Illuminate\Http\JsonResponse;
class AdminNotificationController extends Controller
{
public function __construct(
private AdminNotificationService $service
) {}
public function alerts(): AdminNotificationAlertsResource
{
return new AdminNotificationAlertsResource($this->service->alertsPayload());
}
public function saveSubjects(SaveAdminNotificationSubjectsRequest $request): JsonResponse
{
$result = $this->service->saveSubjects($request->validated('subjects') ?? []);
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
}
public function printRecipients(): PrintRecipientsResource
{
return new PrintRecipientsResource($this->service->printRecipientsPayload());
}
public function savePrintRecipients(SavePrintNotificationRecipientsRequest $request): JsonResponse
{
$result = $this->service->savePrintRecipients($request->validated('notify') ?? []);
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
}
}
@@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Models\ClassProgressAttachment;
use App\Models\ClassProgressReport;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AdminProgressAttachmentController extends Controller
{
use HandlesProgressAttachments;
/**
* Download legacy attachment from class_progress_reports.attachment_path
*/
public function downloadLegacyByReport(int $reportId): BinaryFileResponse
{
$row = ClassProgressReport::query()->find($reportId);
if (! $row || empty($row->attachment_path)) {
abort(404, 'Attachment not found.');
}
$file = $this->resolveAttachmentFile($row->toArray());
if (! $file) {
abort(404, 'Attachment missing.');
}
return response()->download($file, basename($file));
}
/**
* Download file from normalized attachments table
*/
public function downloadAttachment(int $attachmentId): BinaryFileResponse
{
$attachment = ClassProgressAttachment::query()->find($attachmentId);
if (! $attachment || empty($attachment->file_path)) {
abort(404, 'Attachment not found.');
}
$file = $this->resolveAttachmentPath((string) $attachment->file_path);
if (! $file) {
abort(404, 'Attachment missing.');
}
$downloadName = $attachment->original_name ?: basename($file);
return response()->download($file, $downloadName);
}
}
@@ -0,0 +1,209 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Admin\IndexAdminProgressRequest;
use App\Http\Resources\Admin\Progress\ProgressGroupResource;
use App\Http\Resources\Admin\Progress\ProgressReportDetailResource;
use App\Models\ClassProgressReport;
use App\Models\ClassSection;
use App\Models\StudentClass;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class AdminProgressReportController extends Controller
{
use HandlesProgressAttachments;
public function index(IndexAdminProgressRequest $request): JsonResponse
{
$filters = $request->validated();
// Ensure all keys exist (same shape as CI)
$filters = array_merge([
'from' => '',
'to' => '',
'class_section_id' => '',
'status' => '',
], $filters);
$query = ClassProgressReport::query()
->from('class_progress_reports')
->select([
'class_progress_reports.*',
'cs.class_section_name',
])
->selectRaw("CONCAT(COALESCE(u.firstname, ''), ' ', COALESCE(u.lastname, '')) AS teacher_name")
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'class_progress_reports.class_section_id')
->leftJoin('users as u', 'u.id', '=', 'class_progress_reports.teacher_id');
$this->applyFilters($query, $filters);
$rows = $query
->orderByDesc('week_start')
->get()
->map(function ($row) {
$arr = $row->toArray();
$arr['status_label'] = config('progress.status_options.' . ($arr['status'] ?? ''), 'Unknown');
return $arr;
})
->all();
$reportGroups = $this->groupReportsByWeekAndSection($rows);
$classSections = $this->getClassSections();
$studentCounts = $this->getStudentCountsBySection();
$filteredSections = array_values(array_filter($classSections, function ($section) use ($studentCounts) {
$sectionId = (int) ($section['class_section_id'] ?? 0);
return isset($studentCounts[$sectionId]) && $studentCounts[$sectionId] > 0;
}));
return response()->json([
'data' => ProgressGroupResource::collection(collect(array_values($reportGroups))),
'meta' => [
'filters' => $filters,
'class_sections' => $filteredSections,
'status_options' => config('progress.status_options', []),
'subject_sections' => config('progress.subject_sections', []),
],
]);
}
public function show(int $id): JsonResponse
{
$row = ClassProgressReport::query()
->from('class_progress_reports')
->select([
'class_progress_reports.*',
'cs.class_section_name',
])
->selectRaw("CONCAT(COALESCE(u.firstname, ''), ' ', COALESCE(u.lastname, '')) AS teacher_name")
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'class_progress_reports.class_section_id')
->leftJoin('users as u', 'u.id', '=', 'class_progress_reports.teacher_id')
->where('class_progress_reports.id', $id)
->first();
if (! $row) {
return response()->json(['message' => 'Progress report not found.'], 404);
}
$rowData = $row->toArray();
$rowData['status_label'] = config('progress.status_options.' . ($rowData['status'] ?? ''), 'Unknown');
$rowData['flags'] = $this->decodeFlags($rowData['flags_json'] ?? null);
$weeklyReports = ClassProgressReport::query()
->where('class_section_id', $rowData['class_section_id'])
->where('week_start', $rowData['week_start'])
->orderBy('subject', 'asc')
->get()
->map(fn ($r) => $r->toArray())
->all();
$attachmentMap = $this->loadAttachmentsForReports(array_column($weeklyReports, 'id'));
foreach ($weeklyReports as &$report) {
$reportId = (int) ($report['id'] ?? 0);
$report['attachments'] = $attachmentMap[$reportId] ?? [];
// Legacy fallback attachment (same behavior as CI)
if (empty($report['attachments']) && ! empty($report['attachment_path'])) {
$report['attachments'][] = [
'id' => $reportId, // pseudo-id for legacy
'name' => basename((string) $report['attachment_path']),
'legacy' => true,
];
}
}
unset($report);
return response()->json([
'data' => new ProgressReportDetailResource([
'report' => $rowData,
'weekly_reports' => $weeklyReports,
'subject_sections' => config('progress.subject_sections', []),
]),
]);
}
private function applyFilters(Builder $query, array $filters): void
{
if (! empty($filters['from'])) {
$query->where('week_start', '>=', $filters['from']);
}
if (! empty($filters['to'])) {
$query->where('week_end', '<=', $filters['to']);
}
if (! empty($filters['class_section_id'])) {
$query->where('class_progress_reports.class_section_id', (int) $filters['class_section_id']);
}
if (! empty($filters['status'])) {
$query->where('class_progress_reports.status', $filters['status']);
}
}
private function groupReportsByWeekAndSection(array $rows): array
{
$groups = [];
foreach ($rows as $row) {
$key = ($row['week_start'] ?? '') . '_' . ($row['class_section_id'] ?? '');
if ($key === '_') {
continue;
}
if (! isset($groups[$key])) {
$groups[$key] = [
'week_start' => $row['week_start'] ?? null,
'week_end' => $row['week_end'] ?? null,
'class_section_id' => $row['class_section_id'] ?? null,
'class_section_name' => $row['class_section_name'] ?? '',
'reports' => [],
];
}
$subject = (string) ($row['subject'] ?? 'unknown');
$groups[$key]['reports'][$subject] = $row;
}
return $groups;
}
/**
* Replace with relation/scopes/service if you already have them.
*/
private function getClassSections(): array
{
if (! class_exists(ClassSection::class)) {
return [];
}
return ClassSection::query()
->orderBy('class_section_name')
->get()
->map(fn ($r) => $r->toArray())
->all();
}
/**
* Expected shape: [class_section_id => count]
*/
private function getStudentCountsBySection(): array
{
if (! class_exists(StudentClass::class)) {
return [];
}
return StudentClass::query()
->selectRaw('class_section_id, COUNT(*) as total')
->groupBy('class_section_id')
->pluck('total', 'class_section_id')
->map(fn ($v) => (int) $v)
->all();
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\SendTeacherSubmissionNotificationsRequest;
use App\Http\Resources\Admin\TeacherSubmissionReportResource;
use App\Services\Admin\TeacherSubmissionService;
use Illuminate\Http\JsonResponse;
class AdminTeacherSubmissionController extends Controller
{
public function __construct(
private TeacherSubmissionService $service
) {}
public function report(): TeacherSubmissionReportResource
{
return new TeacherSubmissionReportResource($this->service->report());
}
public function sendNotifications(SendTeacherSubmissionNotificationsRequest $request): JsonResponse
{
$result = $this->service->sendNotifications(
(int) $request->user()->id,
$request->validated()
);
return response()->json($result);
}
}
@@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Models\ClassProgressAttachment;
trait HandlesProgressAttachments
{
protected function resolveAttachmentFile(array $row): ?string
{
$path = trim((string) ($row['attachment_path'] ?? ''));
if ($path === '') {
return null;
}
return $this->resolveAttachmentPath($path);
}
protected function resolveAttachmentPath(string $path): ?string
{
// CI stored paths may look like: writable/uploads/...
$relative = preg_replace('#^writable/uploads/#', '', $path);
// Laravel equivalent target
$absolute = storage_path('app/uploads/' . ltrim((string) $relative, '/'));
return is_file($absolute) ? $absolute : null;
}
/**
* Returns map: [report_id => [ ['id'=>.., 'name'=>..], ... ]]
*/
protected function loadAttachmentsForReports(array $reportIds): array
{
$reportIds = array_values(array_filter(array_map('intval', $reportIds)));
if (empty($reportIds)) {
return [];
}
$rows = ClassProgressAttachment::query()
->whereIn('report_id', $reportIds)
->orderBy('id', 'asc')
->get();
$map = [];
foreach ($rows as $row) {
$reportId = (int) ($row->report_id ?? 0);
if ($reportId === 0) {
continue;
}
$map[$reportId][] = [
'id' => (int) $row->id,
'name' => $row->original_name ?: basename((string) $row->file_path),
];
}
return $map;
}
protected function decodeFlags(?string $json): array
{
if (! $json) {
return [];
}
$flags = json_decode($json, true);
return is_array($flags) ? $flags : [];
}
}