| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636 | // ignore_for_file: use_build_context_synchronouslyimport 'dart:developer';import 'dart:io';//import 'package:awesome_dialog/awesome_dialog.dart';import 'package:collection/collection.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:scroll_to_index/scroll_to_index.dart';import 'package:tp5/core/basic_page.dart';import 'package:tp5/core/core.dart';import 'package:tp5/csv/data.dart';import 'package:tp5/fltinfo/view/dutyinfo_page.dart';import 'package:tp5/fltinfo/view/fltinfo_page.dart';import 'package:tp5/pdf/pdf_page.dart';import 'package:tp5/roster/api/crewlink_api.dart';import 'package:tp5/roster/models/duty.dart';import 'package:tp5/roster/view/crewlist_page.dart';import 'package:tp5/roster/widgets/w_day.dart';import 'package:tp5/roster/widgets/w_duty.dart';import 'package:tp5/roster/widgets/w_horizontal_month.dart';class RosterPage extends ConsumerStatefulWidget {  const RosterPage({required this.params, super.key});  final RosterPageParams params;  @override  ConsumerState<ConsumerStatefulWidget> createState() => _RosterPageState();}class _RosterPageState extends ConsumerState<RosterPage> {  late String crewlinkUser;  late String crewlinkPass;  Map? roster;  List<Duty> duties = [];  List<Jiffy> get rostermonths => duties      .fold(          <Jiffy>[],          (List<Jiffy> p, Duty e) => [                ...p,                if (!p                    .map((Jiffy j) => j.format(pattern: "MMMyy"))                    .contains(e.jdate.format(pattern: "MMMyy")))                  e.jdate              ])      .where((Jiffy e) => (duties              .map((Duty d) => d.jdate.yMd)              .contains(e.startOf(Unit.month).yMd) &&          duties              .map((Duty d) => d.jdate.yMd)              .contains(e.endOf(Unit.month).yMd)))      .toList();  String get _rosterKey =>      "roster_${crewlinkUser}_${widget.params.datestart}_${widget.params.dateend}";  String get _rosterMinMaxKey => "minmax_$crewlinkUser";  String get fileidroster => PathTo().crewlinkFile("$_rosterKey.pdf");  String get fileidnotif => PathTo().crewlinkFile("notif_$crewlinkUser.pdf");  final AutoScrollController _scrollCtrl = AutoScrollController();  late Jiffy now;  @override  void initState() {    // getusername & pass    // crewlinkPass = Hive.box("settings").get("crewlink_pass");    crewlinkUser = widget.params.crewlinkuser ??        Hive.box("settings").get("crewlink_user") ??        "";    crewlinkPass = widget.params.crewlinkpass ??        Hive.box("settings").get("crewlink_pass") ??        "";    Future.delayed(Duration(milliseconds: 700), () => _loadRoster());    super.initState();  }  Future<Map?> _loadRosterOnline() async {    if (!ref.read(crewlinkapiProvider).logged) {      final login = await ref          .read(crewlinkapiProvider)          .login(username: crewlinkUser, password: crewlinkPass);      if (login["data"]?["logged"] != true) {        context.showError(login["error"] ?? "Unknown error");        return null;      }    }    final roster = await ref.read(crewlinkapiProvider).roster(        start: widget.params.datestart ??            Jiffy.now().toUtc().startOf(Unit.month).format(pattern: "ddMMMyy"),        end: widget.params.dateend ??            Jiffy.now().toUtc().endOf(Unit.month).format(pattern: "ddMMMyy"),        fileid: fileidroster);    if (roster?["data"]?["msg"] != null) {      context.showError(roster?["data"]?["msg"] ?? "Unknown error");    }    if (roster["error"] == null && roster["data"] != null) {      if (roster["msg"] != null) context.showAlert(roster["msg"]);      Hive.box("crewlink").put(_rosterKey, roster);      return roster;    }    return null;  }  Future<Map?> _loadRosterOffline() async {    return Hive.box("crewlink").get(_rosterKey);  }  _loadRoster() async {    try {      ref.read(isLoadingProvider.notifier).state = true;      final rosterOffline = await _loadRosterOffline();      if (rosterOffline != null && mounted) {        _convertRoster(rosterOffline);        setState(() {});        _scrollToDate();      }      final rosterOnline = await _loadRosterOnline();      if (rosterOnline != null && mounted) {        _convertRoster(rosterOnline);        setState(() {});        _scrollToDate();      }    } finally {      if (mounted) {        ref.read(isLoadingProvider.notifier).state = false;      }    }  }  String get tlc => crewlinkUser;  _scrollToDate({Jiffy? date}) {    final jdate = date ?? Jiffy.now().toUtc();    bool found = false;    int id = 0;    for (Duty duty in duties) {      if (duty.jdate.yMd == jdate.yMd) {        found = true;        break;      }      id++;    }    Future.delayed(const Duration(milliseconds: 100)).then((value) {      if (found && mounted && _scrollCtrl.hasClients) {        _scrollCtrl.scrollToIndex(id,            duration: const Duration(milliseconds: 1300),            preferPosition: AutoScrollPosition.begin);      }    });  }  _convertRoster(Map? input) {    roster = input;    int i = 0;    duties.clear();    final decoded = (input?["data"]?["decoded"]?["roster"] ?? {});    for (var date in decoded.keys) {      var dutylist = (decoded[date] as List);      for (var duty in dutylist) {        i++;        duties.add(            Duty(date: date, type: duty["type"], data: duty["data"], order: i));        // print(        //     "${(duties.last).jdate.yMd} ${(duties.last).type} ${(duties.last).start?.Hm} ${(duties.last).end?.Hm}");      }    }    _calculHours();  }  Duration _fltHrs = Duration.zero;  num _nbIso = 0;  Duration _credit = Duration.zero;  _calculHours() {    _fltHrs = duties        .where((e) => e.jdate.yM == duties.firstOrNull?.jdate.yM)        .fold(Duration.zero, (t, e) {      switch (e.type) {        case "flight":          return t + DTInterval(e.start!, e.end!).duration;        default:          return t;      }    });    _nbIso = duties.fold(        0,        (t, e) =>            t +            ((((duties                            .firstWhereOrNull((f) =>                                (f.jdate.yMEd == e.jdate.yMEd &&                                    f.type == "credit"))                            ?.data["credit"]) ??                        "0:00") ==                    "0:00")                ? 0                : 1));    _credit = duties        .where((e) => e.jdate.yM == duties.firstOrNull?.jdate.yM)        .fold(Duration.zero, (t, e) {      switch (e.type) {        case "flight":          return t + DTInterval(e.start!, e.end!).duration;        case "dhflight":          return t + DTInterval(e.start!, e.end!).duration;        case "dhlimo":          return t + DTInterval(e.start!, e.end!).duration;        case "ground":          if (e.data["label"].startsWith("SBY")) {            return t + const Duration(hours: 6, minutes: 10);          }          if (["CA", "CM", "OFF", "PP", "FR"].contains(e.data["label"])) {            return t;          }          return t;        default:          return t;      }    }).add(_nbIso >= 3            ? Duration.zero            : (const Duration(hours: 2, minutes: 10)                .multiply(_nbIso.toDouble())));  }  final bottomnavstyle = ElevatedButton.styleFrom(      shape: RoundedRectangleBorder(        borderRadius: BorderRadius.circular(5.0),      ),      backgroundColor:          const Color.fromARGB(255, 0, 36, 53) //elevated btton background color      );  @override  Widget build(BuildContext context) {    now = ref.watch(clockProvider);    return BasicPage(      actions: [        IconButton(            onPressed: () => context.push("/crewlink/settings"),            icon: const Icon(Icons.settings)),        const Gap(10)      ],      bottomNavigationBar: Container(        padding: const EdgeInsets.all(8),        // color: Colors.black,        decoration: BoxDecoration(            gradient: LinearGradient(          begin: Alignment.topCenter,          end: Alignment.bottomCenter,          colors: [            Colors.grey[700]!,            Colors.black,          ],        )),        child: Row(          mainAxisAlignment: MainAxisAlignment.spaceAround,          children: [            ElevatedButton.icon(              onPressed: () {                _showMonths(context);              },              icon: const Icon(                  Icons.calendar_month), //icon data for elevated button              label: const Text("Change\nMonth"), //label text              style: bottomnavstyle,            ),            const Gap(10),            ElevatedButton.icon(              onPressed: () {                _showPdf(context);              },              icon: Image.asset(                'assets/pdficon.png',                width: 28,              ), //icon data for elevated button              label: const Text("Roster\n  PDF"), //label text              style: bottomnavstyle,            ),            const Gap(10),            Column(mainAxisSize: MainAxisSize.min, children: [              Text(                "Flt: ${_fltHrs.tohhmm}",                style:                    TextStyle(color: Colors.white, fontWeight: FontWeight.w700),              ),              Text(                "Credit: [${_credit.tohhmm}]",                style: TextStyle(color: Colors.blue),              ),            ])          ],        ),      ),      title: "CrewLink / Roster",      body: ListView.builder(        itemCount: duties.length,        itemBuilder: (contextx, index) => AutoScrollTag(            key: ValueKey(index),            controller: _scrollCtrl,            index: index,            child: _getItem(contextx, index)),        shrinkWrap: false,        controller: _scrollCtrl,      ),    );  }  Widget _getItem(BuildContext ctx, int i) {    final duty = duties.elementAt(i);    Duty? firstitem =        duties.firstWhereOrNull((Duty el) => el.date == duty.date);    bool isfirstitem = false;    if (firstitem != null &&        firstitem.type == duty.type &&        firstitem.data == duty.data) isfirstitem = true;    final String lastday =        duty.jdate.subtract(days: 1).format(pattern: "yyyy-MM-dd");    final Duty? dutyendingtoday = (!isfirstitem)        ? null        : duties.firstWhereOrNull((Duty el) =>            (el.date == lastday) &&            (el.end != null) &&            (el.end!.format(pattern: "yyyy-MM-dd") == duty.date));    return Column(children: [      if (isfirstitem) ...[        const Gap(10),        const Divider(),        WDay(          date: duty.jdate,          highlight: now.yMd == duty.jdate.yMd,          onTap: () => context.go("/crewlink/crewlist",              extra: CrewlistPageParams(                  datestart: duty.jdate.format(pattern: "ddMMMyy"))),        ),        if (dutyendingtoday != null)          WDuty(duty: dutyendingtoday, date: duty.date),      ],      InkWell(        highlightColor: Colors.yellow[900],        onTap: () {          // log("Tap: ${duty.start?.Hm} ${duty.type} ${duty.data}",name: "RosterPage");          if (duty.type == "changed") {            AlertDialog(              title: const Text('Notification'),              content: const Text('Load and show pending notification?'),              actions: [                TextButton(                    child: const Text("Load Notification",                        style: TextStyle(color: Colors.red, fontSize: 16)),                    onPressed: () async {                      ref.read(isLoadingProvider.notifier).state = true;                      final res = await ref                          .read(crewlinkapiProvider)                          .notif(download: true, fileid: fileidnotif);                      if (mounted) {                        ref.read(isLoadingProvider.notifier).state = false;                      }                      if (res is Map && res["data"]?["id"] != null) {                        _showNotif();                      } else {                        AlertDialog(                            title: const Text('Notification'),                            content: Text(res?["error"] ??                                res?["data"]?["msg"] ??                                'No pending notification found !!!'),                            actions: [                              TextButton(                                  child: const Text("OK"),                                  onPressed: () => context.pop())                            ]).show(context);                      }                    }),                TextButton(                    child: const Text(                      "Discard",                      style: TextStyle(color: Colors.green),                    ),                    onPressed: () => context.pop())              ],            ).show(context);          } else if (duty.type == "flight") {            context.push("/fltinfo",                extra: FltinfoParams(                    al: duty.data["al"],                    fnum: duty.data["fnum"],                    dep: duty.data["dep"],                    des: duty.data["des"],                    jdep: duty.start,                    jdes: duty.end));          } else if (duty.type == "dhflight") {            context.push("/fltinfo",                extra: FltinfoParams(                    al: duty.data["al"],                    fnum: duty.data["fnum"],                    dep: duty.data["dep"],                    des: duty.data["des"],                    jdep: duty.start,                    jdes: duty.end));          } else if (duty.type == "dhlimo") {            final pseudoleg = Pnleg(                dep: duty.data["dep"],                arr: duty.data["des"],                depdate: duty.start?.format(pattern: "dd/MM/yyyy"),                deptime: duty.start?.format(pattern: "HHmm"),                arrdate: duty.end?.format(pattern: "dd/MM/yyyy"),                arrtime: duty.end?.format(pattern: "HHmm"),                //label: duty.data["label"],                type: "G");            context.push("/dutyinfo",                extra: DutyinfoParams(                    dutytype: pseudoleg.dutytype,                    jdep: duty.start,                    jdes: duty.end,                    dep: duty.data["dep"],                    des: duty.data["des"]));          } else if (duty.type == "ground") {            final pseudoleg = Pnleg(              tlc: tlc,              dep: duty.data["dep"],              arr: duty.data["des"],              depdate: duty.start?.format(pattern: "dd/MM/yyyy"),              deptime: duty.start?.format(pattern: "HHmm"),              arrdate: duty.end?.format(pattern: "dd/MM/yyyy"),              arrtime: duty.end?.format(pattern: "HHmm"),              label: duty.data["label"],              type: "A",            );            if (pseudoleg.dutytype == "standby") {              context.push("/dutyinfo",                  extra: DutyinfoParams(                    dutytype: pseudoleg.dutytype,                    tlc: pseudoleg.tlc,                    //label: pseudoleg.label,                    jdep: pseudoleg.jdep,                    jdes: pseudoleg.jarr,                    //start: duty.start,                    //end: duty.end,                    dep: pseudoleg.dep,                    des: pseudoleg.arr,                    sameday: true,                  ));            } else {              context.push("/dutyinfo",                  extra: DutyinfoParams(                    // dutytype: pseudoleg.dutytype,                    label: pseudoleg.label,                    jdep: duty.start,                    jdes: duty.end,                    // start: duty.start,                    // end: duty.end,                    dep: duty.data["dep"],                    des: duty.data["des"],                    // sameday: true,                  ));            }          }        },        child: WDuty(duty: duty),      )    ]);  }  void _showNotif() {    {      context.pop();      context          .push("/pdf",              extra: PdfPageParams(                  file: fileidnotif,                  title: "Roster change",                  bottom: ElevatedButton(                    style: ElevatedButton.styleFrom(                      backgroundColor: Colors.red[900],                    ),                    onPressed: () async {                      ref.read(isLoadingProvider.notifier).state = true;                      final ack =                          await ref.read(crewlinkapiProvider).confirmNotif();                      // final ack = {                      //   "error": null,                      //   "data": {                      //     "msg": null,                      //     "error": null,                      //     "notif": true                      //   }                      // };                      if (mounted) {                        ref.read(isLoadingProvider.notifier).state = false;                      }                      final ackok = (ack is Map && ack["error"] == null                          //&&                          //ack["data"]?["error"] != null                          );                      if (ackok) {                        AlertDialog(                            title: const Text('Notification'),                            content:                                // ack["data"]?["msg"] ??                                const Text(                                    'Notification for change was acknowledged.'),                            actions: [                              TextButton(                                  child: const Text("OK"),                                  onPressed: () {                                    context.pop({"notif": true});                                  })                            ]).show(context);                      } else {                        AlertDialog(                            title: const Text('Notification'),                            content:                                // ack["data"]?["msg"] ??                                const Text(                                    'A Problem has occured. Notification not confirmed.'),                            actions: [                              TextButton(                                  child: const Text("OK"),                                  onPressed: () {                                    context.pop({"notif": false});                                  })                            ]).show(context);                      }                    },                    child: const Padding(                      padding: EdgeInsets.all(8.0),                      child: Text(                        'Acknowledge & Confirm changes',                        style: TextStyle(fontSize: 20, color: Colors.yellow),                      ),                    ),                  )))          .then((res) {        // if (res is Map && res["notif"]) _loadRoster();        //! check if the roster is still the same otherwise call _loadRoster        if (res is Map && res["notif"])          context.pushReplacement("/crewlink/roster");      });    }  }  void _showPdf(xcontext) {    if (File(fileidroster).existsSync()) {      context.push("/pdf",          extra: PdfPageParams(              file: fileidroster,              title:                  "Roster: ${Jiffy.parse(widget.params.datestart ?? "", pattern: "ddMMMyy", isUtc: true).format(pattern: "MMMM yyyy")}"));    } else {      context.showError(          "Can't find the roster for this month.\n Try to connect to internet and retrieve it from CrewLink?");    }  }  void _showMonths(xcontext) async {    ref.read(isLoadingProvider.notifier).state = true;    final resoff = Hive.box("crewlink").get(_rosterMinMaxKey);    final reson = await ref.read(crewlinkapiProvider).rosterMinMax();    if (mounted) {      ref.read(isLoadingProvider.notifier).state = false;    }    dynamic res;    if (reson["error"] == null && reson?["data"] != null) {      Hive.box("crewlink").put(_rosterMinMaxKey, reson["data"]);      res = reson["data"];    } else if (resoff == null) {      context.showError(reson["error"] ?? "Unknown error");      return;    }    res = res ?? resoff;    showModalBottomSheet(        useSafeArea: true,        backgroundColor: Colors.blueGrey[900],        context: context,        builder: (BuildContext bc) {          return HorizontalMonth(              start: Jiffy.parse(res["mindate"],                      pattern: "yyyy-MM-dd", isUtc: true)                  // .add(days: 1),                  .add(months: 1)                  .startOf(Unit.month),              end: Jiffy.parse(res["maxdate"],                      pattern: "yyyy-MM-dd", isUtc: true)                  .subtract(days: 1),              selectedmonth: rostermonths,              onmonthclick: (date) {                bc.push("/crewlink/roster",                    extra: RosterPageParams(                        dateend:                            date.endOf(Unit.month).format(pattern: "ddMMMyy"),                        datestart:                            date.startOf(Unit.month).format(pattern: "ddMMMyy"),                        crewlinkuser: crewlinkUser,                        crewlinkpass: crewlinkPass));              });        });  }}class RosterPageParams {  const RosterPageParams({    this.dateend,    this.datestart,    this.crewlinkuser,    this.crewlinkpass,  });  final String? dateend;  final String? datestart;  final String? crewlinkuser;  final String? crewlinkpass;}
 |