| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 |
- // ignore_for_file: use_build_context_synchronously
- import '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("profile").get("crewlink_pass");
- crewlinkUser = widget.params.crewlinkuser ??
- Hive.box("profile").get("crewlink_user") ??
- "";
- crewlinkPass = widget.params.crewlinkpass ??
- Hive.box("profile").get("crewlink_pass") ??
- "";
- Future.delayed(Duration.zero, () => _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 {
- ref.read(isLoadingProvider.notifier).state = false;
- }
- }
- _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");
- switch (duty.type) {
- case "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);
- 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);
- break;
- case "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));
- case "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));
- case "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"]));
- break;
- case "ground":
- 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,
- label: pseudoleg.label,
- jdep: duty.start,
- jdes: duty.end,
- start: duty.start,
- end: duty.end,
- dep: duty.data["dep"],
- des: duty.data["des"]));
- break;
- default:
- }
- },
- 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
- // }
- // };
- 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();
- if (res is Map && res["notif"]) context.go("/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();
- 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;
- }
|