From 0c08fefabe80d5048a118271df32669e685e30cc Mon Sep 17 00:00:00 2001 From: jouel88 Date: Fri, 15 May 2026 11:03:00 +0700 Subject: [PATCH] update --- .../Controllers/FaceApprovalController.php | 31 ++++ resources/views/face-approval/index.blade.php | 160 +++++++++++++++++- routes/web.php | 1 + 3 files changed, 191 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/FaceApprovalController.php b/app/Http/Controllers/FaceApprovalController.php index c49f9226..e1889198 100644 --- a/app/Http/Controllers/FaceApprovalController.php +++ b/app/Http/Controllers/FaceApprovalController.php @@ -11,12 +11,21 @@ use App\Jobs\ReextractAllFrames; use App\Jobs\RetrainAllModels; use App\Jobs\MigrateExistingEmbeddings; +use App\Services\FaceRecognitionService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; class FaceApprovalController extends Controller { + protected $faceService; + + public function __construct(FaceRecognitionService $faceService) + { + $this->faceService = $faceService; + } + public function index(Request $request) { $query = User::with(['jabatan', 'divisi', 'dataWajah']); @@ -251,4 +260,26 @@ public function migrateEmbeddings() dispatch(new MigrateExistingEmbeddings()); return redirect()->back()->with('success', 'Proses migrasi embedding sedang berjalan di background. Anda akan menerima notifikasi setelah selesai.'); } + + public function uploadVideo(Request $request, $id) + { + $request->validate([ + 'video_wajah' => 'required|file|mimetypes:video/mp4,video/quicktime,video/x-msvideo,video/3gpp,video/x-matroska|max:51200', + ], [ + 'video_wajah.required' => 'File video wajah wajib diupload.', + 'video_wajah.mimetypes' => 'Format video tidak didukung. Gunakan MP4, MOV, atau AVI.', + 'video_wajah.max' => 'Ukuran video terlalu besar (maksimal 50MB).', + ]); + + $user = User::findOrFail($id); + + try { + $result = $this->faceService->enrollFace($user->id, $request->file('video_wajah')); + + return redirect()->back()->with('success', "Video wajah untuk {$user->nama_lengkap} berhasil diupload. Proses ekstraksi frame sedang berjalan."); + } catch (\Exception $e) { + Log::error("Upload video wajah gagal untuk user {$id}: " . $e->getMessage()); + return redirect()->back()->with('error', 'Gagal upload video: ' . $e->getMessage()); + } + } } \ No newline at end of file diff --git a/resources/views/face-approval/index.blade.php b/resources/views/face-approval/index.blade.php index f68143de..37220b2b 100644 --- a/resources/views/face-approval/index.blade.php +++ b/resources/views/face-approval/index.blade.php @@ -271,8 +271,23 @@ class="inline-flex items-center px-3 py-1.5 border border-amber-200 text-xs font Reset + @else - + @endif @@ -304,6 +319,80 @@ class="inline-flex items-center px-3 py-1.5 border border-amber-200 text-xs font + @endsection @section('script') @@ -405,5 +494,74 @@ function stopPolling() { btn.disabled = false; btn.classList.remove('opacity-50', 'cursor-not-allowed'); } + + function openUploadModal(userId, userName) { + const form = document.getElementById('uploadVideoForm'); + form.action = `/face-approval/${userId}/upload-video`; + document.getElementById('uploadUserName').textContent = userName; + clearFile(); + const overlay = document.getElementById('uploadVideoOverlay'); + overlay.classList.remove('hidden'); + overlay.classList.add('flex'); + } + + function closeUploadModal() { + const overlay = document.getElementById('uploadVideoOverlay'); + overlay.classList.add('hidden'); + overlay.classList.remove('flex'); + } + + // Drop zone & file input + const dropZone = document.getElementById('dropZone'); + const fileInput = document.getElementById('videoFileInput'); + + dropZone.addEventListener('click', () => fileInput.click()); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('border-primary', 'bg-primary/5'); + }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('border-primary', 'bg-primary/5'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('border-primary', 'bg-primary/5'); + if (e.dataTransfer.files.length > 0) { + fileInput.files = e.dataTransfer.files; + showFileInfo(e.dataTransfer.files[0]); + } + }); + + fileInput.addEventListener('change', (e) => { + if (e.target.files.length > 0) { + showFileInfo(e.target.files[0]); + } + }); + + function showFileInfo(file) { + const sizeMB = (file.size / 1024 / 1024).toFixed(2); + document.getElementById('fileName').textContent = file.name; + document.getElementById('fileSize').textContent = sizeMB + ' MB'; + document.getElementById('fileInfo').classList.remove('hidden'); + dropZone.classList.add('hidden'); + document.getElementById('uploadSubmitBtn').disabled = false; + } + + function clearFile() { + fileInput.value = ''; + document.getElementById('fileInfo').classList.add('hidden'); + dropZone.classList.remove('hidden'); + document.getElementById('uploadSubmitBtn').disabled = true; + } + + document.getElementById('uploadVideoForm').addEventListener('submit', function() { + const btn = document.getElementById('uploadSubmitBtn'); + btn.disabled = true; + document.getElementById('uploadBtnText').classList.add('hidden'); + document.getElementById('uploadBtnLoading').classList.remove('hidden'); + }); @endsection diff --git a/routes/web.php b/routes/web.php index cd95e779..77adaa8d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -125,6 +125,7 @@ Route::put('/face-approval/{id}/approve', [FaceApprovalController::class, 'approve'])->name('face.approve'); Route::delete('/face-approval/{id}/reject', [FaceApprovalController::class, 'reject'])->name('face.reject'); Route::delete('/face-approval/{id}/reset', [FaceApprovalController::class, 'reset'])->name('face.reset'); + Route::post('/face-approval/{id}/upload-video', [FaceApprovalController::class, 'uploadVideo'])->name('face.upload_video'); Route::resource('role', RoleController::class)->except(['create', 'edit']); });