1552 lines
56 KiB
PHP
1552 lines
56 KiB
PHP
@extends('layouts.frontapp')
|
|
|
|
@section('styles')
|
|
<!-- SweetAlert2 CSS -->
|
|
<link rel="stylesheet" href="{{ asset('css/vendor/sweetalert2.min.css') }}">
|
|
<style>
|
|
.card {
|
|
border: 1px solid #ddd;
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.card-body {
|
|
padding: 20px;
|
|
}
|
|
|
|
.section-header {
|
|
background: linear-gradient(135deg, #2c5aa0 0%, #1e3a8a 100%);
|
|
color: white;
|
|
padding: 12px 20px;
|
|
border-radius: 8px;
|
|
margin: 25px 0 15px 0;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.section-header h5 {
|
|
margin: 0;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.section-header i {
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.form-group label {
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin-bottom: 8px;
|
|
display: block;
|
|
}
|
|
|
|
.form-control {
|
|
border: 2px solid #e9ecef;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.form-control:focus {
|
|
border-color: #2c5aa0;
|
|
box-shadow: 0 0 0 0.2rem rgba(44, 90, 160, 0.25);
|
|
}
|
|
|
|
.form-control[readonly] {
|
|
background-color: #f8f9fa;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.camera-container {
|
|
border: 2px dashed #ddd;
|
|
border-radius: 12px;
|
|
padding: 15px;
|
|
background: #f8f9fa;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.camera-video {
|
|
width: 100%;
|
|
max-width: 400px;
|
|
height: 250px;
|
|
background: #000;
|
|
border-radius: 8px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.camera-controls {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.btn {
|
|
border-radius: 8px;
|
|
font-weight: 500;
|
|
padding: 8px 16px;
|
|
font-size: 14px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, #2c5aa0 0%, #1e3a8a 100%);
|
|
border: none;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.btn-success {
|
|
background: linear-gradient(135deg, #059669 0%, #10b981 100%);
|
|
border: none;
|
|
}
|
|
|
|
.btn-success:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: linear-gradient(135deg, #6c757d 0%, #495057 100%);
|
|
border: none;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.btn-info {
|
|
background: linear-gradient(135deg, #0dcaf0 0%, #0aa2c0 100%);
|
|
border: none;
|
|
}
|
|
|
|
.btn-info:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.btn-lg {
|
|
padding: 15px 30px;
|
|
font-size: 16px;
|
|
}
|
|
|
|
/* Ensure submit button is clickable */
|
|
#submitButton {
|
|
position: relative;
|
|
z-index: 10;
|
|
pointer-events: auto;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#submitButton:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
#submitButton:not(:disabled):hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.photo-preview {
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.photo-preview img {
|
|
max-width: 200px;
|
|
max-height: 150px;
|
|
border-radius: 8px;
|
|
border: 3px solid #059669;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.photo-preview-container {
|
|
background: #f8f9fa;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
border: 1px solid #dee2e6;
|
|
}
|
|
|
|
.camera-controls .btn {
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.camera-controls .btn-sm {
|
|
padding: 6px 12px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.form-control.is-invalid {
|
|
border-color: #dc3545;
|
|
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
|
|
}
|
|
|
|
.form-control.is-invalid:focus {
|
|
border-color: #dc3545;
|
|
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
|
|
}
|
|
|
|
.invalid-feedback {
|
|
display: block;
|
|
width: 100%;
|
|
margin-top: 0.25rem;
|
|
font-size: 80%;
|
|
color: #dc3545;
|
|
}
|
|
|
|
.camera-info {
|
|
margin-top: 8px;
|
|
padding: 5px 10px;
|
|
background: rgba(5, 150, 105, 0.1);
|
|
border-radius: 4px;
|
|
border-left: 3px solid #059669;
|
|
}
|
|
|
|
.alert {
|
|
border-radius: 8px;
|
|
border: none;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
#precheck_notes {
|
|
resize: vertical;
|
|
min-height: 80px;
|
|
}
|
|
|
|
.file-input-hidden {
|
|
display: none;
|
|
}
|
|
|
|
.file-info {
|
|
margin-top: 5px;
|
|
font-size: 12px;
|
|
color: #6c757d;
|
|
}
|
|
|
|
/* Mobile Responsive */
|
|
@media (max-width: 768px) {
|
|
.container {
|
|
padding: 0 10px;
|
|
}
|
|
|
|
.card-body {
|
|
padding: 15px;
|
|
}
|
|
|
|
.section-header {
|
|
padding: 10px 15px;
|
|
margin: 20px 0 10px 0;
|
|
}
|
|
|
|
.section-header h5 {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.form-control {
|
|
padding: 10px 12px;
|
|
font-size: 16px; /* Prevent zoom on iOS */
|
|
}
|
|
|
|
.camera-video {
|
|
height: 200px;
|
|
}
|
|
|
|
.camera-controls {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.btn {
|
|
width: 100%;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.photo-preview img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
.form-row {
|
|
margin: 0;
|
|
}
|
|
|
|
.form-row > .col-md-6 {
|
|
padding: 0 5px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 576px) {
|
|
.mobile-container {
|
|
padding: 0;
|
|
}
|
|
|
|
.card {
|
|
border-radius: 0;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.camera-container {
|
|
padding: 10px;
|
|
}
|
|
|
|
.camera-video {
|
|
height: 180px;
|
|
}
|
|
}
|
|
</style>
|
|
@endsection
|
|
|
|
@section('content')
|
|
<div class="mobile-container">
|
|
<div class="container">
|
|
<div class="row mb-4 mt-4">
|
|
<div class="col-8">
|
|
<a href="/"><img src="{{ asset('logo-ckb.png') }}" style="width: 100%" alt="LOGO CKB"></a>
|
|
</div>
|
|
<div class="col-4 text-right my-auto">
|
|
<a class="btn btn-sm btn-danger mt-3" style="background: red !important;" href="{{ route('logout') }}" onclick="logout(event)">
|
|
{{ __('Logout') }}
|
|
</a>
|
|
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
|
|
@csrf
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<h2 class="text-center">{{ isset($precheck) ? 'Edit Form Precheck' : 'Form Precheck' }}</h2>
|
|
</div>
|
|
</div>
|
|
|
|
@if(session('success'))
|
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
|
{{ session('success') }}
|
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
@endif
|
|
|
|
@if($errors->any())
|
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
|
<ul class="mb-0">
|
|
@foreach($errors->all() as $error)
|
|
<li>{{ $error }}</li>
|
|
@endforeach
|
|
</ul>
|
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
@endif
|
|
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<a href="/" class="btn btn-warning btn-sm">
|
|
Kembali
|
|
</a>
|
|
<form action="{{ isset($precheck) ? route('prechecks.update', [$transaction->id, $precheck->id]) : route('prechecks.store', $transaction->id) }}" method="POST" id="precheckForm" enctype="multipart/form-data" novalidate>
|
|
@csrf
|
|
@if(isset($precheck))
|
|
@method('PUT')
|
|
@endif
|
|
<input type="hidden" name="transaction_id" value="{{ $transaction->id }}">
|
|
|
|
<!-- Informasi Transaksi -->
|
|
<div class="section-header">
|
|
<h5><i class="fas fa-info-circle"></i> Informasi Transaksi</h5>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label>Nomor Polisi</label>
|
|
<input type="text" class="form-control" value="{{ $transaction->police_number }}" readonly>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label>Nomor SPK</label>
|
|
<input type="text" class="form-control" value="{{ $transaction->spk }}" readonly>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Data Dasar -->
|
|
<div class="section-header">
|
|
<h5><i class="fas fa-clipboard-list"></i> Data Dasar</h5>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label>Tanggal Precheck</label>
|
|
<input type="text" class="form-control" value="{{ now()->format('d/m/Y H:i') }}" readonly>
|
|
<small class="form-text text-muted">Tanggal dan waktu saat ini akan digunakan</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="kilometer">Kilometer <span class="text-danger">*</span></label>
|
|
<input type="number" class="form-control" id="kilometer" name="kilometer" step="0.01" value="{{ old('kilometer', $precheck->kilometer ?? '') }}" required>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="pressure_high">Pressure High (PSI) <span class="text-danger">*</span></label>
|
|
<input type="number" class="form-control" id="pressure_high" name="pressure_high" step="0.01" value="{{ old('pressure_high', $precheck->pressure_high ?? '') }}" required>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="pressure_low">Pressure Low (PSI)</label>
|
|
<input type="number" class="form-control" id="pressure_low" name="pressure_low" step="0.01" value="{{ old('pressure_low', $precheck->pressure_low ?? '') }}">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Foto Depan -->
|
|
<div class="section-header">
|
|
<h5><i class="fas fa-camera"></i> Foto Depan Kendaraan <span class="text-danger">*</span></h5>
|
|
</div>
|
|
<div class="form-group">
|
|
<input type="file" id="front_image" name="front_image" accept="image/*" class="file-input-hidden" {{ !isset($precheck) ? 'required' : '' }}>
|
|
<div class="camera-container">
|
|
<video id="front_camera" autoplay playsinline class="camera-video"></video>
|
|
<canvas id="front_canvas" style="display: none;"></canvas>
|
|
<div class="camera-controls">
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="startCamera('front_camera')">
|
|
<i class="fas fa-camera"></i> Buka Kamera
|
|
</button>
|
|
<button type="button" class="btn btn-success btn-sm" onclick="capturePhoto('front_camera', 'front_canvas', 'front_image', 'front_preview')">
|
|
<i class="fas fa-camera-retro"></i> Ambil Foto
|
|
</button>
|
|
<button type="button" class="btn btn-warning btn-sm" onclick="stopCamera('front_camera')">
|
|
<i class="fas fa-times"></i> Tutup Kamera
|
|
</button>
|
|
<button type="button" class="btn btn-info btn-sm" onclick="switchCamera('front_camera')">
|
|
<i class="fas fa-sync"></i> Ganti Kamera
|
|
</button>
|
|
</div>
|
|
<div class="mt-2">
|
|
<small class="text-muted">Atau upload foto dari galeri (maksimal 20MB, format: JPG, JPEG, PNG):</small>
|
|
<input type="file" class="form-control-file mt-1" accept="image/jpeg,image/jpg,image/png" onchange="handleFileUpload(this, 'front_image', 'front_preview')">
|
|
</div>
|
|
<div id="front_preview" class="photo-preview">
|
|
@if(isset($precheck) && $precheck->front_image_url)
|
|
<div class="photo-preview-container">
|
|
<img src="{{ $precheck->front_image_url }}" style="max-width: 200px; max-height: 150px; border-radius: 8px; border: 3px solid #059669; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
<div class="mt-2">
|
|
<small class="text-info"><i class="fas fa-image"></i> Foto saat ini</small>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Foto Suhu Kabin -->
|
|
<div class="section-header">
|
|
<h5><i class="fas fa-thermometer-half"></i> Foto Suhu Kabin</h5>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="cabin_temperature">Suhu Kabin (°C)</label>
|
|
<input type="number" class="form-control" id="cabin_temperature" name="cabin_temperature" step="0.1" value="{{ old('cabin_temperature', $precheck->cabin_temperature ?? '') }}" placeholder="Masukkan suhu kabin">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="cabin_temperature_image">Foto Suhu Kabin</label>
|
|
<input type="file" id="cabin_temperature_image" name="cabin_temperature_image" accept="image/*" class="file-input-hidden">
|
|
<div class="camera-container">
|
|
<video id="cabin_camera" autoplay playsinline class="camera-video"></video>
|
|
<canvas id="cabin_canvas" style="display: none;"></canvas>
|
|
<div class="camera-controls">
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="startCamera('cabin_camera')">
|
|
<i class="fas fa-camera"></i> Buka Kamera
|
|
</button>
|
|
<button type="button" class="btn btn-success btn-sm" onclick="capturePhoto('cabin_camera', 'cabin_canvas', 'cabin_temperature_image', 'cabin_preview')">
|
|
<i class="fas fa-camera-retro"></i> Ambil Foto
|
|
</button>
|
|
<button type="button" class="btn btn-warning btn-sm" onclick="stopCamera('cabin_camera')">
|
|
<i class="fas fa-times"></i> Tutup Kamera
|
|
</button>
|
|
<button type="button" class="btn btn-info btn-sm" onclick="switchCamera('cabin_camera')">
|
|
<i class="fas fa-sync"></i> Ganti Kamera
|
|
</button>
|
|
</div>
|
|
<div class="mt-2">
|
|
<small class="text-muted">Atau upload foto dari galeri (maksimal 20MB, format: JPG, JPEG, PNG):</small>
|
|
<input type="file" class="form-control-file mt-1" accept="image/jpeg,image/jpg,image/png" onchange="handleFileUpload(this, 'cabin_temperature_image', 'cabin_preview')">
|
|
</div>
|
|
<div id="cabin_preview" class="photo-preview">
|
|
@if(isset($precheck) && $precheck->cabin_temperature_image_url)
|
|
<div class="photo-preview-container">
|
|
<img src="{{ $precheck->cabin_temperature_image_url }}" style="max-width: 200px; max-height: 150px; border-radius: 8px; border: 3px solid #059669; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
<div class="mt-2">
|
|
<small class="text-info"><i class="fas fa-image"></i> Foto saat ini</small>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kondisi AC -->
|
|
<div class="section-header">
|
|
<h5><i class="fas fa-snowflake"></i> Kondisi AC</h5>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="ac_condition">Kondisi AC</label>
|
|
<select class="form-control" id="ac_condition" name="ac_condition">
|
|
<option value="">Pilih Kondisi</option>
|
|
@foreach($acConditions as $condition)
|
|
<option value="{{ $condition }}" {{ old('ac_condition', $precheck->ac_condition ?? '') == $condition ? 'selected' : '' }}>{{ ucfirst($condition) }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="ac_image">Foto AC</label>
|
|
<input type="file" id="ac_image" name="ac_image" accept="image/*" class="file-input-hidden">
|
|
<div class="camera-container">
|
|
<video id="ac_camera" autoplay playsinline class="camera-video"></video>
|
|
<canvas id="ac_canvas" style="display: none;"></canvas>
|
|
<div class="camera-controls">
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="startCamera('ac_camera')">
|
|
<i class="fas fa-camera"></i> Buka Kamera
|
|
</button>
|
|
<button type="button" class="btn btn-success btn-sm" onclick="capturePhoto('ac_camera', 'ac_canvas', 'ac_image', 'ac_preview')">
|
|
<i class="fas fa-camera-retro"></i> Ambil Foto
|
|
</button>
|
|
<button type="button" class="btn btn-warning btn-sm" onclick="stopCamera('ac_camera')">
|
|
<i class="fas fa-times"></i> Tutup Kamera
|
|
</button>
|
|
<button type="button" class="btn btn-info btn-sm" onclick="switchCamera('ac_camera')">
|
|
<i class="fas fa-sync"></i> Ganti Kamera
|
|
</button>
|
|
</div>
|
|
<div class="mt-2">
|
|
<small class="text-muted">Atau upload foto dari galeri (maksimal 20MB, format: JPG, JPEG, PNG):</small>
|
|
<input type="file" class="form-control-file mt-1" accept="image/jpeg,image/jpg,image/png" onchange="handleFileUpload(this, 'ac_image', 'ac_preview')">
|
|
</div>
|
|
<div id="ac_preview" class="photo-preview">
|
|
@if(isset($precheck) && $precheck->ac_image_url)
|
|
<div class="photo-preview-container">
|
|
<img src="{{ $precheck->ac_image_url }}" style="max-width: 200px; max-height: 150px; border-radius: 8px; border: 3px solid #059669; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
<div class="mt-2">
|
|
<small class="text-info"><i class="fas fa-image"></i> Foto saat ini</small>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kondisi Blower -->
|
|
<div class="section-header">
|
|
<h5><i class="fas fa-fan"></i> Kondisi Blower</h5>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="blower_condition">Kondisi Blower</label>
|
|
<select class="form-control" id="blower_condition" name="blower_condition">
|
|
<option value="">Pilih Kondisi</option>
|
|
@foreach($blowerConditions as $condition)
|
|
<option value="{{ $condition }}" {{ old('blower_condition', $precheck->blower_condition ?? '') == $condition ? 'selected' : '' }}>{{ ucfirst($condition) }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="blower_image">Foto Blower</label>
|
|
<input type="file" id="blower_image" name="blower_image" accept="image/*" class="file-input-hidden">
|
|
<div class="camera-container">
|
|
<video id="blower_camera" autoplay playsinline class="camera-video"></video>
|
|
<canvas id="blower_canvas" style="display: none;"></canvas>
|
|
<div class="camera-controls">
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="startCamera('blower_camera')">
|
|
<i class="fas fa-camera"></i> Buka Kamera
|
|
</button>
|
|
<button type="button" class="btn btn-success btn-sm" onclick="capturePhoto('blower_camera', 'blower_canvas', 'blower_image', 'blower_preview')">
|
|
<i class="fas fa-camera-retro"></i> Ambil Foto
|
|
</button>
|
|
<button type="button" class="btn btn-warning btn-sm" onclick="stopCamera('blower_camera')">
|
|
<i class="fas fa-times"></i> Tutup Kamera
|
|
</button>
|
|
<button type="button" class="btn btn-info btn-sm" onclick="switchCamera('blower_camera')">
|
|
<i class="fas fa-sync"></i> Ganti Kamera
|
|
</button>
|
|
</div>
|
|
<div class="mt-2">
|
|
<small class="text-muted">Atau upload foto dari galeri (maksimal 20MB, format: JPG, JPEG, PNG):</small>
|
|
<input type="file" class="form-control-file mt-1" accept="image/jpeg,image/jpg,image/png" onchange="handleFileUpload(this, 'blower_image', 'blower_preview')">
|
|
</div>
|
|
<div id="blower_preview" class="photo-preview">
|
|
@if(isset($precheck) && $precheck->blower_image_url)
|
|
<div class="photo-preview-container">
|
|
<img src="{{ $precheck->blower_image_url }}" style="max-width: 200px; max-height: 150px; border-radius: 8px; border: 3px solid #059669; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
<div class="mt-2">
|
|
<small class="text-info"><i class="fas fa-image"></i> Foto saat ini</small>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kondisi Evaporator -->
|
|
<div class="section-header">
|
|
<h5><i class="fas fa-tint"></i> Kondisi Evaporator</h5>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="evaporator_condition">Kondisi Evaporator</label>
|
|
<select class="form-control" id="evaporator_condition" name="evaporator_condition">
|
|
<option value="">Pilih Kondisi</option>
|
|
@foreach($evaporatorConditions as $condition)
|
|
<option value="{{ $condition }}" {{ old('evaporator_condition', $precheck->evaporator_condition ?? '') == $condition ? 'selected' : '' }}>{{ ucfirst($condition) }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="evaporator_image">Foto Evaporator</label>
|
|
<input type="file" id="evaporator_image" name="evaporator_image" accept="image/*" class="file-input-hidden">
|
|
<div class="camera-container">
|
|
<video id="evaporator_camera" autoplay playsinline class="camera-video"></video>
|
|
<canvas id="evaporator_canvas" style="display: none;"></canvas>
|
|
<div class="camera-controls">
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="startCamera('evaporator_camera')">
|
|
<i class="fas fa-camera"></i> Buka Kamera
|
|
</button>
|
|
<button type="button" class="btn btn-success btn-sm" onclick="capturePhoto('evaporator_camera', 'evaporator_canvas', 'evaporator_image', 'evaporator_preview')">
|
|
<i class="fas fa-camera-retro"></i> Ambil Foto
|
|
</button>
|
|
<button type="button" class="btn btn-warning btn-sm" onclick="stopCamera('evaporator_camera')">
|
|
<i class="fas fa-times"></i> Tutup Kamera
|
|
</button>
|
|
<button type="button" class="btn btn-info btn-sm" onclick="switchCamera('evaporator_camera')">
|
|
<i class="fas fa-sync"></i> Ganti Kamera
|
|
</button>
|
|
</div>
|
|
<div class="mt-2">
|
|
<small class="text-muted">Atau upload foto dari galeri (maksimal 20MB, format: JPG, JPEG, PNG):</small>
|
|
<input type="file" class="form-control-file mt-1" accept="image/jpeg,image/jpg,image/png" onchange="handleFileUpload(this, 'evaporator_image', 'evaporator_preview')">
|
|
</div>
|
|
<div id="evaporator_preview" class="photo-preview">
|
|
@if(isset($precheck) && $precheck->evaporator_image_url)
|
|
<div class="photo-preview-container">
|
|
<img src="{{ $precheck->evaporator_image_url }}" style="max-width: 200px; max-height: 150px; border-radius: 8px; border: 3px solid #059669; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
<div class="mt-2">
|
|
<small class="text-info"><i class="fas fa-image"></i> Foto saat ini</small>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kondisi Compressor -->
|
|
<div class="section-header">
|
|
<h5><i class="fas fa-cogs"></i> Kondisi Compressor</h5>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="compressor_condition">Kondisi Compressor</label>
|
|
<select class="form-control" id="compressor_condition" name="compressor_condition">
|
|
<option value="">Pilih Kondisi</option>
|
|
@foreach($compressorConditions as $condition)
|
|
<option value="{{ $condition }}" {{ old('compressor_condition', $precheck->compressor_condition ?? '') == $condition ? 'selected' : '' }}>{{ ucfirst($condition) }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="precheck_notes">Catatan Tambahan</label>
|
|
<textarea class="form-control" id="precheck_notes" name="precheck_notes" rows="3" placeholder="Masukkan catatan tambahan jika ada...">{{ old('precheck_notes', $precheck->precheck_notes ?? '') }}</textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tombol Submit -->
|
|
<div class="section-header">
|
|
<h5><i class="fas fa-save"></i> Simpan Data</h5>
|
|
</div>
|
|
<div class="form-group text-center">
|
|
<button type="submit" class="btn btn-primary btn-lg" id="submitButton">
|
|
<i class="fas fa-save"></i> {{ isset($precheck) ? 'Update Precheck' : 'Simpan Precheck' }}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@section('javascripts')
|
|
<!-- SweetAlert2 JS -->
|
|
<script src="{{ asset('js/vendor/sweetalert2.min.js') }}"></script>
|
|
<script>
|
|
let streams = {};
|
|
|
|
// Logout function
|
|
function logout(event){
|
|
event.preventDefault();
|
|
|
|
// Stop all cameras before logout
|
|
Object.keys(streams).forEach(videoId => {
|
|
stopCamera(videoId);
|
|
});
|
|
|
|
Swal.fire({
|
|
title: 'Logout?',
|
|
text: "Anda akan keluar dari sistem!",
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#d33',
|
|
cancelButtonColor: '#dedede',
|
|
confirmButtonText: 'Logout'
|
|
}).then((result) => {
|
|
if (result.value) {
|
|
$('#logout-form').submit();
|
|
}
|
|
})
|
|
}
|
|
|
|
// Fallback untuk browser lama
|
|
if (navigator.mediaDevices === undefined) {
|
|
navigator.mediaDevices = {};
|
|
}
|
|
|
|
if (navigator.mediaDevices.getUserMedia === undefined) {
|
|
navigator.mediaDevices.getUserMedia = function(constraints) {
|
|
// Coba berbagai versi getUserMedia
|
|
const getUserMedia = navigator.webkitGetUserMedia ||
|
|
navigator.mozGetUserMedia ||
|
|
navigator.msGetUserMedia ||
|
|
navigator.oGetUserMedia;
|
|
|
|
if (!getUserMedia) {
|
|
return Promise.reject(new Error('getUserMedia tidak didukung di browser ini'));
|
|
}
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
getUserMedia.call(navigator, constraints, resolve, reject);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Tambahan fallback untuk browser yang sangat lama
|
|
if (navigator.getUserMedia === undefined) {
|
|
navigator.getUserMedia = navigator.mediaDevices.getUserMedia;
|
|
}
|
|
|
|
// Get available cameras and select back camera
|
|
async function getBackCamera() {
|
|
try {
|
|
// Minta izin kamera terlebih dahulu
|
|
await navigator.mediaDevices.getUserMedia({ video: true });
|
|
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
|
|
|
console.log('Kamera yang tersedia:', videoDevices.map(d => ({ label: d.label, deviceId: d.deviceId })));
|
|
|
|
// Cari kamera belakang berdasarkan label atau deviceId
|
|
const backCamera = videoDevices.find(device =>
|
|
device.label.toLowerCase().includes('back') ||
|
|
device.label.toLowerCase().includes('belakang') ||
|
|
device.label.toLowerCase().includes('rear') ||
|
|
device.label.toLowerCase().includes('environment') ||
|
|
device.deviceId.includes('back') ||
|
|
device.deviceId.includes('rear')
|
|
);
|
|
|
|
if (backCamera) {
|
|
console.log('Kamera belakang ditemukan:', backCamera.label);
|
|
return backCamera.deviceId;
|
|
}
|
|
|
|
// Jika tidak ada kamera belakang yang terdeteksi, gunakan kamera pertama
|
|
if (videoDevices.length > 0) {
|
|
console.log('Menggunakan kamera pertama:', videoDevices[0].label);
|
|
return videoDevices[0].deviceId;
|
|
}
|
|
|
|
return null;
|
|
} catch (err) {
|
|
console.log('Tidak dapat mendapatkan daftar kamera:', err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Start camera
|
|
async function startCamera(videoId) {
|
|
try {
|
|
const video = document.getElementById(videoId);
|
|
|
|
// Cek apakah browser mendukung getUserMedia
|
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
throw new Error('Browser tidak mendukung akses kamera');
|
|
}
|
|
|
|
// Cek apakah kamera sudah berjalan
|
|
if (streams[videoId] && streams[videoId].active) {
|
|
console.log('Kamera sudah berjalan untuk:', videoId);
|
|
return;
|
|
}
|
|
|
|
// Stop stream yang sedang berjalan untuk kamera lain
|
|
Object.keys(streams).forEach(key => {
|
|
if (streams[key] && streams[key].active) {
|
|
streams[key].getTracks().forEach(track => track.stop());
|
|
delete streams[key];
|
|
}
|
|
});
|
|
|
|
// Dapatkan deviceId kamera belakang
|
|
const backCameraId = await getBackCamera();
|
|
|
|
let stream;
|
|
|
|
if (backCameraId) {
|
|
// Gunakan deviceId kamera belakang yang spesifik
|
|
const constraints = {
|
|
video: {
|
|
deviceId: { exact: backCameraId },
|
|
width: { min: 320, ideal: 640, max: 1280 },
|
|
height: { min: 240, ideal: 480, max: 720 },
|
|
aspectRatio: { ideal: 4/3 }
|
|
}
|
|
};
|
|
|
|
try {
|
|
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
console.log('Menggunakan kamera belakang dengan deviceId:', backCameraId);
|
|
} catch (err) {
|
|
console.log('Gagal menggunakan kamera belakang, mencoba fallback...');
|
|
// Fallback ke constraint biasa
|
|
stream = await navigator.mediaDevices.getUserMedia({
|
|
video: {
|
|
width: { min: 320, ideal: 640, max: 1280 },
|
|
height: { min: 240, ideal: 480, max: 720 },
|
|
aspectRatio: { ideal: 4/3 },
|
|
facingMode: { ideal: 'environment' }
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
// Jika tidak bisa mendapatkan deviceId, gunakan facingMode
|
|
const constraints = {
|
|
video: {
|
|
width: { min: 320, ideal: 640, max: 1280 },
|
|
height: { min: 240, ideal: 480, max: 720 },
|
|
aspectRatio: { ideal: 4/3 },
|
|
facingMode: { ideal: 'environment' } // Kamera belakang
|
|
}
|
|
};
|
|
|
|
try {
|
|
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
} catch (err) {
|
|
console.log('Kamera belakang tidak tersedia, mencoba kamera depan...');
|
|
|
|
// Fallback ke kamera depan
|
|
const frontCameraConstraints = {
|
|
video: {
|
|
width: { min: 320, ideal: 640, max: 1280 },
|
|
height: { min: 240, ideal: 480, max: 720 },
|
|
aspectRatio: { ideal: 4/3 },
|
|
facingMode: { ideal: 'user' } // Kamera depan
|
|
}
|
|
};
|
|
|
|
try {
|
|
stream = await navigator.mediaDevices.getUserMedia(frontCameraConstraints);
|
|
} catch (frontErr) {
|
|
// Jika kamera depan juga gagal, coba tanpa constraint facingMode
|
|
const fallbackConstraints = {
|
|
video: {
|
|
width: { min: 320, ideal: 640, max: 1280 },
|
|
height: { min: 240, ideal: 480, max: 720 },
|
|
aspectRatio: { ideal: 4/3 }
|
|
}
|
|
};
|
|
|
|
stream = await navigator.mediaDevices.getUserMedia(fallbackConstraints);
|
|
}
|
|
}
|
|
}
|
|
|
|
video.srcObject = stream;
|
|
streams[videoId] = stream;
|
|
|
|
// Tunggu video siap
|
|
video.onloadedmetadata = function() {
|
|
video.play();
|
|
|
|
// Tampilkan informasi kamera yang digunakan
|
|
const cameraInfo = document.createElement('div');
|
|
cameraInfo.className = 'camera-info';
|
|
cameraInfo.innerHTML = `
|
|
<small class="text-success">
|
|
<i class="fas fa-camera"></i> Kamera aktif - ${backCameraId ? 'Kamera belakang' : 'Kamera default'}
|
|
</small>
|
|
`;
|
|
|
|
// Hapus info kamera sebelumnya jika ada
|
|
const existingInfo = video.parentElement.querySelector('.camera-info');
|
|
if (existingInfo) {
|
|
existingInfo.remove();
|
|
}
|
|
|
|
video.parentElement.appendChild(cameraInfo);
|
|
};
|
|
|
|
video.onerror = function(e) {
|
|
console.error('Error pada video:', e);
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Error Video',
|
|
text: 'Error pada video stream',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
};
|
|
|
|
console.log('Kamera berhasil dibuka untuk:', videoId);
|
|
|
|
// Tampilkan notifikasi sukses
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Kamera Berhasil Dibuka',
|
|
text: backCameraId ? 'Menggunakan kamera belakang' : 'Menggunakan kamera default',
|
|
timer: 2000,
|
|
showConfirmButton: false,
|
|
toast: true,
|
|
position: 'top-end'
|
|
});
|
|
|
|
} catch (err) {
|
|
// Pesan error yang lebih spesifik
|
|
let errorMessage = 'Tidak dapat mengakses kamera. ';
|
|
|
|
if (err.name === 'NotAllowedError') {
|
|
errorMessage += 'Izin kamera ditolak. Silakan:\n1. Klik ikon kamera di address bar\n2. Pilih "Allow"\n3. Refresh halaman';
|
|
} else if (err.name === 'NotFoundError') {
|
|
errorMessage += 'Kamera tidak ditemukan. Pastikan HP memiliki kamera.';
|
|
} else if (err.name === 'NotReadableError') {
|
|
errorMessage += 'Kamera sedang digunakan aplikasi lain. Tutup aplikasi kamera lain.';
|
|
} else if (err.name === 'OverconstrainedError') {
|
|
errorMessage += 'Kamera tidak mendukung resolusi yang diminta.';
|
|
} else if (err.name === 'SecurityError') {
|
|
errorMessage += 'Akses kamera diblokir. Pastikan menggunakan HTTPS atau localhost.';
|
|
} else {
|
|
errorMessage += 'Error: ' + err.message;
|
|
}
|
|
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Error Kamera',
|
|
text: errorMessage,
|
|
confirmButtonText: 'OK'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Capture photo and convert to file
|
|
function capturePhoto(videoId, canvasId, inputId, previewId) {
|
|
const video = document.getElementById(videoId);
|
|
const canvas = document.getElementById(canvasId);
|
|
const fileInput = document.getElementById(inputId);
|
|
const preview = document.getElementById(previewId);
|
|
|
|
if (!video.srcObject) {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Kamera Belum Dibuka',
|
|
text: 'Silakan buka kamera terlebih dahulu',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Pastikan video sudah siap
|
|
if (video.videoWidth === 0 || video.videoHeight === 0) {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Video Belum Siap',
|
|
text: 'Video belum siap. Tunggu sebentar dan coba lagi.',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const context = canvas.getContext('2d');
|
|
canvas.width = video.videoWidth;
|
|
canvas.height = video.videoHeight;
|
|
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
|
|
// Convert canvas ke File object
|
|
canvas.toBlob(function(blob) {
|
|
// Buat File object
|
|
const file = new File([blob], `photo_${Date.now()}.jpg`, {
|
|
type: 'image/jpeg',
|
|
lastModified: Date.now()
|
|
});
|
|
|
|
// Assign ke file input
|
|
const dataTransfer = new DataTransfer();
|
|
dataTransfer.items.add(file);
|
|
fileInput.files = dataTransfer.files;
|
|
|
|
// Preview dengan tombol batal dan retake
|
|
const url = URL.createObjectURL(blob);
|
|
preview.innerHTML = `
|
|
<div class="photo-preview-container">
|
|
<img src="${url}" style="max-width: 200px; max-height: 150px; border-radius: 8px; border: 3px solid #059669; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
<div class="mt-2">
|
|
<small class="text-success"><i class="fas fa-check"></i> Foto berhasil diambil</small>
|
|
<br>
|
|
<small class="text-muted">Ukuran: ${(file.size / 1024).toFixed(1)} KB</small>
|
|
</div>
|
|
<div class="mt-2">
|
|
<button type="button" class="btn btn-warning btn-sm mr-1" onclick="retakePhoto('${videoId}', '${inputId}', '${previewId}')">
|
|
<i class="fas fa-redo"></i> Ambil Ulang
|
|
</button>
|
|
<button type="button" class="btn btn-danger btn-sm" onclick="cancelPhoto('${inputId}', '${previewId}')">
|
|
<i class="fas fa-times"></i> Batal
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Stop kamera setelah mengambil foto
|
|
stopCamera(videoId);
|
|
|
|
// Tampilkan notifikasi foto berhasil diambil
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Foto Berhasil Diambil',
|
|
text: 'Foto berhasil disimpan',
|
|
timer: 2000,
|
|
showConfirmButton: false,
|
|
toast: true,
|
|
position: 'top-end'
|
|
});
|
|
|
|
}, 'image/jpeg', 0.8);
|
|
|
|
} catch (err) {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Gagal Mengambil Foto',
|
|
text: 'Gagal mengambil foto: ' + err.message,
|
|
confirmButtonText: 'OK'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Function to retake photo
|
|
function retakePhoto(videoId, inputId, previewId) {
|
|
const fileInput = document.getElementById(inputId);
|
|
const preview = document.getElementById(previewId);
|
|
|
|
// Clear file input
|
|
const dataTransfer = new DataTransfer();
|
|
fileInput.files = dataTransfer.files;
|
|
|
|
// Clear preview
|
|
preview.innerHTML = '';
|
|
|
|
// Stop any existing camera first
|
|
stopCamera(videoId);
|
|
|
|
// Start camera again after a short delay
|
|
setTimeout(() => {
|
|
startCamera(videoId);
|
|
}, 100);
|
|
|
|
// Tampilkan notifikasi foto diambil ulang
|
|
Swal.fire({
|
|
icon: 'info',
|
|
title: 'Mengambil Foto Ulang',
|
|
text: 'Kamera dibuka untuk mengambil foto ulang',
|
|
timer: 1500,
|
|
showConfirmButton: false,
|
|
toast: true,
|
|
position: 'top-end'
|
|
});
|
|
}
|
|
|
|
// Function to cancel photo
|
|
function cancelPhoto(inputId, previewId) {
|
|
const fileInput = document.getElementById(inputId);
|
|
const preview = document.getElementById(previewId);
|
|
|
|
// Clear file input
|
|
const dataTransfer = new DataTransfer();
|
|
fileInput.files = dataTransfer.files;
|
|
|
|
// Clear preview
|
|
preview.innerHTML = '';
|
|
|
|
// Tampilkan notifikasi foto dibatalkan
|
|
Swal.fire({
|
|
icon: 'info',
|
|
title: 'Foto Dibatalkan',
|
|
text: 'Foto berhasil dibatalkan',
|
|
timer: 1500,
|
|
showConfirmButton: false,
|
|
toast: true,
|
|
position: 'top-end'
|
|
});
|
|
}
|
|
|
|
// Function to switch between front and back camera
|
|
async function switchCamera(videoId) {
|
|
const video = document.getElementById(videoId);
|
|
|
|
if (!streams[videoId]) {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Kamera Belum Dibuka',
|
|
text: 'Silakan buka kamera terlebih dahulu',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Get current facing mode before stopping
|
|
const currentTracks = streams[videoId] ? streams[videoId].getVideoTracks() : [];
|
|
let currentFacingMode = 'environment'; // default to back camera
|
|
|
|
if (currentTracks.length > 0) {
|
|
const settings = currentTracks[0].getSettings();
|
|
if (settings.facingMode) {
|
|
currentFacingMode = settings.facingMode;
|
|
}
|
|
}
|
|
|
|
// Switch to opposite camera
|
|
const newFacingMode = currentFacingMode === 'environment' ? 'user' : 'environment';
|
|
|
|
// Stop current stream
|
|
stopCamera(videoId);
|
|
|
|
// Wait a bit before starting new camera
|
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
|
|
// Try to get the specific camera
|
|
let stream;
|
|
|
|
try {
|
|
// First try with specific facing mode
|
|
const constraints = {
|
|
video: {
|
|
width: { min: 320, ideal: 640, max: 1280 },
|
|
height: { min: 240, ideal: 480, max: 720 },
|
|
aspectRatio: { ideal: 4/3 },
|
|
facingMode: { ideal: newFacingMode }
|
|
}
|
|
};
|
|
|
|
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
} catch (err) {
|
|
console.log('Gagal dengan facingMode, mencoba dengan deviceId...');
|
|
|
|
// If facingMode fails, try to get available devices and switch
|
|
try {
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
|
|
|
if (videoDevices.length > 1) {
|
|
// Find current device index
|
|
const currentDevice = videoDevices.find(device => {
|
|
if (currentTracks.length > 0) {
|
|
return device.deviceId === currentTracks[0].getSettings().deviceId;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
const currentIndex = currentDevice ? videoDevices.indexOf(currentDevice) : 0;
|
|
const nextIndex = (currentIndex + 1) % videoDevices.length;
|
|
const nextDevice = videoDevices[nextIndex];
|
|
|
|
console.log('Beralih dari device:', currentDevice?.label, 'ke:', nextDevice?.label);
|
|
|
|
const deviceConstraints = {
|
|
video: {
|
|
deviceId: { exact: nextDevice.deviceId },
|
|
width: { min: 320, ideal: 640, max: 1280 },
|
|
height: { min: 240, ideal: 480, max: 720 },
|
|
aspectRatio: { ideal: 4/3 }
|
|
}
|
|
};
|
|
|
|
stream = await navigator.mediaDevices.getUserMedia(deviceConstraints);
|
|
} else {
|
|
// Fallback to basic constraints
|
|
stream = await navigator.mediaDevices.getUserMedia({
|
|
video: {
|
|
width: { min: 320, ideal: 640, max: 1280 },
|
|
height: { min: 240, ideal: 480, max: 720 },
|
|
aspectRatio: { ideal: 4/3 }
|
|
}
|
|
});
|
|
}
|
|
} catch (deviceErr) {
|
|
console.error('Gagal beralih dengan deviceId:', deviceErr);
|
|
throw deviceErr;
|
|
}
|
|
}
|
|
|
|
video.srcObject = stream;
|
|
streams[videoId] = stream;
|
|
|
|
// Update camera info
|
|
video.onloadedmetadata = function() {
|
|
video.play();
|
|
|
|
const cameraInfo = document.createElement('div');
|
|
cameraInfo.className = 'camera-info';
|
|
cameraInfo.innerHTML = `
|
|
<small class="text-success">
|
|
<i class="fas fa-camera"></i> Kamera aktif - ${newFacingMode === 'environment' ? 'Kamera belakang' : 'Kamera depan'}
|
|
</small>
|
|
`;
|
|
|
|
const existingInfo = video.parentElement.querySelector('.camera-info');
|
|
if (existingInfo) {
|
|
existingInfo.remove();
|
|
}
|
|
|
|
video.parentElement.appendChild(cameraInfo);
|
|
};
|
|
|
|
console.log('Berhasil beralih ke kamera:', newFacingMode === 'environment' ? 'belakang' : 'depan');
|
|
|
|
// Show success notification
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Kamera Berhasil Diganti',
|
|
text: `Berhasil beralih ke ${newFacingMode === 'environment' ? 'kamera belakang' : 'kamera depan'}`,
|
|
timer: 1500,
|
|
showConfirmButton: false,
|
|
toast: true,
|
|
position: 'top-end'
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error('Gagal beralih kamera:', err);
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Gagal Beralih Kamera',
|
|
text: 'Tidak dapat beralih ke kamera lain: ' + err.message,
|
|
confirmButtonText: 'OK'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Function to stop camera
|
|
function stopCamera(videoId) {
|
|
const video = document.getElementById(videoId);
|
|
|
|
if (streams[videoId]) {
|
|
streams[videoId].getTracks().forEach(track => {
|
|
track.stop();
|
|
console.log('Track stopped:', track.kind);
|
|
});
|
|
delete streams[videoId];
|
|
}
|
|
|
|
if (video.srcObject) {
|
|
video.srcObject = null;
|
|
}
|
|
|
|
// Hapus info kamera
|
|
const cameraInfo = video.parentElement.querySelector('.camera-info');
|
|
if (cameraInfo) {
|
|
cameraInfo.remove();
|
|
}
|
|
|
|
console.log('Kamera ditutup untuk:', videoId);
|
|
|
|
// Tampilkan notifikasi kamera ditutup
|
|
Swal.fire({
|
|
icon: 'info',
|
|
title: 'Kamera Ditutup',
|
|
text: 'Kamera berhasil ditutup',
|
|
timer: 1500,
|
|
showConfirmButton: false,
|
|
toast: true,
|
|
position: 'top-end'
|
|
});
|
|
}
|
|
|
|
// Handle file upload from gallery
|
|
function handleFileUpload(input, inputId, previewId) {
|
|
const file = input.files[0];
|
|
if (!file) return;
|
|
|
|
// Validasi tipe file yang lebih spesifik
|
|
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png'];
|
|
if (!allowedTypes.includes(file.type.toLowerCase())) {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Format File Tidak Didukung',
|
|
text: 'Hanya file JPG, JPEG, dan PNG yang diperbolehkan',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
// Clear the input
|
|
input.value = '';
|
|
return;
|
|
}
|
|
|
|
// Validasi ukuran file (max 20MB)
|
|
if (file.size > 20 * 1024 * 1024) {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Ukuran File Terlalu Besar',
|
|
text: 'Ukuran file maksimal 20MB',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Assign ke file input yang sesuai
|
|
const targetInput = document.getElementById(inputId);
|
|
const dataTransfer = new DataTransfer();
|
|
dataTransfer.items.add(file);
|
|
targetInput.files = dataTransfer.files;
|
|
|
|
// Preview dengan tombol batal
|
|
const url = URL.createObjectURL(file);
|
|
const preview = document.getElementById(previewId);
|
|
preview.innerHTML = `
|
|
<div class="photo-preview-container">
|
|
<img src="${url}" style="max-width: 200px; max-height: 150px; border-radius: 8px; border: 3px solid #059669; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
<div class="mt-2">
|
|
<small class="text-success"><i class="fas fa-check"></i> Foto berhasil diupload</small>
|
|
<br>
|
|
<small class="text-muted">Ukuran: ${(file.size / 1024).toFixed(1)} KB</small>
|
|
</div>
|
|
<div class="mt-2">
|
|
<button type="button" class="btn btn-danger btn-sm" onclick="cancelPhoto('${inputId}', '${previewId}')">
|
|
<i class="fas fa-times"></i> Batal
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Clear the original input
|
|
input.value = '';
|
|
|
|
// Tampilkan notifikasi foto berhasil diupload
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Foto Berhasil Dipilih',
|
|
text: `File ${file.name} berhasil dipilih (${(file.size / 1024).toFixed(1)} KB)`,
|
|
timer: 3000,
|
|
showConfirmButton: false,
|
|
toast: true,
|
|
position: 'top-end'
|
|
});
|
|
}
|
|
|
|
// Stop all cameras when page is unloaded
|
|
window.addEventListener('beforeunload', function() {
|
|
Object.values(streams).forEach(stream => {
|
|
if (stream && stream.active) {
|
|
stream.getTracks().forEach(track => {
|
|
track.stop();
|
|
console.log('Track stopped on page unload:', track.kind);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// Stop all cameras when page is hidden (mobile browsers)
|
|
document.addEventListener('visibilitychange', function() {
|
|
if (document.hidden) {
|
|
Object.values(streams).forEach(stream => {
|
|
if (stream && stream.active) {
|
|
stream.getTracks().forEach(track => {
|
|
track.stop();
|
|
console.log('Track stopped on page hidden:', track.kind);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Form validation
|
|
document.getElementById('precheckForm').addEventListener('submit', function(e) {
|
|
// Check if this is edit mode (precheck exists)
|
|
const isEditMode = {{ isset($precheck) ? 'true' : 'false' }};
|
|
|
|
console.log('Form submission started, edit mode:', isEditMode);
|
|
|
|
// Required fields - front_image only required for create mode
|
|
const requiredFields = ['kilometer', 'pressure_high'];
|
|
if (!isEditMode) {
|
|
requiredFields.push('front_image');
|
|
}
|
|
|
|
let isValid = true;
|
|
let errorMessages = [];
|
|
|
|
requiredFields.forEach(fieldId => {
|
|
const field = document.getElementById(fieldId);
|
|
|
|
if (!field) {
|
|
console.warn('Field not found:', fieldId);
|
|
return;
|
|
}
|
|
|
|
if (field.type === 'file') {
|
|
// Validasi file input
|
|
if (!field.files || field.files.length === 0) {
|
|
field.classList.add('is-invalid');
|
|
isValid = false;
|
|
errorMessages.push('Foto depan kendaraan wajib diisi');
|
|
} else {
|
|
field.classList.remove('is-invalid');
|
|
}
|
|
} else {
|
|
// Validasi input biasa
|
|
if (!field.value.trim()) {
|
|
field.classList.add('is-invalid');
|
|
isValid = false;
|
|
if (fieldId === 'kilometer') {
|
|
errorMessages.push('Kilometer wajib diisi');
|
|
} else if (fieldId === 'pressure_high') {
|
|
errorMessages.push('Pressure High wajib diisi');
|
|
}
|
|
} else {
|
|
field.classList.remove('is-invalid');
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log('Validation result:', { isValid, errorMessages });
|
|
|
|
if (!isValid) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
// Use setTimeout to ensure SweetAlert is loaded
|
|
setTimeout(() => {
|
|
if (typeof Swal !== 'undefined') {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Validasi Gagal',
|
|
html: errorMessages.join('<br>'),
|
|
confirmButtonText: 'OK'
|
|
});
|
|
} else {
|
|
alert('Validasi Gagal:\n' + errorMessages.join('\n'));
|
|
}
|
|
}, 100);
|
|
} else {
|
|
// Stop all cameras before submitting
|
|
Object.keys(streams).forEach(videoId => {
|
|
stopCamera(videoId);
|
|
});
|
|
|
|
// Show loading state
|
|
const submitButton = document.querySelector('button[type="submit"]');
|
|
if (submitButton) {
|
|
submitButton.disabled = true;
|
|
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> ' + (isEditMode ? 'Memperbarui...' : 'Menyimpan...');
|
|
}
|
|
}
|
|
});
|
|
|
|
// Remove error styling when user starts typing
|
|
document.addEventListener('input', function(e) {
|
|
if (e.target.classList.contains('is-invalid')) {
|
|
e.target.classList.remove('is-invalid');
|
|
}
|
|
});
|
|
|
|
// Remove error styling when user selects a file
|
|
document.addEventListener('change', function(e) {
|
|
if (e.target.type === 'file' && e.target.classList.contains('is-invalid')) {
|
|
e.target.classList.remove('is-invalid');
|
|
}
|
|
});
|
|
|
|
// Debug: Check if form and button exist
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const form = document.getElementById('precheckForm');
|
|
const submitButton = document.getElementById('submitButton');
|
|
|
|
console.log('Form found:', !!form);
|
|
console.log('Submit button found:', !!submitButton);
|
|
|
|
if (submitButton) {
|
|
console.log('Submit button classes:', submitButton.className);
|
|
console.log('Submit button disabled:', submitButton.disabled);
|
|
|
|
// Add click event listener for debugging
|
|
submitButton.addEventListener('click', function(e) {
|
|
console.log('Submit button clicked');
|
|
console.log('Event:', e);
|
|
});
|
|
}
|
|
|
|
// Check for any overlapping elements
|
|
if (submitButton) {
|
|
setTimeout(() => {
|
|
const rect = submitButton.getBoundingClientRect();
|
|
const elementAtPoint = document.elementFromPoint(rect.left + rect.width/2, rect.top + rect.height/2);
|
|
console.log('Element at button center:', elementAtPoint);
|
|
console.log('Is button clickable?', elementAtPoint === submitButton);
|
|
console.log('Button rect:', rect);
|
|
|
|
// Check if button is visible
|
|
const computedStyle = window.getComputedStyle(submitButton);
|
|
console.log('Button visibility:', computedStyle.visibility);
|
|
console.log('Button display:', computedStyle.display);
|
|
console.log('Button pointer-events:', computedStyle.pointerEvents);
|
|
console.log('Button z-index:', computedStyle.zIndex);
|
|
}, 100);
|
|
}
|
|
});
|
|
</script>
|
|
@endsection |