diff --git a/app/Http/Controllers/Transactions/PrechecksController.php b/app/Http/Controllers/Transactions/PrechecksController.php index 21d121e..69a3dc6 100644 --- a/app/Http/Controllers/Transactions/PrechecksController.php +++ b/app/Http/Controllers/Transactions/PrechecksController.php @@ -6,6 +6,8 @@ use App\Http\Controllers\Controller; use Illuminate\Http\Request; use App\Models\Precheck; use App\Models\Transaction; +use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\Log; class PrechecksController extends Controller { @@ -32,41 +34,152 @@ class PrechecksController extends Controller 'pressure_high' => 'required|numeric|min:0', 'pressure_low' => 'nullable|numeric|min:0', 'cabin_temperature' => 'nullable|numeric', - 'cabin_temperature_image' => 'nullable|string', + 'cabin_temperature_image' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', 'ac_condition' => 'nullable|in:' . implode(',', Precheck::getAcConditionOptions()), - 'ac_image' => 'nullable|string', + 'ac_image' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', 'blower_condition' => 'nullable|in:' . implode(',', Precheck::getBlowerConditionOptions()), - 'blower_image' => 'nullable|string', + 'blower_image' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', 'evaporator_condition' => 'nullable|in:' . implode(',', Precheck::getEvaporatorConditionOptions()), - 'evaporator_image' => 'nullable|string', + 'evaporator_image' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', 'compressor_condition' => 'nullable|in:' . implode(',', Precheck::getCompressorConditionOptions()), 'precheck_notes' => 'nullable|string', - 'front_image' => 'required|string', + 'front_image' => 'required|image|mimes:jpeg,png,jpg|max:2048', ]); - // Pastikan transaction_id sama dengan $transaction->id - $precheck = Precheck::create([ + $data = [ 'transaction_id' => $transaction->id, 'precheck_by' => auth()->id(), 'precheck_at' => now(), 'police_number' => $transaction->police_number, 'spk_number' => $transaction->spk, - 'front_image' => $request->front_image, 'kilometer' => $request->kilometer, 'pressure_high' => $request->pressure_high, 'pressure_low' => $request->pressure_low, 'cabin_temperature' => $request->cabin_temperature, - 'cabin_temperature_image' => $request->cabin_temperature_image, 'ac_condition' => $request->ac_condition, - 'ac_image' => $request->ac_image, 'blower_condition' => $request->blower_condition, - 'blower_image' => $request->blower_image, 'evaporator_condition' => $request->evaporator_condition, - 'evaporator_image' => $request->evaporator_image, 'compressor_condition' => $request->compressor_condition, 'precheck_notes' => $request->precheck_notes, - ]); + ]; - return redirect()->route('transaction')->with('success', 'Precheck berhasil disimpan'); + // Handle file uploads + $imageFields = [ + 'front_image', 'cabin_temperature_image', 'ac_image', + 'blower_image', 'evaporator_image' + ]; + + foreach ($imageFields as $field) { + if ($request->hasFile($field) && $request->file($field)->isValid()) { + try { + $file = $request->file($field); + + // Generate unique filename + $filename = time() . '_' . uniqid() . '_' . $field . '.' . $file->getClientOriginalExtension(); + + // Create directory path + $directory = 'prechecks/' . date('Y/m'); + + // Ensure base storage directory exists + $this->ensureStorageDirectoryExists(); + + // Ensure prechecks directory exists + if (!Storage::disk('public')->exists('prechecks')) { + Storage::disk('public')->makeDirectory('prechecks', 0755, true); + Log::info('Created prechecks directory'); + } + + // Ensure year directory exists + $yearDir = 'prechecks/' . date('Y'); + if (!Storage::disk('public')->exists($yearDir)) { + Storage::disk('public')->makeDirectory($yearDir, 0755, true); + Log::info('Created year directory: ' . $yearDir); + } + + // Ensure month directory exists + if (!Storage::disk('public')->exists($directory)) { + Storage::disk('public')->makeDirectory($directory, 0755, true); + Log::info('Created month directory: ' . $directory); + } + + // Store file in organized directory structure + $path = $file->storeAs($directory, $filename, 'public'); + + // Store file path + $data[$field] = $path; + + // Store metadata + $data[$field . '_metadata'] = [ + 'original_name' => $file->getClientOriginalName(), + 'size' => $file->getSize(), + 'mime_type' => $file->getMimeType(), + 'uploaded_at' => now()->toISOString(), + ]; + + Log::info('File uploaded successfully: ' . $path); + + } catch (\Exception $e) { + // Log error for debugging + Log::error('File upload failed: ' . $e->getMessage(), [ + 'field' => $field, + 'file' => $file->getClientOriginalName(), + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return back()->withErrors(['error' => 'Gagal mengupload file: ' . $field . '. Error: ' . $e->getMessage()]); + } + } + } + + try { + Precheck::create($data); + return redirect()->route('transaction')->with('success', 'Precheck berhasil disimpan'); + } catch (\Exception $e) { + Log::error('Precheck creation failed: ' . $e->getMessage()); + return back()->withErrors(['error' => 'Gagal menyimpan data precheck. Silakan coba lagi.']); + } + } + + /** + * Ensure the base storage directory exists + */ + private function ensureStorageDirectoryExists() + { + $storagePath = storage_path('app/public'); + + if (!is_dir($storagePath)) { + if (!mkdir($storagePath, 0755, true)) { + Log::error('Failed to create storage directory: ' . $storagePath); + throw new \Exception('Cannot create storage directory: ' . $storagePath . '. Please run: php fix_permissions.php or manually create the directory.'); + } + Log::info('Created storage directory: ' . $storagePath); + } + + // Check if directory is writable + if (!is_writable($storagePath)) { + Log::error('Storage directory is not writable: ' . $storagePath); + throw new \Exception( + 'Storage directory is not writable: ' . $storagePath . '. ' . + 'Please run one of these commands from your project root: ' . + '1) php fix_permissions.php ' . + '2) chmod -R 775 storage/ ' . + '3) mkdir -p storage/app/public/prechecks/' . date('Y/m') + ); + } + + // Check if we can create subdirectories + $testDir = $storagePath . '/test_' . time(); + if (!mkdir($testDir, 0755, true)) { + Log::error('Cannot create subdirectories in storage: ' . $storagePath); + throw new \Exception( + 'Cannot create subdirectories in storage. ' . + 'Please check permissions and run: php fix_permissions.php' + ); + } + + // Clean up test directory + rmdir($testDir); + Log::info('Storage directory is properly configured: ' . $storagePath); } } diff --git a/app/Models/Precheck.php b/app/Models/Precheck.php index 104b63c..598ea04 100644 --- a/app/Models/Precheck.php +++ b/app/Models/Precheck.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Storage; class Precheck extends Model { @@ -16,17 +17,22 @@ class Precheck extends Model 'police_number', 'spk_number', 'front_image', + 'front_image_metadata', 'kilometer', 'pressure_high', 'pressure_low', 'cabin_temperature', 'cabin_temperature_image', + 'cabin_temperature_image_metadata', 'ac_condition', 'ac_image', + 'ac_image_metadata', 'blower_condition', 'blower_image', + 'blower_image_metadata', 'evaporator_condition', 'evaporator_image', + 'evaporator_image_metadata', 'compressor_condition', 'precheck_notes' ]; @@ -37,6 +43,11 @@ class Precheck extends Model 'pressure_high' => 'decimal:2', 'pressure_low' => 'decimal:2', 'cabin_temperature' => 'decimal:2', + 'front_image_metadata' => 'array', + 'cabin_temperature_image_metadata' => 'array', + 'ac_image_metadata' => 'array', + 'blower_image_metadata' => 'array', + 'evaporator_image_metadata' => 'array', ]; /** @@ -59,6 +70,67 @@ class Precheck extends Model return $this->belongsTo(User::class, 'precheck_by'); } + /** + * Get front image URL + */ + public function getFrontImageUrlAttribute() + { + return $this->front_image ? Storage::disk('public')->url($this->front_image) : null; + } + + /** + * Get cabin temperature image URL + */ + public function getCabinTemperatureImageUrlAttribute() + { + return $this->cabin_temperature_image ? Storage::disk('public')->url($this->cabin_temperature_image) : null; + } + + /** + * Get AC image URL + */ + public function getAcImageUrlAttribute() + { + return $this->ac_image ? Storage::disk('public')->url($this->ac_image) : null; + } + + /** + * Get blower image URL + */ + public function getBlowerImageUrlAttribute() + { + return $this->blower_image ? Storage::disk('public')->url($this->blower_image) : null; + } + + /** + * Get evaporator image URL + */ + public function getEvaporatorImageUrlAttribute() + { + return $this->evaporator_image ? Storage::disk('public')->url($this->evaporator_image) : null; + } + + /** + * Delete associated files when model is deleted + */ + protected static function boot() + { + parent::boot(); + + static::deleting(function ($precheck) { + $imageFields = [ + 'front_image', 'cabin_temperature_image', 'ac_image', + 'blower_image', 'evaporator_image' + ]; + + foreach ($imageFields as $field) { + if ($precheck->$field && Storage::disk('public')->exists($precheck->$field)) { + Storage::disk('public')->delete($precheck->$field); + } + } + }); + } + /** * Get the AC condition options * diff --git a/database/migrations/2025_07_10_140352_create_prechecks_table.php b/database/migrations/2025_07_10_140352_create_prechecks_table.php index 87a54dd..635df64 100644 --- a/database/migrations/2025_07_10_140352_create_prechecks_table.php +++ b/database/migrations/2025_07_10_140352_create_prechecks_table.php @@ -20,18 +20,23 @@ class CreatePrechecksTable extends Migration $table->timestamp('precheck_at')->nullable(); $table->string('police_number'); $table->string('spk_number'); - $table->string('front_image'); + $table->string('front_image', 255)->nullable(); + $table->json('front_image_metadata')->nullable(); $table->decimal('kilometer', 10, 2); $table->decimal('pressure_high', 10, 2); $table->decimal('pressure_low', 10, 2)->nullable(); $table->decimal('cabin_temperature', 10, 2)->nullable(); - $table->string('cabin_temperature_image')->nullable(); + $table->string('cabin_temperature_image', 255)->nullable(); + $table->json('cabin_temperature_image_metadata')->nullable(); $table->enum('ac_condition', ['kotor', 'rusak', 'baik', 'tidak ada'])->nullable(); - $table->string('ac_image')->nullable(); + $table->string('ac_image', 255)->nullable(); + $table->json('ac_image_metadata')->nullable(); $table->enum('blower_condition', ['kotor', 'rusak', 'baik', 'tidak ada'])->nullable(); - $table->string('blower_image')->nullable(); + $table->string('blower_image', 255)->nullable(); + $table->json('blower_image_metadata')->nullable(); $table->enum('evaporator_condition', ['kotor', 'berlendir', 'bocor', 'bersih'])->nullable(); - $table->string('evaporator_image')->nullable(); + $table->string('evaporator_image', 255)->nullable(); + $table->json('evaporator_image_metadata')->nullable(); $table->enum('compressor_condition', ['kotor', 'rusak', 'baik', 'tidak ada'])->nullable(); $table->text('precheck_notes')->nullable(); $table->timestamps(); diff --git a/resources/views/transaction/prechecks.blade.php b/resources/views/transaction/prechecks.blade.php index bbf0de7..6e1e910 100644 --- a/resources/views/transaction/prechecks.blade.php +++ b/resources/views/transaction/prechecks.blade.php @@ -151,6 +151,16 @@ 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 { @@ -275,7 +285,7 @@ Kembali -