From a7e981f8c70a7b6cccfcfab51ee83a1e7b2e3e60 Mon Sep 17 00:00:00 2001 From: Rizky Date: Sun, 8 Jun 2025 15:33:40 +0700 Subject: [PATCH] update : hubungin kamera ke web, dan kirim data absensi setelah terdeteksi ke database sesuai tgl. next : sesuaikan dengan matkul --- .../API/AbsensiLaporanController.php | 58 ++++ .../API/DeviceStudentController.php | 46 +++ app/Http/Controllers/DevicesController.php | 63 ++++ app/Http/Resources/FotoSiswaResource.php | 23 ++ app/Http/Resources/SiswaResource.php | 25 ++ app/Models/AbsensiGuru.php | 2 +- app/Models/Devices.php | 35 ++- app/Models/Siswa.php | 5 + .../2025_05_5_051500_create_devices_table.php | 1 + database/seeders/SiswaTableSeeder.php | 2 +- .../views/admin/component/sidebar.blade.php | 3 + .../views/admin/devices/create.blade.php | 0 resources/views/admin/devices/edit.blade.php | 0 resources/views/admin/devices/index.blade.php | 183 ++++++++++++ .../views/admin/presensi/siswa.blade.php | 280 ++++++++++-------- resources/views/admin/siswa/index.blade.php | 172 ++++++----- routes/api.php | 9 +- routes/web.php | 11 + 18 files changed, 710 insertions(+), 208 deletions(-) create mode 100644 app/Http/Controllers/API/AbsensiLaporanController.php create mode 100644 app/Http/Controllers/API/DeviceStudentController.php create mode 100644 app/Http/Controllers/DevicesController.php create mode 100644 app/Http/Resources/FotoSiswaResource.php create mode 100644 app/Http/Resources/SiswaResource.php create mode 100644 resources/views/admin/devices/create.blade.php create mode 100644 resources/views/admin/devices/edit.blade.php create mode 100644 resources/views/admin/devices/index.blade.php diff --git a/app/Http/Controllers/API/AbsensiLaporanController.php b/app/Http/Controllers/API/AbsensiLaporanController.php new file mode 100644 index 0000000..91a6b95 --- /dev/null +++ b/app/Http/Controllers/API/AbsensiLaporanController.php @@ -0,0 +1,58 @@ +input('tanggal', Carbon::today()->toDateString()); + $query->whereDate('waktu', $tanggal); + + // 2. Filter berdasarkan Jurusan + if ($request->filled('jurusan_id') && $request->jurusan_id != 'all') { + $query->whereHas('siswa', function ($q) use ($request) { + $q->where('id_jurusan', $request->jurusan_id); + }); + } + + // 3. Filter berdasarkan Kelas + if ($request->filled('kelas_id') && $request->kelas_id != 'all') { + $query->whereHas('siswa', function ($q) use ($request) { + $q->where('id_kelas', $request->kelas_id); + }); + } + + // 4. Filter berdasarkan Device + if ($request->filled('device_id') && $request->device_id != 'all') { + $query->where('id_devices', $request->device_id); + } + + // Eksekusi query + $absensi = $query->latest('waktu')->get(); + + // Ubah data menjadi format JSON yang rapi untuk dikirim + $formattedData = $absensi->map(function ($item, $key) { + return [ + 'no' => $key + 1, + 'nama_siswa' => $item->siswa->nama_siswa ?? 'Siswa Dihapus', + 'jurusan' => $item->siswa->jurusan->nama_jurusan ?? 'N/A', + 'kelas' => $item->siswa->kelas->nama_kelas ?? 'N/A', + 'waktu' => Carbon::parse($item->waktu)->format('H:i:s'), + 'ruangan' => $item->devices->nama_device ?? 'Device Dihapus', + 'status' => strtolower($item->status), + ]; + }); + + return response()->json($formattedData); + } +} diff --git a/app/Http/Controllers/API/DeviceStudentController.php b/app/Http/Controllers/API/DeviceStudentController.php new file mode 100644 index 0000000..79760d2 --- /dev/null +++ b/app/Http/Controllers/API/DeviceStudentController.php @@ -0,0 +1,46 @@ +loadMissing('kelas'); + + // Jika device tidak terhubung dengan kelas, kembalikan pesan error yang jelas. + if (!$device->kelas) { + return response()->json([ + 'success' => false, + 'message' => 'Device ini tidak terhubung dengan kelas manapun.' + ], 404); + } + + // Ambil semua siswa dari kelas yang sama dengan device tersebut + // dan pastikan kita juga memuat relasi 'fotos' untuk setiap siswa. + $students = Siswa::where('id_kelas', $device->id_kelas) + ->with('fotos') // Eager load relasi 'fotos' dari model Siswa + ->get(); + + // Kembalikan data siswa menggunakan SiswaResource untuk format yang rapi. + return SiswaResource::collection($students) + ->additional([ + 'success' => true, + 'message' => 'Berhasil mengambil data siswa untuk device ' . $device->nama_device, + 'device' => [ + 'id' => $device->id, + 'nama' => $device->nama_device, + 'kelas' => $device->kelas->nama_kelas + ] + ]); + } +} diff --git a/app/Http/Controllers/DevicesController.php b/app/Http/Controllers/DevicesController.php new file mode 100644 index 0000000..f5730d1 --- /dev/null +++ b/app/Http/Controllers/DevicesController.php @@ -0,0 +1,63 @@ +latest()->paginate(10); + $kelas = Kelas::orderBy('nama_kelas')->get(); + $ruangan = Ruangan::orderBy('nama_ruangan')->get(); + + return view('admin.devices.index', compact('devices', 'kelas', 'ruangan')); + } + + public function store(Request $request) + { + $validator = Validator::make($request->all(), [ + 'nama_device' => 'required|string|max:255|unique:devices,nama_device', + 'ip_address' => 'nullable|ipv4', + 'id_kelas' => 'required|exists:kelas,id', + 'id_ruangan' => 'required|exists:ruangan,id', + ]); + + if ($validator->fails()) { + return redirect()->back()->withErrors($validator)->withInput(); + } + + Devices::create($request->all()); + + return redirect()->route('admin.devices.index')->with('success', 'Device baru berhasil ditambahkan.'); + } + + public function update(Request $request, Devices $device) + { + $validator = Validator::make($request->all(), [ + 'nama_device' => 'required|string|max:255|unique:devices,nama_device,' . $device->id, + 'ip_address' => 'nullable|ipv4', + 'id_kelas' => 'required|exists:kelas,id', + 'id_ruangan' => 'required|exists:ruangan,id', + ]); + + if ($validator->fails()) { + return redirect()->back()->withErrors($validator)->withInput(); + } + + $device->update($request->all()); + + return redirect()->route('admin.devices.index')->with('success', 'Data device berhasil diperbarui.'); + } + + public function destroy(Devices $device) + { + $device->delete(); + return redirect()->route('admin.devices.index')->with('success', 'Device berhasil dihapus.'); + } +} diff --git a/app/Http/Resources/FotoSiswaResource.php b/app/Http/Resources/FotoSiswaResource.php new file mode 100644 index 0000000..9758738 --- /dev/null +++ b/app/Http/Resources/FotoSiswaResource.php @@ -0,0 +1,23 @@ + + */ + public function toArray(Request $request): array + { + return [ + // 'url' akan berisi link lengkap ke foto di storage + 'url' => Storage::url($this->path), + ]; + } +} diff --git a/app/Http/Resources/SiswaResource.php b/app/Http/Resources/SiswaResource.php new file mode 100644 index 0000000..1c46bdb --- /dev/null +++ b/app/Http/Resources/SiswaResource.php @@ -0,0 +1,25 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'nama_siswa' => $this->nama_siswa, + 'nisn' => $this->nisn, + // 'fotos' akan menjadi array berisi URL foto dari FotoSiswaResource + 'fotos' => FotoSiswaResource::collection($this->whenLoaded('fotos')), + ]; + } +} diff --git a/app/Models/AbsensiGuru.php b/app/Models/AbsensiGuru.php index 4c6e76e..735fee0 100644 --- a/app/Models/AbsensiGuru.php +++ b/app/Models/AbsensiGuru.php @@ -21,7 +21,7 @@ class AbsensiGuru extends Model //Relasi ke tabel devices public function devices() { - return $this->belongsTo(Device::class, 'id_devices'); + return $this->belongsTo(Devices::class, 'id_devices'); } // Relasi ke tabel siswa diff --git a/app/Models/Devices.php b/app/Models/Devices.php index bdbc616..b5bd27d 100644 --- a/app/Models/Devices.php +++ b/app/Models/Devices.php @@ -6,13 +6,34 @@ class Devices extends Model { - protected $fillable = ['nama_device', 'id_kelas', 'id_ruangan']; - - public function kelas(){ - return $this->belongsTo(Kelas::class); + protected $table = 'devices'; + + /** + * Kolom yang bisa diisi secara massal (mass assignable). + * Pastikan semua kolom dari form ada di sini. + */ + protected $fillable = [ + 'nama_device', + 'ip_address', + 'id_kelas', + 'id_ruangan', + ]; + + /** + * Mendefinisikan relasi "belongsTo" ke model Kelas. + * Ini memberitahu Laravel bahwa satu device "milik" satu kelas. + */ + public function kelas() + { + return $this->belongsTo(Kelas::class, 'id_kelas'); } - - public function ruangan(){ - return $this->belongsTo(Ruangan::class); + + /** + * Mendefinisikan relasi "belongsTo" ke model Ruangan. + * Ini memberitahu Laravel bahwa satu device "milik" satu ruangan. + */ + public function ruangan() + { + return $this->belongsTo(Ruangan::class, 'id_ruangan'); } } diff --git a/app/Models/Siswa.php b/app/Models/Siswa.php index d52f08f..9db511c 100644 --- a/app/Models/Siswa.php +++ b/app/Models/Siswa.php @@ -39,4 +39,9 @@ public function absensi() { return $this->hasMany(AbsensiSiswa::class, 'id_siswa'); } + + public function fotos() + { + return $this->hasMany(FotoSiswa::class, 'id_siswa'); + } } \ No newline at end of file diff --git a/database/migrations/2025_05_5_051500_create_devices_table.php b/database/migrations/2025_05_5_051500_create_devices_table.php index df0371b..41c5557 100644 --- a/database/migrations/2025_05_5_051500_create_devices_table.php +++ b/database/migrations/2025_05_5_051500_create_devices_table.php @@ -16,6 +16,7 @@ public function up(): void $table->string('nama_device')->unique(); $table->foreignId('id_kelas')->constrained('kelas')->onDelete('cascade'); $table->foreignId('id_ruangan')->constrained('ruangan')->onDelete('cascade'); + $table->string('ip_address')->nullable(); $table->timestamps(); }); } diff --git a/database/seeders/SiswaTableSeeder.php b/database/seeders/SiswaTableSeeder.php index d84fe73..979b75a 100644 --- a/database/seeders/SiswaTableSeeder.php +++ b/database/seeders/SiswaTableSeeder.php @@ -44,7 +44,7 @@ public function run(): void 'alamat' => 'Jl. Kenanga No. 3', 'no_hp' => '081234567892', 'email' => 'raihan@example.com', - 'id_jurusan' => 2, + 'id_jurusan' => 2, 'id_kelas' => 3, ], ]); diff --git a/resources/views/admin/component/sidebar.blade.php b/resources/views/admin/component/sidebar.blade.php index 10a2620..9afff53 100644 --- a/resources/views/admin/component/sidebar.blade.php +++ b/resources/views/admin/component/sidebar.blade.php @@ -52,6 +52,9 @@ class="block p-2 rounded-lg {{ request()->is('admin/jurusan/index') ? 'bg-blue-1
  • Ruangan
  • +
  • Devices +
  • diff --git a/resources/views/admin/devices/create.blade.php b/resources/views/admin/devices/create.blade.php new file mode 100644 index 0000000..e69de29 diff --git a/resources/views/admin/devices/edit.blade.php b/resources/views/admin/devices/edit.blade.php new file mode 100644 index 0000000..e69de29 diff --git a/resources/views/admin/devices/index.blade.php b/resources/views/admin/devices/index.blade.php new file mode 100644 index 0000000..349e8d4 --- /dev/null +++ b/resources/views/admin/devices/index.blade.php @@ -0,0 +1,183 @@ +@extends('layouts.dashboard') + +@section('title', 'Smart School | Manajemen Device') + +@section('content') +
    + + +
    + @if (session('success')) + + @endif + @if ($errors->any()) + + @endif +
    + +
    + +
    +
    +
    +

    + + Manajemen Device +

    +

    Total {{ $devices->total() }} device terpasang

    +
    + +
    +
    + + +
    +
    + + + + + + + + + + + + + @forelse($devices as $key => $device) + + + + + + + + + @empty + + @endforelse + +
    NoNama DeviceIP AddressKelasRuanganAksi
    {{ $devices->firstItem() + $key }}{{ $device->nama_device }}{{ $device->ip_address ?? 'N/A' }}{{ $device->kelas->nama_kelas ?? '-' }}{{ $device->ruangan->nama_ruangan ?? '-' }} +
    + +
    + @csrf @method('DELETE') + +
    +
    +
    Belum ada device yang ditambahkan.
    +
    +
    {{ $devices->links() }}
    +
    +
    +
    + + + + + +@foreach($devices as $device) + +@endforeach + + + +@endsection diff --git a/resources/views/admin/presensi/siswa.blade.php b/resources/views/admin/presensi/siswa.blade.php index c444e21..82b342c 100644 --- a/resources/views/admin/presensi/siswa.blade.php +++ b/resources/views/admin/presensi/siswa.blade.php @@ -13,62 +13,48 @@
    -
    -

    Filter Data

    - - {{-- Bungkus filter dalam form GET agar bisa dibaca controller --}} -
    -
    - -
    - - {{-- Tambahkan atribut 'name' agar bisa dibaca oleh Request --}} - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    +
    + +
    + +
    - + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    @@ -78,41 +64,17 @@ - - - - - - - + + + + + + + - @forelse ($absensi as $key => $item) - - - - - - - - - - @empty - - - - @endforelse +
    NoNama SiswaJurusanKelasWaktu PresensiRuanganStatusNoNama SiswaJurusanKelasWaktu PresensiRuanganStatus
    {{ $key + 1 }}{{ $item->siswa->nama_siswa ?? 'Siswa Dihapus' }}{{ $item->siswa->jurusan->nama_jurusan ?? 'N/A' }}{{ $item->siswa->kelas->nama_kelas ?? 'N/A' }}{{ \Carbon\Carbon::parse($item->waktu)->format('H:i:s') }}{{ $item->devices->nama_device ?? 'Device Dihapus' }} - @if(strtolower($item->status) == 'hadir') - Hadir - @elseif(strtolower($item->status) == 'terlambat') - Terlambat - @else - {{ ucfirst($item->status) }} - @endif -
    - Tidak ada data yang cocok dengan filter yang dipilih. -
    Memuat data...
    @@ -131,51 +93,135 @@
    - @endsection diff --git a/resources/views/admin/siswa/index.blade.php b/resources/views/admin/siswa/index.blade.php index 07ea596..16ddf62 100644 --- a/resources/views/admin/siswa/index.blade.php +++ b/resources/views/admin/siswa/index.blade.php @@ -3,26 +3,59 @@ @section('title', 'Smart School | Data Siswa') @section('content') -
    +
    + + +
    + + @if (session('success')) + + @endif + + + @if (session('error')) + + @endif +
    +

    - - - + Data Siswa

    Total {{ $siswa->total() }} siswa terdaftar

    @@ -33,23 +66,18 @@ class="flex items-center px-4 py-2 bg-white text-indigo-600 rounded-lg shadow ho
    -
    -
    - +
    +
    - - - +
    - -
    - -
    -
    - @foreach($kelas as $item) @foreach($jurusan as $item) @endforeach - + + + @if(request('search') || request('kelas') || request('jurusan')) - + Reset @endif -
    -
    +
    +
    @@ -83,24 +111,12 @@ class="px-3 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transiti - - - - - - + + + + + + @@ -108,27 +124,21 @@ class="px-3 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transiti - + @empty - + @endforelse @@ -170,8 +172,8 @@ class="text-red-600 hover:text-red-900 p-1 rounded hover:bg-red-50 transition"> -
    - {{ $siswa->links() }} +
    + {{ $siswa->appends(request()->query())->links() }}
    @@ -181,7 +183,18 @@ class="text-red-600 hover:text-red-900 p-1 rounded hover:bg-red-50 transition"> -@endsection \ No newline at end of file +@endsection diff --git a/routes/api.php b/routes/api.php index cefb39f..0811627 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,5 +1,6 @@ group(function () { Route::post('/ring', [BelController::class, 'ring'])->name('api.bel.ring'); @@ -50,4 +51,8 @@ Route::get('/siswa', [SiswaApiController::class, 'index']); Route::get('/devices', [DevicesApiController::class, 'index']); -Route::post('/absensi-siswa', [AbsensiSiswaApiController::class, 'store']); \ No newline at end of file +Route::post('/absensi-siswa', [AbsensiSiswaApiController::class, 'store']); + +Route::get('/devices/{device}/students', [DeviceStudentController::class, 'index']); + +Route::get('/laporan-absensi', AbsensiLaporanController::class); \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index c893a96..0e23a2e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -16,6 +16,7 @@ use App\Http\Controllers\AnnouncementController; use App\Http\Controllers\RuanganController; use App\Http\Controllers\BellHistoryController; +use App\Http\Controllers\DevicesController; use App\Models\AbsensiGuru; use App\Models\AbsensiSiswa; @@ -91,6 +92,16 @@ 'update' => 'admin.ruangan.update', 'destroy' => 'admin.ruangan.destroy', ]); + + Route::resource('devices', DevicesController::class)->names([ + 'index' => 'admin.devices.index', + 'create' => 'admin.devices.create', + 'store' => 'admin.devices.store', + 'edit' => 'admin.devices.edit', + 'update' => 'admin.devices.update', + 'destroy' => 'admin.devices.destroy', + ]); + // Presensi Siswa Route::controller(AbsensiSiswaController::class)->group(function () { Route::get('/presensi/siswa', 'index')->name('admin.presensi.siswa');
    - Foto - - Nama Siswa - - NISN - - Kelas/Jurusan - - Kontak - - Aksi - FotoNama SiswaNISNKelas/JurusanKontakAksi
    - @if($item->foto_siswa) - Foto siswa + {{-- Mengambil foto pertama dari relasi 'fotos' --}} + @if($item->fotos && $item->fotos->isNotEmpty()) + Foto {{ $item->nama_siswa }} @else
    - - - +
    @endif
    {{ $item->nama_siswa }}
    -
    - {{ $item->jenis_kelamin == 'L' ? 'Laki-laki' : 'Perempuan' }} • - {{ \Carbon\Carbon::parse($item->tanggal_lahir)->age }} tahun -
    -
    - {{ $item->nisn }} +
    {{ $item->jenis_kelamin == 'L' ? 'Laki-laki' : 'Perempuan' }} • {{ \Carbon\Carbon::parse($item->tanggal_lahir)->age }} tahun
    {{ $item->nisn }}
    {{ $item->kelas->nama_kelas ?? '-' }}
    {{ $item->jurusan->nama_jurusan ?? '-' }}
    @@ -139,20 +149,14 @@ class="px-3 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transiti
    - - - - + + -
    + @csrf @method('DELETE') -
    @@ -160,9 +164,7 @@ class="text-red-600 hover:text-red-900 p-1 rounded hover:bg-red-50 transition">
    - Tidak ada data siswa ditemukan - Tidak ada data siswa ditemukan.