EVOLUTION-NINJA
Edit File: AdminAttendanceController.php
<?php // app/Http/Controllers/AdminAttendanceController.php namespace App\Http\Controllers; use App\Models\AttendanceSubmission; use App\Models\AttendanceAbsence; use App\Models\add_student; use App\Models\ClassRoom; use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; class AdminAttendanceController extends Controller { // ======= Dropdowns ======= // All classes for admin dropdown public function classes_list() { $list = ClassRoom::select('id','name','section') ->orderBy('name')->orderBy('section') ->get() ->map(fn($c)=>[ 'id'=>$c->id, 'name'=>$c->name, 'section'=>$c->section, 'label'=>$c->name.($c->section?' - '.$c->section:'') ]); return response()->json($list); } // ======= Step 1: Load students for class+date (defaults present) ======= // GET /admin/attendance/students?class_name=...§ion=...&date=YYYY-MM-DD public function students_list(Request $request) { $data = $request->validate([ 'className' => 'required|string', 'section' => 'nullable|string', 'date' => 'required|string', ]); [$dd,$mm,$yy,$key,$col] = $this->parseAndKey($data['date']); $class = $this->findClass($data['className'], $data['section'] ?? null); if (!$class) return response()->json(['message'=>'Class not found'], 404); $students = add_student::select('student_id','first_name','middle_name','last_name') ->where('currentClass', $class->name) ->when($class->section, fn($q)=>$q->where('section',$class->section)) ->when(!$class->section, fn($q)=>$q->whereNull('section')) ->orderBy('first_name')->orderBy('last_name') ->get(); $ids = $students->pluck('student_id')->all(); $absMap = AttendanceAbsence::whereIn('student_id',$ids)->get()->keyBy('student_id'); $rows = []; foreach ($students as $stu) { $status = 1; // default present $abs = $absMap->get($stu->student_id); if ($abs && is_array($abs->{$col})) { $map = $this->toAssoc($abs->{$col}); if (array_key_exists($key,$map) && (int)$map[$key]===0) $status = 0; } $rows[] = [ 'student_id' => $stu->student_id, 'name' => $stu->name, 'date' => $key, 'status' => $status ]; } return response()->json([ 'class' => $class->name, 'section' => $class->section, 'date' => $key, 'data' => $rows ]); } // ======= Step 2: Submit attendance (absent IDs only) ======= // POST /admin/attendance/submit // body: { "admin_name":"...", "class_name":"...", "section": "A|null", "date":"YYYY-MM-DD", "absent_student_ids":["STU1","STU2"] } public function submit_attendance(Request $request) { $data = $request->validate([ 'className' => 'required|string', 'section' => 'nullable|string', 'date' => 'required|string', 'absent_student_ids' => 'sometimes|array', 'absent_student_ids.*' => 'string', 'admin_name' => 'required|string', // ✅ added back ]); [$dd,$mm,$yy,$key,$col] = $this->parseAndKey($data['date']); $class = $this->findClass($data['className'], $data['section'] ?? null); if (!$class) { return response()->json(['message'=>'Class not found'], 404); } $submission = AttendanceSubmission::updateOrCreate( [ 'class_id' => $class->id, 'submitted_date' => Carbon::createFromFormat('d-m-Y',$key)->format('Y-m-d') ], [ 'class_name' => $class->name, 'section' => $class->section, 'submitted_by' => $data['admin_name'], // ✅ always set ] ); $absentIds = collect($data['absent_student_ids'] ?? [])->unique()->values(); $students = add_student::select('student_id','first_name','middle_name','last_name') ->where('currentClass',$class->name) ->when($class->section, fn($q)=>$q->where('section',$class->section)) ->when(!$class->section, fn($q)=>$q->whereNull('section')) ->get()->keyBy('student_id'); DB::beginTransaction(); try { // remove marks for those now present $prev = AttendanceAbsence::whereIn('student_id', $students->keys())->get(); foreach ($prev as $rec) { $map = $this->toAssoc($rec->{$col} ?? []); if (array_key_exists($key,$map) && !$absentIds->contains($rec->student_id)) { unset($map[$key]); $rec->{$col} = $this->toList($map); $rec->save(); } } // add marks for absentees foreach ($absentIds as $sid) { if (!$students->has($sid)) continue; $stu = $students->get($sid); $fullName = $stu->first_name . ' ' . $stu->middle_name . ' ' . $stu->last_name; $abs = AttendanceAbsence::firstOrNew(['student_id'=>$sid]); if (!$abs->exists) { $abs->student_name = $fullName; $abs->class_id = $class->id; } else { if (empty($abs->student_name)) $abs->student_name = $fullName; if (!$abs->class_id) $abs->class_id = $class->id; } $map = $this->toAssoc($abs->{$col} ?? []); $map[$key] = 0; $abs->{$col} = $this->toList($map); $abs->save(); } DB::commit(); } catch (\Throwable $e) { DB::rollBack(); return response()->json(['message'=>'Failed to submit','error'=>$e->getMessage()],500); } return response()->json([ 'message'=>'Attendance submitted', 'submission'=>$submission, 'absent_ids'=>$absentIds ],201); } // ======= Update/Delete submission ======= // PUT /admin/attendance/submissions/{id} // body: { "admin_name": "...", "absent_student_ids": ["STU..."] } public function update_attendance(Request $request, int $id) { $submission = AttendanceSubmission::findOrFail($id); $data = $request->validate([ 'admin_name' => 'required|string', 'absent_student_ids' => 'sometimes|array', 'absent_student_ids.*'=> 'string' ]); $class = $submission->classRoom; [$dd,$mm,$yy,$key,$col] = $this->parseAndKey($submission->submitted_date->format('Y-m-d')); $students = add_student::select('student_id','first_name','middle_name','last_name') ->where('currentClass',$class->name) ->when($class->section, fn($q)=>$q->where('section',$class->section)) ->when(!$class->section, fn($q)=>$q->whereNull('section')) ->get()->keyBy('student_id'); $newAbsent = collect($data['absent_student_ids'] ?? [])->unique()->values(); DB::beginTransaction(); try { $current = AttendanceAbsence::whereIn('student_id',$students->keys())->get(); $currentAbsentIds = []; foreach ($current as $rec) { $map = $this->toAssoc($rec->{$col} ?? []); if (array_key_exists($key,$map)) $currentAbsentIds[] = $rec->student_id; } $currentAbsent = collect($currentAbsentIds); $toRemove = $currentAbsent->diff($newAbsent); $toAdd = $newAbsent->diff($currentAbsent); foreach ($current as $rec) { if (!$toRemove->contains($rec->student_id)) continue; $map = $this->toAssoc($rec->{$col} ?? []); if (array_key_exists($key,$map)) { unset($map[$key]); $rec->{$col} = $this->toList($map); $rec->save(); } } foreach ($toAdd as $sid) { if (!$students->has($sid)) continue; $stu = $students->get($sid); $fullName = $stu->name; $abs = AttendanceAbsence::firstOrNew(['student_id'=>$sid]); if (!$abs->exists) { $abs->student_name = $fullName; $abs->class_id=$class->id; } $map = $this->toAssoc($abs->{$col} ?? []); $map[$key] = 0; $abs->{$col} = $this->toList($map); $abs->save(); } $submission->submitted_by = $data['admin_name']; $submission->save(); DB::commit(); } catch (\Throwable $e) { DB::rollBack(); return response()->json(['message'=>'Failed to update','error'=>$e->getMessage()],500); } return response()->json(['message'=>'Submission updated']); } // DELETE /admin/attendance/submissions/{id} public function delete_attendance(int $id) { $submission = AttendanceSubmission::findOrFail($id); $class = $submission->classRoom; [$dd,$mm,$yy,$key,$col] = $this->parseAndKey($submission->submitted_date->format('Y-m-d')); DB::beginTransaction(); try { $students = add_student::select('student_id') ->where('currentClass',$class->name) ->when($class->section, fn($q)=>$q->where('section',$class->section)) ->when(!$class->section, fn($q)=>$q->whereNull('section')) ->get(); $absences = AttendanceAbsence::whereIn('student_id',$students->pluck('student_id'))->get(); foreach ($absences as $rec) { $map = $this->toAssoc($rec->{$col} ?? []); if (array_key_exists($key,$map)) { unset($map[$key]); $rec->{$col} = $this->toList($map); $rec->save(); } } $submission->delete(); DB::commit(); } catch (\Throwable $e) { DB::rollBack(); return response()->json(['message'=>'Failed to delete','error'=>$e->getMessage()],500); } return response()->json(['message'=>'Submission deleted']); } // ======= Reports ======= // Day-wise class report (present/absent list) // GET /admin/attendance/day?class_name=§ion=&date=YYYY-MM-DD public function dayReport(Request $request) { return $this->students_list($request); // same output (status 1/0 per student) } // Date-range class report (summary per date: counts + absent ids) // GET /admin/attendance/date-range?class_name=§ion=&start=YYYY-MM-DD&end=YYYY-MM-DD public function dateRangeReport(Request $request) { $data = $request->validate([ 'class_name'=>'required|string', 'section' =>'nullable|string', 'start' =>'required|date_format:Y-m-d', 'end' =>'required|date_format:Y-m-d|after_or_equal:start' ]); $class = $this->findClass($data['class_name'], $data['section'] ?? null); if (!$class) return response()->json(['message'=>'Class not found'],404); // all students in class $students = add_student::select('student_id') ->where('currentClass',$class->name) ->when($class->section, fn($q)=>$q->where('section',$class->section)) ->when(!$class->section, fn($q)=>$q->whereNull('section')) ->get(); $ids = $students->pluck('student_id')->all(); $start = Carbon::createFromFormat('Y-m-d',$data['start']); $end = Carbon::createFromFormat('Y-m-d',$data['end']); $days = $end->diffInDays($start)+1; // Load all absence rows once $absMap = AttendanceAbsence::whereIn('student_id',$ids)->get()->keyBy('student_id'); $out = []; for ($i=0;$i<$days;$i++) { $d = $start->copy()->addDays($i); [$dd,$mm,$yy,$key,$col] = $this->parseAndKey($d->format('Y-m-d')); $absent = []; foreach ($ids as $sid) { $row = $absMap->get($sid); if ($row && is_array($row->{$col})) { $map = $this->toAssoc($row->{$col}); if (array_key_exists($key,$map)) $absent[] = $sid; } } $out[] = [ 'date' => $key, 'total_students' => count($ids), 'absent_count' => count($absent), 'present_count' => count($ids) - count($absent), 'absent_student_ids' => $absent ]; } return response()->json([ 'class' => $class->name, 'section' => $class->section, 'start' => $data['start'], 'end' => $data['end'], 'days' => $out ]); } // Student month-wise report (fills missing days as present=1) // GET /admin/attendance/student/{student_id}?month=YYYY-MM public function studentMonthReport(Request $request, string $studentId) { $request->validate(['month'=>'required|regex:/^\d{4}-\d{2}$/']); [$y,$m] = array_map('intval', explode('-',$request->month)); $col = $this->monthColumn($m); $daysInMonth = Carbon::create($y,$m,1)->daysInMonth; $abs = AttendanceAbsence::where('student_id',$studentId)->first(); $name = add_student::where('student_id',$studentId)->value(DB::raw("CONCAT_WS(' ', first_name, middle_name, last_name)")); $stored = $abs ? $this->toAssoc($abs->{$col} ?? []) : []; $data = []; for ($d=1;$d<=$daysInMonth;$d++) { $key = sprintf('%02d-%02d-%04d',$d,$m,$y); $data[] = ['date'=>$key,'status'=> array_key_exists($key,$stored)?0:1]; } return response()->json([ 'student_id'=>$studentId, 'name'=>$name, 'month'=>$request->month, 'data'=>$data ]); } // Class month summary (counts per day) // GET /admin/attendance/class-month?class_name=§ion=&month=YYYY-MM public function classMonthReport(Request $request) { $request->validate([ 'class_name'=>'required|string', 'section' =>'nullable|string', 'month' =>'required|regex:/^\d{4}-\d{2}$/' ]); [$y,$m] = array_map('intval', explode('-',$request->month)); $col = $this->monthColumn($m); $daysInMonth = Carbon::create($y,$m,1)->daysInMonth; $class = $this->findClass($request->class_name, $request->section ?? null); if (!$class) return response()->json(['message'=>'Class not found'],404); $ids = add_student::where('currentClass',$class->name) ->when($class->section, fn($q)=>$q->where('section',$class->section)) ->when(!$class->section, fn($q)=>$q->whereNull('section')) ->pluck('student_id')->all(); $absMap = AttendanceAbsence::whereIn('student_id',$ids)->get()->keyBy('student_id'); $days = []; for ($d=1;$d<=$daysInMonth;$d++) { $key = sprintf('%02d-%02d-%04d',$d,$m,$y); $absent = 0; foreach ($ids as $sid) { $row = $absMap->get($sid); if ($row && is_array($row->{$col})) { $map = $this->toAssoc($row->{$col}); if (array_key_exists($key,$map)) $absent++; } } $days[] = [ 'date'=>$key, 'total_students'=>count($ids), 'absent_count'=>$absent, 'present_count'=>count($ids)-$absent ]; } return response()->json([ 'class'=>$class->name, 'section'=>$class->section, 'month'=>$request->month, 'days'=>$days ]); } // ======= Helpers ======= private function findClass(string $name, ?string $section): ?ClassRoom { $q = ClassRoom::where('name',$name); if ($section === null || $section === '') $q->whereNull('section'); else $q->where('section',$section); return $q->first(); } // Y-m-d or d-m-Y → [dd,mm,yy,"DD-MM-YYYY","monthCol"] private function parseAndKey(string $date): array { if (preg_match('/^\d{4}-\d{2}-\d{2}$/',$date)) $c = Carbon::createFromFormat('Y-m-d',$date); elseif (preg_match('/^\d{2}-\d{2}-\d{4}$/',$date)) $c = Carbon::createFromFormat('d-m-Y',$date); else abort(422,'Invalid date format. Use "YYYY-MM-DD" or "DD-MM-YYYY".'); $dd = sprintf('%02d',$c->day); $mm=(int)$c->month; $yy=(int)$c->year; $key = $dd.'-'.sprintf('%02d',$mm).'-'.$yy; $col = $this->monthColumn($mm); return [$dd,$mm,$yy,$key,$col]; } private function monthColumn(int $m): string { return [1=>'jan',2=>'feb',3=>'mar',4=>'apr',5=>'may',6=>'jun',7=>'jul',8=>'aug',9=>'sep',10=>'oct',11=>'nov',12=>'dec'][$m]; } // ["05-01-2025=0"] → ["05-01-2025"=>0] private function toAssoc(array $list): array { $a=[]; foreach ($list as $item) { if(!is_string($item)) continue; $p=explode('=',$item); if(count($p)===2) $a[$p[0]]=(int)$p[1]; } ksort($a); return $a; } // ["05-01-2025"=>0] → ["05-01-2025=0"] private function toList(array $assoc): array { ksort($assoc); $out=[]; foreach($assoc as $k=>$v){ $out[]=$k.'='.(int)$v; } return $out; } }