class Booking { final int id; final int userId; final int kontrakanId; final DateTime tanggalMulai; final DateTime tanggalSelesai; final double totalBiaya; final String status; final String? catatan; final dynamic kontrakan; // Pembeda pengajuan survei dan sewa. final String jenisPengajuan; final DateTime? tanggalSurvei; final String? jamSurvei; final DateTime? surveyFollowUpExpiresAt; // Pembayaran hanya digunakan pada pengajuan sewa. final String paymentStatus; final String? paymentProof; Booking({ required this.id, required this.userId, required this.kontrakanId, required this.tanggalMulai, required this.tanggalSelesai, required this.totalBiaya, required this.status, this.catatan, this.kontrakan, this.jenisPengajuan = 'sewa', this.tanggalSurvei, this.jamSurvei, this.surveyFollowUpExpiresAt, this.paymentStatus = 'unpaid', this.paymentProof, }); static DateTime? _parseDate(dynamic value) { if (value == null) return null; return DateTime.tryParse(value.toString()); } factory Booking.fromJson(Map json) { final startDate = json['start_date'] ?? json['tanggal_mulai'] ?? json['tanggal_survei']; final endDate = json['end_date'] ?? json['tanggal_selesai'] ?? json['tanggal_survei']; final amount = json['amount'] ?? json['total_biaya']; final notes = json['notes'] ?? json['catatan']; final jenis = (json['jenis_pengajuan'] ?? 'sewa').toString().toLowerCase(); final parsedSurveyDate = _parseDate(json['tanggal_survei']); final parsedStartDate = _parseDate(startDate) ?? parsedSurveyDate ?? DateTime.now(); final parsedEndDate = _parseDate(endDate) ?? parsedStartDate; return Booking( id: int.tryParse(json['id']?.toString() ?? '0') ?? 0, userId: int.tryParse(json['user_id']?.toString() ?? '0') ?? 0, kontrakanId: int.tryParse(json['kontrakan_id']?.toString() ?? '0') ?? 0, tanggalMulai: parsedStartDate, tanggalSelesai: parsedEndDate, totalBiaya: double.tryParse(amount?.toString() ?? '0') ?? 0, status: json['status']?.toString() ?? 'pending', catatan: notes?.toString(), kontrakan: json['kontrakan'], jenisPengajuan: jenis, tanggalSurvei: parsedSurveyDate, jamSurvei: json['jam_survei']?.toString(), surveyFollowUpExpiresAt: _parseDate( json['survey_follow_up_expires_at'], ), paymentStatus: json['payment_status']?.toString() ?? 'unpaid', paymentProof: json['payment_proof']?.toString(), ); } bool get isSurvei => jenisPengajuan == 'survei'; bool get isSewa => !isSurvei; bool get isSurveyFollowUpActive => isSurvei && status.toLowerCase() == 'confirmed'; String get jenisLabel { return isSurvei ? 'Pengajuan Survei' : 'Pengajuan Sewa'; } bool get canUploadPaymentProof { return isSewa && status.toLowerCase() == 'confirmed' && paymentStatus == 'unpaid'; } bool get isWaitingPaymentVerification { return isSewa && paymentStatus == 'verification'; } String get formattedTotalBiaya { if (isSurvei) return '-'; final nominal = totalBiaya.toStringAsFixed(0).replaceAllMapped( RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match match) => '${match[1]}.', ); return 'Rp $nominal'; } int get durasiBulan { if (isSurvei) return 0; final totalBulan = (tanggalSelesai.year - tanggalMulai.year) * 12 + tanggalSelesai.month - tanggalMulai.month; return totalBulan.clamp(1, 99).toInt(); } String get statusColor { switch (status.toLowerCase()) { case 'confirmed': return 'blue'; case 'checked_in': case 'active': return 'green'; case 'completed': case 'expired': return 'gray'; case 'cancelled': return 'red'; default: return 'orange'; } } String get statusLabel { if (isSurvei) { switch (status.toLowerCase()) { case 'pending': return 'Menunggu Konfirmasi Survei'; case 'confirmed': return 'Survei Disetujui'; case 'completed': return 'Survei Selesai'; case 'cancelled': return 'Tidak Jadi Sewa'; case 'expired': return 'Masa Tindak Lanjut Berakhir'; default: return status; } } switch (status.toLowerCase()) { case 'pending': return 'Menunggu Persetujuan Sewa'; case 'confirmed': switch (paymentStatus.toLowerCase()) { case 'paid': return 'Sewa Disetujui'; case 'verification': return 'Menunggu Verifikasi Pembayaran'; default: return 'Menunggu Pembayaran'; } case 'checked_in': case 'active': return 'Sedang Ditempati'; case 'completed': return 'Sewa Selesai'; case 'cancelled': return 'Pengajuan Sewa Dibatalkan'; default: return status; } } String get paymentStatusLabel { if (isSurvei) return '-'; switch (paymentStatus.toLowerCase()) { case 'paid': return 'Lunas'; case 'verification': return 'Menunggu Verifikasi'; case 'refunded': return 'Dikembalikan'; default: return 'Belum Bayar'; } } }