174 lines
5.1 KiB
Dart
174 lines
5.1 KiB
Dart
|
|
// ── Activity ──────────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
class BookingActivity {
|
||
|
|
final int id;
|
||
|
|
final String name;
|
||
|
|
|
||
|
|
const BookingActivity({required this.id, required this.name});
|
||
|
|
|
||
|
|
factory BookingActivity.fromJson(Map<String, dynamic> 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<BookingCell> cells; // one per court
|
||
|
|
|
||
|
|
const BookingRow({
|
||
|
|
required this.slotIndex,
|
||
|
|
required this.timeLabel,
|
||
|
|
required this.cells,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
class BookingGrid {
|
||
|
|
final List<BookingRow> rows;
|
||
|
|
final List<String> 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<String, dynamic> json) {
|
||
|
|
final aoColumns = json['aoColumns'] as List<dynamic>? ?? [];
|
||
|
|
final aaData = json['aaData'] as List<dynamic>? ?? [];
|
||
|
|
|
||
|
|
final courtTitles = aoColumns
|
||
|
|
.skip(1)
|
||
|
|
.map((c) => (c as Map<String, dynamic>)['sTitle'] as String? ?? '')
|
||
|
|
.toList();
|
||
|
|
|
||
|
|
final rows = aaData.map((rowData) {
|
||
|
|
final row = rowData as List<dynamic>;
|
||
|
|
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<String, dynamic>? ?? {};
|
||
|
|
|
||
|
|
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<String, dynamic> 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<TicketLine> lines;
|
||
|
|
|
||
|
|
const TicketPreview({
|
||
|
|
required this.concepto,
|
||
|
|
required this.base,
|
||
|
|
required this.iva,
|
||
|
|
required this.total,
|
||
|
|
required this.lines,
|
||
|
|
});
|
||
|
|
|
||
|
|
factory TicketPreview.fromJson(Map<String, dynamic> 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<dynamic>? ?? [])
|
||
|
|
.map((l) => TicketLine.fromJson(l as Map<String, dynamic>))
|
||
|
|
.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<String, dynamic> json) {
|
||
|
|
final book = json['book'] as Map<String, dynamic>? ?? {};
|
||
|
|
final ticketJson = json['ticket'] as Map<String, dynamic>?;
|
||
|
|
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,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|