// ── Activity ────────────────────────────────────────────────────────────────── class BookingActivity { final int id; final String name; const BookingActivity({required this.id, required this.name}); factory BookingActivity.fromJson(Map json) => BookingActivity( id: json['pk_i_id'] ?? 0, name: json['s_name'] ?? '', ); } // ── Grid ────────────────────────────────────────────────────────────────────── class BookingCell { final String raw; const BookingCell(this.raw); bool get isEmpty => raw.isEmpty; bool get isPaid => raw.isNotEmpty && raw.endsWith('+'); bool get isBooked => raw.isNotEmpty; int? get memberId { final m = RegExp(r'^(\d+)\.').firstMatch(raw); return m != null ? int.tryParse(m.group(1)!) : null; } int? get familyId { final m = RegExp(r'\.(\d+)[+\-]?$').firstMatch(raw); return m != null ? int.tryParse(m.group(1)!) : null; } } class BookingRow { final int slotIndex; final String timeLabel; final List cells; // one per court const BookingRow({ required this.slotIndex, required this.timeLabel, required this.cells, }); } class BookingGrid { final List rows; final List courtTitles; // ["01", "02", ...] final String activityName; final int light; final int extra; final String? celebrations; const BookingGrid({ required this.rows, required this.courtTitles, required this.activityName, required this.light, required this.extra, this.celebrations, }); factory BookingGrid.fromJson(Map json) { final aoColumns = json['aoColumns'] as List? ?? []; final aaData = json['aaData'] as List? ?? []; final courtTitles = aoColumns .skip(1) .map((c) => (c as Map)['sTitle'] as String? ?? '') .toList(); final rows = aaData.map((rowData) { final row = rowData as List; final firstCell = row[0] as String? ?? ''; final idMatch = RegExp(r'id="(\d+)"').firstMatch(firstCell); final slotIndex = idMatch != null ? int.parse(idMatch.group(1)!) : 0; final timeMatch = RegExp(r'>([^<]+)<').firstMatch(firstCell); final timeLabel = timeMatch?.group(1) ?? firstCell; final cells = row .skip(1) .map((c) => BookingCell(c as String? ?? '')) .toList(); return BookingRow(slotIndex: slotIndex, timeLabel: timeLabel, cells: cells); }).toList(); final ac = json['ac'] as Map? ?? {}; return BookingGrid( rows: rows, courtTitles: courtTitles, activityName: ac['s_name'] as String? ?? '', light: (json['light'] as num?)?.toInt() ?? 0, extra: (json['extra'] as num?)?.toInt() ?? 0, celebrations: json['celebrations'] as String?, ); } } // ── Booking detail (single cell) ────────────────────────────────────────────── class TicketLine { final String concepto; final double precio; const TicketLine({required this.concepto, required this.precio}); factory TicketLine.fromJson(Map json) => TicketLine( concepto: json['concepto'] ?? '', precio: (json['precio'] as num?)?.toDouble() ?? 0, ); } class TicketPreview { final String concepto; final double base; final double iva; final double total; final List lines; const TicketPreview({ required this.concepto, required this.base, required this.iva, required this.total, required this.lines, }); factory TicketPreview.fromJson(Map json) => TicketPreview( concepto: json['concepto'] ?? '', base: (json['base'] as num?)?.toDouble() ?? 0, iva: (json['iva'] as num?)?.toDouble() ?? 0, total: (json['total'] as num?)?.toDouble() ?? 0, lines: (json['lines'] as List? ?? []) .map((l) => TicketLine.fromJson(l as Map)) .toList(), ); } class BookingDetail { final int id; final int memberId; final int familyId; final String date; final bool paid; final TicketPreview? ticket; const BookingDetail({ required this.id, required this.memberId, required this.familyId, required this.date, required this.paid, this.ticket, }); factory BookingDetail.fromJson(Map json) { final book = json['book'] as Map? ?? {}; final ticketJson = json['ticket'] as Map?; return BookingDetail( id: book['pk_i_id'] ?? 0, memberId: book['fk_i_member_id'] ?? 0, familyId: book['fk_i_family'] ?? 1, date: book['d_date'] ?? '', paid: (book['b_paid'] ?? 0) == 1, ticket: ticketJson != null ? TicketPreview.fromJson(ticketJson) : null, ); } }