add projet
This commit is contained in:
@@ -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 : [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user