import '../config/app_config.dart'; class Laundry { final int id; final String nama; final String alamat; final String? noWhatsapp; final double? latitude; final double? longitude; final double jarakKampus; // jarak ke kampus dalam km final String jamBuka; final String jamTutup; final double hargaPerKg; final double hargaKiloan; final double hargaHarian; final double hargaJam; final int waktuProses; // dalam jam final String? deskripsi; final String? fotoUtama; final String? foto; // Field foto dari API final String? fotoUrl; // Full absolute URL dari API (foto_url) final List galeri; final double? avgRating; final int? totalReviews; final String status; Laundry({ required this.id, required this.nama, required this.alamat, this.noWhatsapp, this.latitude, this.longitude, required this.jarakKampus, required this.jamBuka, required this.jamTutup, required this.hargaPerKg, required this.hargaKiloan, required this.hargaHarian, required this.hargaJam, required this.waktuProses, this.deskripsi, this.fotoUtama, this.foto, this.fotoUrl, this.galeri = const [], this.avgRating, this.totalReviews, required this.status, }); factory Laundry.fromJson(Map json) { // Try to get jarak from different possible field names double jarak = 0; if (json['jarak_kampus'] != null) { jarak = double.tryParse(json['jarak_kampus'].toString()) ?? 0; } else if (json['jarak'] != null) { // Backend uses 'jarak' column, convert meters to km jarak = (double.tryParse(json['jarak'].toString()) ?? 0) / 1000; } // Get harga from layanan if harga_per_kg is not set directly double harga = 0; double hargaKiloan = 0; if (json['harga_per_kg'] != null) { harga = double.tryParse(json['harga_per_kg'].toString()) ?? 0; } if (json['harga_kiloan'] != null) { hargaKiloan = double.tryParse(json['harga_kiloan'].toString()) ?? 0; } double hargaHarian = 0; double hargaJam = 0; if (json['layanan'] != null && (json['layanan'] as List).isNotEmpty) { final layananList = json['layanan'] as List; harga = harga > 0 ? harga : double.tryParse(layananList.first['harga']?.toString() ?? '0') ?? 0; for (final item in layananList) { final jenis = item['jenis_layanan']?.toString().toLowerCase() ?? ''; final itemHarga = double.tryParse(item['harga']?.toString() ?? '0') ?? 0; if (jenis == 'harian' || jenis == 'kiloan' || jenis == 'reguler') { hargaHarian = itemHarga; hargaKiloan = itemHarga; } else if (jenis == 'jam' || jenis == 'express' || jenis == 'kilat' || jenis == 'satuan') { hargaJam = itemHarga; } } } // Fallback if not populated if (hargaHarian == 0) hargaHarian = hargaKiloan > 0 ? hargaKiloan : harga; if (hargaJam == 0) hargaJam = harga; // Get foto from API and build gallery list final List galleryList = []; // Parse galeri array if exists if (json['galeri'] != null && (json['galeri'] as List).isNotEmpty) { galleryList.addAll( (json['galeri'] as List).map((g) => GaleriLaundry.fromJson(g)).toList(), ); } // If gallery is empty but foto field exists, create a galeri item from it if (galleryList.isEmpty && json['foto'] != null && json['foto'].toString().isNotEmpty) { final resolved = json['foto_url'] != null && json['foto_url'].toString().isNotEmpty ? json['foto_url'].toString() : (json['foto'].toString().startsWith('http') ? json['foto'].toString() : '${AppConfig.serverUrl}/uploads/Laundry/${json['foto']}'); galleryList.add( GaleriLaundry( id: json['id'] ?? 0, foto: resolved, isPrimary: true, urutan: 0, ), ); } return Laundry( id: json['id'] ?? 0, nama: json['nama'] ?? '', alamat: json['alamat'] ?? '', noWhatsapp: json['no_whatsapp'], latitude: json['latitude'] != null ? double.tryParse(json['latitude'].toString()) : null, longitude: json['longitude'] != null ? double.tryParse(json['longitude'].toString()) : null, jarakKampus: jarak, jamBuka: _parseTime(json['jam_buka'], '08:00'), jamTutup: _parseTime(json['jam_tutup'], '20:00'), hargaPerKg: harga, hargaKiloan: hargaKiloan, hargaHarian: hargaHarian, hargaJam: hargaJam, waktuProses: json['waktu_proses'] ?? 24, deskripsi: json['deskripsi'], fotoUtama: json['foto_utama'], foto: json['foto'], // Store raw foto field fotoUrl: json['foto_url'], // Full URL dari API galeri: galleryList, avgRating: json['avg_rating'] != null ? double.tryParse(json['avg_rating'].toString()) : null, totalReviews: json['total_reviews'], status: json['status'] ?? 'buka', ); } // Parse time string, stripping seconds if present (e.g. "08:00:00" -> "08:00") static String _parseTime(dynamic value, String fallback) { if (value == null) return fallback; final str = value.toString(); if (str.length >= 5) return str.substring(0, 5); return fallback; } bool get hasPhoto { bool isValidUrl(String? url) { if (url == null || url.isEmpty) return false; final lower = url.toLowerCase(); if (lower.contains('via.placeholder.com')) return false; return true; } if (isValidUrl(foto)) return true; if (galeri.any((g) => isValidUrl(g.foto))) return true; return false; } // Get primary photo URL // Priority: foto_url (full URL from API) > foto field > galeri String get primaryPhoto { // 1. Gunakan foto_url dari API jika sudah full URL (paling akurat) if (fotoUrl != null && fotoUrl!.isNotEmpty) { return fotoUrl!; } // 2. Construct URL dari nama file foto if (foto != null && foto!.isNotEmpty) { if (foto!.startsWith('http')) { return foto!; } return '${AppConfig.serverUrl}/uploads/Laundry/$foto'; } // 3. Fall back to galeri items if foto is not set if (galeri.isNotEmpty) { final primary = galeri.firstWhere( (g) => g.isPrimary, orElse: () => galeri.first, ); if (primary.foto.isNotEmpty && primary.foto.startsWith('http')) { return primary.foto; } if (primary.foto.isNotEmpty) { return primary.photoUrl; } } return ''; } // Format harga dengan Rupiah String get formattedHarga { return 'Rp ${_formatRupiah(hargaHarian > 0 ? hargaHarian : hargaPerKg)}'; } // Format harga sesuai jenis layanan (harian / jam) String formattedHargaFor(String? jenisLayanan) { if (jenisLayanan == null || jenisLayanan.isEmpty) { return formattedHarga; } final target = jenisLayanan.toLowerCase(); if (target == 'jam' || target == 'express' || target == 'kilat' || target == 'satuan') { final val = hargaJam > 0 ? hargaJam : hargaPerKg; return 'Rp ${_formatRupiah(val)}'; } else { final val = hargaHarian > 0 ? hargaHarian : hargaPerKg; return 'Rp ${_formatRupiah(val)}'; } } String _formatRupiah(double value) { return value .toStringAsFixed(0) .replaceAllMapped( RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]}.', ); } String get formattedHargaKiloan { final value = hargaKiloan > 0 ? hargaKiloan : hargaPerKg; return 'Rp ${_formatRupiah(value)}'; } double get jarak => jarakKampus; // alias for jarak field double get rating => avgRating ?? 0.0; String get estimasiSelesai => '$waktuProses jam'; // Check if currently open bool get isOpen { if (status != 'buka') return false; final now = DateTime.now(); final currentTime = '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}'; return currentTime.compareTo(jamBuka) >= 0 && currentTime.compareTo(jamTutup) <= 0; } // Get status text String get statusText { if (status == 'tutup') return 'Tutup'; if (isOpen) return 'Buka'; return 'Tutup Sementara'; } } class GaleriLaundry { final int id; final String foto; final bool isPrimary; final int urutan; GaleriLaundry({ required this.id, required this.foto, required this.isPrimary, required this.urutan, }); factory GaleriLaundry.fromJson(Map json) { return GaleriLaundry( id: json['id'] ?? 0, foto: json['foto'] ?? '', isPrimary: json['is_primary'] == 1 || json['is_primary'] == true, urutan: json['urutan'] ?? 0, ); } // Get full photo URL String get photoUrl { if (foto.isEmpty) return ''; if (foto.startsWith('http')) { return foto; } if (foto.startsWith('uploads/')) { return '${AppConfig.serverUrl}/$foto'; } return '${AppConfig.serverUrl}/uploads/galeri/laundry/$foto'; } }