MIF_E31230069/spk_mobile/lib/models/laundry.dart

372 lines
11 KiB
Dart

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 int waktuProsesHarian;
final int waktuProsesJam;
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<GaleriLaundry> 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,
required this.waktuProsesHarian,
required this.waktuProsesJam,
this.deskripsi,
this.fotoUtama,
this.foto,
this.fotoUrl,
this.galeri = const [],
this.avgRating,
this.totalReviews,
required this.status,
});
factory Laundry.fromJson(Map<String, dynamic> 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;
int waktuProsesHarian = 0;
int waktuProsesJam = 0;
if (json['layanan'] != null &&
(json['layanan'] as List).isNotEmpty) {
for (final item in json['layanan'] as List) {
final jenis = item['jenis_layanan']?.toString().toLowerCase() ?? '';
final estimasi = int.tryParse(item['estimasi_selesai']?.toString() ?? '0') ?? 0;
final waktu = int.tryParse(item['waktu_proses']?.toString() ?? '0') ?? 0;
final actualWaktu = waktu > 0 ? waktu : (estimasi > 0 ? estimasi : 24);
if (jenis == 'harian' || jenis == 'kiloan' || jenis == 'reguler') {
waktuProsesHarian = actualWaktu;
} else if (jenis == 'jam' || jenis == 'express' || jenis == 'kilat' || jenis == 'satuan') {
waktuProsesJam = actualWaktu;
}
}
}
if (waktuProsesHarian == 0) waktuProsesHarian = json['waktu_proses'] ?? 24;
if (waktuProsesJam == 0) waktuProsesJam = json['waktu_proses'] ?? 24;
// Get foto from API and build gallery list
final List<GaleriLaundry> 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,
waktuProsesHarian: waktuProsesHarian,
waktuProsesJam: waktuProsesJam,
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)}';
}
}
// Format waktu proses sesuai jenis layanan (harian / jam)
int waktuProsesFor(String? jenisLayanan) {
if (jenisLayanan == null || jenisLayanan.isEmpty) {
return waktuProsesHarian > 0 ? waktuProsesHarian : waktuProses;
}
final target = jenisLayanan.toLowerCase();
if (target == 'jam' || target == 'express' || target == 'kilat' || target == 'satuan') {
return waktuProsesJam > 0 ? waktuProsesJam : waktuProses;
} else {
return waktuProsesHarian > 0 ? waktuProsesHarian : waktuProses;
}
}
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;
// Format jam secara umum menjadi format ramah pengguna (contoh: "3 hari", "10 jam")
static String formatJam(int jam) {
if (jam >= 24) {
final hari = jam ~/ 24;
final sisaJam = jam % 24;
if (sisaJam == 0) {
return '$hari hari';
} else {
return '$hari hari $sisaJam jam';
}
}
return '$jam jam';
}
// Format waktu proses sesuai jenis layanan
String formattedWaktuProsesFor(String? jenisLayanan) {
return formatJam(waktuProsesFor(jenisLayanan));
}
String get estimasiSelesai => formatJam(waktuProses);
// 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<String, dynamic> 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';
}
}