import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:jiffy/jiffy.dart'; import 'package:tp5/core/basic_page.dart'; import 'package:tp5/core/utils.dart'; import 'package:tp5/csv/data.dart'; import 'package:linked_scroll_controller/linked_scroll_controller.dart'; import 'package:tp5/fltinfo/view/dutyinfo_page.dart'; import 'package:tp5/fltinfo/view/fltinfo_page.dart'; import 'package:tp5/rosters/rosters_crew_filter.dart'; import 'package:tp5/widgets/my_col.dart'; import 'package:tp5/widgets/my_row.dart'; import 'package:google_fonts/google_fonts.dart'; class RostersPage extends ConsumerStatefulWidget { const RostersPage({super.key}); @override ConsumerState createState() => _RostersPageState(); } class _RostersPageState extends ConsumerState { final scrollControllerGroup = LinkedScrollControllerGroup(); Map scrollCtrls = {}; ScrollController? _getCtrl(int i) { if (!scrollCtrls.containsKey(i)) { scrollCtrls[i] = scrollControllerGroup.addAndGet(); } return scrollCtrls[i]; } final bottomnavstyle = ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), ), backgroundColor: const Color.fromARGB(255, 0, 36, 53) //elevated btton background color ); TextEditingController ctrldep = TextEditingController(); TextEditingController ctrldes = TextEditingController(); TextEditingController ctrlairline = TextEditingController(); TextEditingController ctrlfnum = TextEditingController(); Map flightFilter = { "dep": "", "des": "", "al": "", "fnum": "" }; void _searchFlights(xcontext) async { ctrldep.text = flightFilter["dep"] ?? ""; ctrldes.text = flightFilter["des"] ?? ""; ctrlairline.text = flightFilter["al"] ?? ""; ctrlfnum.text = flightFilter["fnum"] ?? ""; final res = await showModalBottomSheet( context: context, builder: (context) => _flightFilter(xcontext)); if (res is Map) { Hive.box("settings").put("rosters.filter.flights", (res)); await recalcultlcs(); setState(() { flightFilter = res; }); } } Widget _flightFilter(BuildContext context) { return Container( padding: const EdgeInsets.all(20), child: Column(mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Expanded( child: TextField( decoration: const InputDecoration(labelText: "Departure Airport"), maxLength: 3, // Set maximum length controller: ctrldep, ), ), const SizedBox(width: 10), Expanded( child: TextField( decoration: const InputDecoration(labelText: "Arrival Airport"), maxLength: 3, // Set maximum length controller: ctrldes, ), ), ], ), const SizedBox(height: 15), Row( children: [ Expanded( child: TextField( controller: ctrlairline, decoration: const InputDecoration(labelText: "Airline IATA code"), maxLength: 2, // Set maximum length ), ), const SizedBox(width: 10), Expanded( child: TextField( controller: ctrlfnum, decoration: const InputDecoration(labelText: "Flight number"), maxLength: 6, // Set maximum length ), ), ], ), const SizedBox(height: 15), const SizedBox(height: 35), SizedBox( width: 360, child: ElevatedButton( onPressed: () async => Navigator.pop(context, { "dep": ctrldep.text.toUpperCase(), "des": ctrldes.text.toUpperCase(), "al": ctrlairline.text.toUpperCase(), "fnum": ctrlfnum.text.toUpperCase() }), // Close the bottom sheet style: ElevatedButton.styleFrom( backgroundColor: Colors.green, // Set button color to green shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), // Add rounded corners ), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), ), child: const Text( 'Flight filter', style: TextStyle(fontSize: 18, color: Colors.white), ), ), ), ])); } Map> crewFilter = {"college": [], "ac": [], "base": []}; void _searchCrew(xcontext, List qualif) async { final res = await showModalBottomSheet( context: context, builder: (context) => RostersCrewFilter(qualif: qualif, crewFilter: crewFilter)); if (res is Map>) { Hive.box("settings").put("rosters.filter.crew", (res)); await recalcultlcs(); setState(() { crewFilter = res; }); } } final double rosterswidth = 110; final double rostersheight = 60; Widget _duty2line(Pnleg leg, {bool showdatedep = true}) { // print( // ">>${leg.label},${leg.type},${leg.dep},${leg.arr},${leg.jdep?.format(pattern: "HHmm")},${leg.jarr?.format(pattern: "HHmm")},"); final whdep = showdatedep ? Text( "${leg.jdep?.format(pattern: "HHmm")}", style: const TextStyle(fontSize: 10, color: Colors.teal), ) : const Row(children: [ Icon( Icons.arrow_back_ios_new_outlined, size: 12, color: Colors.teal, ), Icon( Icons.arrow_back_ios_new_outlined, size: 12, color: Colors.teal, ), ]); final wharr = Text( "${leg.jarr?.format(pattern: "HHmm")}", style: const TextStyle(fontSize: 10, color: Colors.teal), ); if (leg.type == "L" || (leg.type == "F")) { return InkWell( onTap: () { context.push("/fltinfo", extra: FltinfoParams( al: leg.al, fnum: leg.fnum, dep: leg.dep, des: leg.arr, jdep: leg.jdep, jdes: leg.jarr)); }, child: MyRow(children: [ whdep, const Gap(3), Text( "${leg.dep}-${leg.arr}", style: GoogleFonts.robotoMono( fontSize: 11, fontWeight: FontWeight.w300), ), const Gap(3), wharr, ]), ); } else if ((leg.type == "G") || ((leg.dep ?? "") != "" && (leg.arr ?? "") != "")) { return InkWell( onTap: () { context.push("/dutyinfo", extra: DutyinfoParams( dutytype: leg.dutytype, label: leg.label, dep: leg.dep, des: leg.arr, jdep: leg.jdep, jdes: leg.jarr)); }, child: MyRow(children: [ whdep, const Gap(3), const Text("Limo ", style: TextStyle(fontSize: 11, color: Colors.amber)), Text( "${leg.dep}-${leg.arr}", style: GoogleFonts.robotoMono( fontSize: 11, fontWeight: FontWeight.w300), ), const Gap(3), wharr, ]), ); } // else if ((leg.type == "F") || // ((leg.dep ?? "") != "" && (leg.arr ?? "") != "")) { // return MyRow(children: [ // whdep, // const Gap(3), // const Text("DH ", style: TextStyle(fontSize: 11, color: Colors.amber)), // Text( // "${leg.dep}-${leg.arr}", // style: // GoogleFonts.robotoMono(fontSize: 11, fontWeight: FontWeight.w300), // ), // const Gap(3), // wharr, // ]); // } else if ((!["OFF", "CM", "CA", "PP"].contains(leg.label)) && (leg.jarr != null && leg.jdep != null && DTInterval(leg.jdep!, leg.jarr!).duration.inHours < 18)) { return InkWell( onTap: () { context.push("/dutyinfo", extra: DutyinfoParams( dutytype: leg.dutytype, label: leg.label, dep: leg.dep, des: leg.arr, jdep: leg.jdep, jdes: leg.jarr)); }, child: MyRow(children: [ whdep, const Gap(3), Text("${leg.type == "S" ? "SIMU " : ""}${leg.label}", style: const TextStyle(fontSize: 11, color: Colors.amber)), Text( " ${leg.dep}", style: GoogleFonts.robotoMono( fontSize: 8, fontWeight: FontWeight.w300), ), const Gap(3), wharr, ]), ); } else { return Text( leg.label ?? "", style: TextStyle( fontSize: 16, color: Colors.blueGrey[800], fontWeight: FontWeight.w700, letterSpacing: 1), ); } } Widget _rostersDay( {required Jiffy date, required List legs, Color? color, bool border = false}) { return Container( decoration: BoxDecoration( border: border ? Border.all(color: Colors.white, width: 1) : null, color: color ?? Colors.grey[900]), width: rosterswidth, height: rosterswidth, child: Column(children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( date.format(pattern: "EEE"), style: const TextStyle( fontSize: 14, color: Colors.blueGrey, fontWeight: FontWeight.w700), ), Text( date.format(pattern: "dd MMM "), style: const TextStyle( fontSize: 14, color: Color.fromARGB(255, 151, 163, 55), fontWeight: FontWeight.w500), ) ], ), Expanded( child: MyCol( crossAxisAlignment: CrossAxisAlignment.start, children: legs .map((leg) => _duty2line(leg, showdatedep: date.yMEd == leg.jdep?.yMEd)) .toList())) ]), ); } bool showalldates = false; recalcultlcs() async { final data = ref.read(dataProvider); final pnlegTlcs = ref.read(pnlegMeta).tlcs; final pnleg = data.pnleg; final qualif = data.qualif; tlcs = await compute( calcultlcs, { "crewFilter": crewFilter, "flightFilter": flightFilter, "pnleg": pnleg, "pnlegTlcs": pnlegTlcs, "qualif": qualif }, debugLabel: "calcul tlcs"); setState(() {}); // print("rosterspages: recalcultlcs: $tlcs"); } // recalculdates() async { // final data = ref.watch(dataProvider); // final pnleg = data.pnleg; // dates = await compute(calculdates, pnleg, debugLabel: "calcul dates"); // setState(() {}); // print("rosterspages: recalculdates: $dates"); // } // static List calculdates(List pnleg) { // return pnleg // .fold( // {}, // (t, e) => { // ...t, // e.jdep?.startOf(Unit.day) ?? // Jiffy.parse(e.date ?? "01/01/1970", // pattern: "dd/MM/yyyy", isUtc: true) // }) // .sortedBy((e) => e.dateTime) // .where((e) => // e.isAfter(Jiffy.now().toUtc().startOf(Unit.day).subtract(days: 3))) // .toList(); // } static List calcultlcs(Map data) { final List qualif = data["qualif"]; final crewFilter = data["crewFilter"]; final flightFilter = data["flightFilter"]; final List pnleg = data["pnleg"]; final Set tlcsCrewfilter = qualif .where((e) => ((crewFilter["college"]!.isEmpty || crewFilter["college"]!.contains(e.college)) && (crewFilter["ac"]!.isEmpty || crewFilter["ac"]!.contains(e.ac)) && (crewFilter["base"]!.isEmpty || crewFilter["base"]!.contains(e.base)))) .map((e) => e.tlc ?? "") .toSet(); final List pnlegTlcs = data["pnlegTlcs"]; // final List pnlegTlcs = // pnleg.fold({}, (t, e) => {...t, e.tlc ?? ""}).sortedBy((e) { // final pn = qualif.firstWhereOrNull((q) => q.tlc == e); // return "${pn?.lname} ${pn?.fname}"; // }); final Set tlcsFlightfilter = pnlegTlcs .where((e) => ((flightFilter["al"] == "" || pnleg.firstWhereOrNull((k) => (k.tlc == e) && (k.al == flightFilter["al"])) != null) && (flightFilter["fnum"] == "" || pnleg.firstWhereOrNull((k) => (k.tlc == e) && (k.fnum == flightFilter["fnum"])) != null) && (flightFilter["dep"] == "" || pnleg.firstWhereOrNull((k) => (k.tlc == e) && (k.dep == flightFilter["dep"])) != null) && (flightFilter["des"] == "" || pnleg.firstWhereOrNull((k) => (k.tlc == e) && (k.arr == flightFilter["des"])) != null))) .toSet(); return tlcsCrewfilter.intersection(tlcsFlightfilter).sortedBy((e) => "${qualif.firstWhereOrNull((k) => k.tlc == e)?.lname} ${qualif.firstWhereOrNull((k) => k.tlc == e)?.fname}"); } List tlcs = []; // List dates = []; @override Widget build(BuildContext context) { final data = ref.watch(dataProvider); final pnleg = data.pnleg; final qualif = data.qualif; // final now = ref.watch(clockProvider); final now = Jiffy.now().toUtc(); final pnlegmeta = ref.watch(pnlegMeta); final dates = pnlegmeta.dates; final alltlcs = pnlegmeta.tlcs; // final dates = data.pnleg_dates; // final tlcs = data.pnleg_tlcs; return BasicPage( actions: [Text("showing ${tlcs.length} crew member"), const Gap(5)], bottomNavigationBar: Container( padding: const EdgeInsets.only(left: 8, right: 8, bottom: 2, top: 6), // color: Colors.black, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.grey[700]!, Colors.black, ], )), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Row( children: [ ElevatedButton.icon( onPressed: () { _searchCrew(context, qualif); }, icon: const Icon( Icons.calendar_month), //icon data for elevated button label: const Text("Filter\nCrew"), //label text style: bottomnavstyle, ), const Gap(10), Row( children: [ Column( mainAxisSize: MainAxisSize.min, children: crewFilter.keys .where((e) => crewFilter[e]!.isNotEmpty) .map((e) => Text( "${crewFilter[e]}", style: const TextStyle( fontSize: 12, color: Colors.yellow), )) .toList()), const Gap(10), if (crewFilter.values.flattened.isNotEmpty) Badge( label: Text( crewFilter.values.flattened.length.toString()), child: IconButton.outlined( onPressed: () { for (var k in crewFilter.keys) { crewFilter[k]!.clear(); } Hive.box("settings") .delete("rosters.filter.crew"); setState(() {}); }, icon: const Icon(Icons.filter_alt_off_rounded, size: 18)), ), ], ) ], ), const Gap(10), Row( children: [ ElevatedButton.icon( onPressed: () { _searchFlights(context); }, icon: const Icon(Icons.manage_search), label: const Text("Filter\nFlights"), style: bottomnavstyle, ), const Gap(10), if (flightFilter.values.any((e) => e != "")) Badge( label: Text(flightFilter.values .where((e) => e != "") .length .toString()), child: IconButton.outlined( onPressed: () { for (var k in flightFilter.keys) { flightFilter[k] = ""; } Hive.box("settings") .delete("rosters.filter.flights"); setState(() {}); }, icon: const Icon(Icons.filter_alt_off_rounded, size: 18)), ) ], ), const Gap(10), //icon data for elevated button ], ), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ data.aclegupdate != null ? Text("data retrieved ${data.pnlegupdate?.from(now)}", style: GoogleFonts.robotoMono( fontSize: 10, fontWeight: FontWeight.w300, color: Colors.white)) : Text("no data found", style: GoogleFonts.robotoMono( fontSize: 10, fontWeight: FontWeight.w300)) ], ), ], ), ), title: "Rosters", body: tlcs.isEmpty ? const Text("no Pnleg data") : ListView.builder( itemCount: tlcs.length, // Number of list items itemBuilder: (context, index) { final qualifData = qualif.where((k) => k.tlc == tlcs.elementAt(index)); final tlc = tlcs.elementAt(index); final dutiesTlc = ref.watch(pnlegByTlcProvider(tlc)).where( (e) => ((flightFilter["al"] == "" || e.al == flightFilter["al"]) && (flightFilter["fnum"] == "" || e.fnum == flightFilter["fnum"]) && (flightFilter["dep"] == "" || e.dep == flightFilter["dep"]) && (flightFilter["des"] == "" || e.arr == flightFilter["des"]))); final name = "${qualifData.firstOrNull?.lname?.capitalizeword()}, ${qualifData.firstOrNull?.fname?.capitalizeword()}"; final base = "${qualifData.map((e) => e.base).toSet()}"; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(2), child: Row(children: [ const Gap(10), Text(name, style: const TextStyle( fontWeight: FontWeight.w700, letterSpacing: 1)), const Gap(10), Text( tlc, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w300), ), const Gap(5), Text( base, style: GoogleFonts.robotoMono( fontSize: 11, fontWeight: FontWeight.w300), ) ])), SizedBox( height: rostersheight, child: (dates.isEmpty) ? const Text("No Pnlegs data found") : ListView.builder( // controller: scrollControllerGroup.addAndGet(), //_scrollCtrl, controller: _getCtrl(index), //_scrollCtrl, scrollDirection: Axis.horizontal, itemCount: dates.length, // Number of horizontal items itemBuilder: (context, horizontalIndex) { final date = dates.elementAt(horizontalIndex); final duties = dutiesTlc.where((e) => DTInterval( e.jdep ?? e.jdate?.startOf(Unit.day) ?? Jiffy.now(), e.jarr ?? e.jdate?.endOf(Unit.day) ?? Jiffy.now()) .isOverlap(DTInterval( date.startOf(Unit.day), date.endOf(Unit.day)))); if (pnleg.isEmpty) { return const Padding( padding: EdgeInsets.all(5.0), child: Center( child: Text("No Pnlegs data found"), ), ); } return Padding( padding: const EdgeInsets.symmetric( horizontal: 2, vertical: 1), child: _rostersDay( date: date, legs: duties.toList(), color: date .endOf(Unit.day) .isBefore(now.startOf(Unit.day)) ? Colors.grey[900] : Colors.black, border: date.yMEd == now.yMEd), ); }, ), ), ], ); }, ), ); } @override void initState() { Future.delayed(Duration.zero).then((x) async { final savedcrewfilter = await Hive.box("settings").get("rosters.filter.crew"); final savedflightsfilter = await Hive.box("settings").get("rosters.filter.flights"); print( "rosters: saved crew filter: ${savedcrewfilter.runtimeType} $savedcrewfilter"); print("rosters: saved flights filter: $savedflightsfilter"); if (savedcrewfilter != null && savedcrewfilter is Map) { crewFilter = savedcrewfilter .map((key, value) => MapEntry(key, List.from(value))); } if (savedflightsfilter != null && savedflightsfilter is Map) { flightFilter = savedflightsfilter.map((key, value) => MapEntry(key, value)); } }); super.initState(); } } class PnlegMeta { PnlegMeta({required this.tlcs, required this.dates}); final List tlcs; final List dates; } // final pnlegMeta = Provider((ref) { // print("data: start analyzing pnleg"); // final now = Jiffy.now(); // final data = ref.watch(dataProvider); // final pnlegs = data.pnleg; // final qualif = data.qualif; // Set tlcs = {}; // Set dates = {}; // for (Pnleg pnleg in pnlegs) { // tlcs.add(pnleg.tlc ?? ""); // dates.add(pnleg.jdep?.startOf(Unit.day) ?? // Jiffy.parse(pnleg.date ?? "01/01/1970", // pattern: "dd/MM/yyyy", isUtc: true)); // } // // final restlcs = tlcs.sortedBy((k) =>qualif.firstWhereOrNull((q) => q.tlc == k)?.lname ?? "zzzzzzzzzzzzz"); // final restlcs = tlcs.sortedBy((e) => // "${qualif.firstWhereOrNull((k) => k.tlc == e)?.lname} ${qualif.firstWhereOrNull((k) => k.tlc == e)?.fname}"); // final resdates = dates.sortedBy((k) => k.millisecondsSinceEpoch.toString()); // print( // "data: end analyzing pnleg ${Duration(milliseconds: Jiffy.now().millisecondsSinceEpoch - now.millisecondsSinceEpoch).inSeconds} sec"); // return PnlegMeta(tlcs: restlcs, dates: resdates); // }); final pnlegMeta = Provider((ref) { print("data: start analyzing pnleg"); final now = Jiffy.now(); final data = ref.watch(dataProvider); final pnlegs = data.pnleg; final qualif = data.qualif; print( "data: end analyzing pnleg ${Duration(milliseconds: Jiffy.now().millisecondsSinceEpoch - now.millisecondsSinceEpoch).inSeconds} sec"); return PnlegMeta(tlcs: pnleg.fold({}, (t, e) => {...t, e.tlc ?? ""}).sortedBy((e) { final pn = qualif.firstWhereOrNull((q) => q.tlc == e); return "${pn?.lname} ${pn?.fname}"; }), dates: pnleg.fold( {}, (t, e) => { ...t, e.jdep?.startOf(Unit.day) ?? Jiffy.parse(e.date ?? "01/01/1970", pattern: "dd/MM/yyyy", isUtc: true) }).sortedBy((e) => e.dateTime)); });