import 'dart:async'; import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hub/components/templates_components/card_item_template_component/card_item_template_component_widget.dart'; import 'package:hub/features/backend/index.dart'; import 'package:hub/flutter_flow/flutter_flow_icon_button.dart'; import 'package:hub/flutter_flow/flutter_flow_theme.dart'; import 'package:hub/flutter_flow/flutter_flow_util.dart'; import 'package:hub/shared/utils/dialog_util.dart'; import 'package:hub/shared/utils/log_util.dart'; import 'package:hub/shared/utils/snackbar_util.dart'; import 'package:rxdart/rxdart.dart'; import '/flutter_flow/form_field_controller.dart'; import 'package:hub/flutter_flow/nav/nav.dart'; import 'package:hub/shared/utils/limited_text_size.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hub/features/storage/index.dart'; /// [ProvisionalHistoryEvent] class ProvisionalHistoryEvent {} /// [LoadProvisionalHistory] class LoadProvisionalHistory extends ProvisionalHistoryEvent {} /// [ProvisionalHistoryStateBloc] class ProvisionalHistoryStateBloc { final String devUUID; final String userUUID; final String cliUUID; final bool isLoading; ProvisionalHistoryStateBloc({ required this.devUUID, required this.userUUID, required this.cliUUID, this.isLoading = false, }); ProvisionalHistoryStateBloc copyWith({ String? devUUID, String? userUUID, String? cliUUID, bool? isLoading, }) { return ProvisionalHistoryStateBloc( devUUID: devUUID ?? this.devUUID, userUUID: userUUID ?? this.userUUID, cliUUID: cliUUID ?? this.cliUUID, isLoading: isLoading ?? this.isLoading, ); } } /// [ProvisionalHistoryBloc] class ProvisionalHistoryBloc extends Bloc { ProvisionalHistoryBloc() : super(ProvisionalHistoryStateBloc( devUUID: '', userUUID: '', cliUUID: '')) { on(_onLoadProvisionalHistory); } Future _onLoadProvisionalHistory( LoadProvisionalHistory event, Emitter emit, ) async { emit(state.copyWith(isLoading: true)); final devUUID = (await StorageHelper().get(ProfileStorageKey.devUUID.key)) ?? ''; final userUUID = (await StorageHelper().get(ProfileStorageKey.userUUID.key)) ?? ''; final cliUUID = (await StorageHelper().get(ProfileStorageKey.clientUUID.key)) ?? ''; emit(state.copyWith( devUUID: devUUID, userUUID: userUUID, cliUUID: cliUUID, isLoading: false)); } } /// [ProvisionalHistoryPage] @immutable // ignore: must_be_immutable class ProvisionalHistoryPage extends StatefulWidget { Map opt; ProvisionalHistoryPage({super.key, Map? opt}) : opt = opt ?? const {'AGP_STATUS': '.*'}; @override State createState() => ProvisionalHistoryState(opt); } /// [ProvisionalHistoryState] class ProvisionalHistoryState extends State { final BehaviorSubject> selectedTypeSubject; late ScrollController _scrollController; final scaffoldKey = GlobalKey(); bool _isSubjectClosed = false; int _pageNumber = 1; bool hasData = false; bool _loading = false; String status = '.*'; late Future future; List wrap = []; ProvisionalHistoryState(Map opt) : selectedTypeSubject = BehaviorSubject.seeded(opt) { selectedTypeSubject.listen((value) {}); } @override void initState() { super.initState(); future = fetchHistoryService(); _scrollController = ScrollController() ..addListener(() { if (_scrollController.position.atEdge && _scrollController.position.pixels != 0) { _loadMore(); } }); } @override void dispose() { selectedTypeSubject.close(); _isSubjectClosed = true; super.dispose(); } @override Widget build(BuildContext context) { final theme = FlutterFlowTheme.of(context); return Scaffold( key: scaffoldKey, backgroundColor: FlutterFlowTheme.of(context).primaryBackground, appBar: _appBar(context, theme), body: _body(context), ); } PreferredSizeWidget _appBar(BuildContext context, FlutterFlowTheme theme) { return AppBar( backgroundColor: theme.primaryBackground, automaticallyImplyLeading: false, leading: _backButton(context, theme), title: _title(context, theme), centerTitle: true, elevation: 0.0, actions: [_filterButton(context)], ); } Widget _backButton(BuildContext context, FlutterFlowTheme theme) { return FlutterFlowIconButton( key: ValueKey('BackNavigationAppBar'), borderColor: Colors.transparent, borderRadius: 30.0, borderWidth: 1.0, buttonSize: 60.0, icon: Icon( Icons.keyboard_arrow_left, color: theme.primaryText, size: 30.0, ), onPressed: () => Navigator.of(context).pop(), ); } Widget _title(BuildContext context, FlutterFlowTheme theme) { return Text( FFLocalizations.of(context).getVariableText( ptText: 'Consultar Agendas', enText: 'Provisional History', ), style: FlutterFlowTheme.of(context).headlineMedium.override( fontFamily: FlutterFlowTheme.of(context).headlineMediumFamily, color: FlutterFlowTheme.of(context).primaryText, fontSize: 16.0, fontWeight: FontWeight.bold, letterSpacing: 0.0, useGoogleFonts: GoogleFonts.asMap() .containsKey(FlutterFlowTheme.of(context).headlineMediumFamily), ), ); } Widget _filterButton(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Padding( padding: const EdgeInsets.fromLTRB(0, 0, 10, 0), child: IconButton( icon: const Icon(Icons.filter_list), onPressed: () async { final Map? selectedFilter = await showModalBottomSheet>( isScrollControlled: true, backgroundColor: Colors.transparent, context: context, builder: (context) { return GestureDetector( onTap: () => Navigator.of(context).pop(), child: Container( color: Colors.transparent, child: GestureDetector( onTap: () {}, child: FilterWidget( defaultSelections: selectedTypeSubject.value, filterOptions: { 'AGP_STATUS': [ { 'title': FFLocalizations.of(context) .getVariableText( ptText: 'Ativo', enText: 'Active', ), 'value': 'AT', }, { 'title': FFLocalizations.of(context) .getVariableText( ptText: 'Concluído', enText: 'Completed', ), 'value': 'CO', }, { 'title': FFLocalizations.of(context) .getVariableText( ptText: 'Inativo', enText: 'Inactive', ), 'value': 'IN', }, { 'title': FFLocalizations.of(context) .getVariableText( ptText: 'Aguardando Aprovação', enText: 'Awaiting Approval', ), 'value': 'AA', }, ], }, filterTitles: {'AGP_STATUS': ''}, ), ), ), ); }); if (selectedFilter != null) { _updateHistoryAction(selectedFilter); } }, ), ), ], ); } void _updateHistoryAction(Map newType) { if (!_isSubjectClosed) { final currentType = selectedTypeSubject.value; final updatedType = Map.from(currentType); bool needsUpdate = false; newType.forEach((key, newValue) { if (currentType[key] != newValue) { updatedType[key] = newValue; needsUpdate = true; } }); if (needsUpdate) { selectedTypeSubject.add(updatedType); fetchCardListViewService(updatedType); safeSetState(() {}); } } } Future fetchHistoryService() async { try { setState(() => _loading = true); var response = await PhpGroup.getProvSchedules(_pageNumber.toString(), status); final List history = response.jsonBody['agendamento']['value'] ?? []; if (history.isNotEmpty) { setState(() { wrap.addAll(history); hasData = true; _loading = false; }); return response; } SnackBarUtil.showNoMoreDataSnackbar(context); setState(() { hasData = false; _loading = false; }); return null; } catch (e, s) { await DialogUtil.errorDefault(context); LogUtil.requestAPIFailed('processRequest', "", "Busca Acesso", e, s); setState(() { hasData = false; _loading = false; }); } return null; } Widget _body(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ if (hasData == false && _pageNumber <= 1 && _loading == false) Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ Center( child: Text( FFLocalizations.of(context).getVariableText( ptText: "Nenhum histórico encontrado!", enText: "No history found!"), )), ], ), ) else if (hasData || _pageNumber >= 1) Expanded(child: _cardListViewOrganismWidget()), if (hasData == true && _loading) Container( padding: const EdgeInsets.only(top: 15, bottom: 15), child: Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( FlutterFlowTheme.of(context).primary, ), ), ), ) ], ); } void _loadMore() { if (hasData == true) { _pageNumber++; future = fetchHistoryService(); } } void fetchCardListViewService(Map select) { status = select['AGP_STATUS']!; wrap = []; _pageNumber = 1; future = fetchHistoryService(); } Widget _cardListViewOrganismWidget() { return FutureBuilder( future: future, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting && wrap.isEmpty) { return Center( child: SizedBox( width: 50.0, height: 50.0, child: SpinKitCircle( color: FlutterFlowTheme.of(context).primary, size: 50.0, ), ), ); } else if (snapshot.hasError) { return Center( child: Text(FFLocalizations.of(context).getVariableText( ptText: "Falha ao efetuar operação!", enText: "Failed to perform operation!")), ); } return ListView.builder( shrinkWrap: true, physics: const BouncingScrollPhysics(), controller: _scrollController, itemCount: wrap.length, itemBuilder: (context, index) { final historyItem = wrap[index]; return _historyCardMoleculeWidget(context, historyItem); }, ); }, ); } Widget _historyCardMoleculeWidget(BuildContext context, dynamic historyItem) { return CardItemTemplateComponentWidget( imagePath: null, labelsHashMap: _buildLabelsHashMap(context, historyItem), statusHashMap: _buildStatusHashMap(context, historyItem), onTapCardItemAction: () async {}, ); } String _imageUrlAtomWidget(String document, String type) { return valueOrDefault( "https://freaccess.com.br/freaccess/getImage.php?&cliID=&atividade=getFoto&Documento=$document&tipo=$type", "https://storage.googleapis.com/flutterflow-io-6f20.appspot.com/projects/flutter-freaccess-hub-0xgz9q/assets/7ftdetkzc3s0/360_F_64676383_LdbmhiNM6Ypzb3FM4PPuFP9rHe7ri8Ju.jpg", ); } Map _buildLabelsHashMap( BuildContext context, dynamic historyItem) { return { FFLocalizations.of(context).getVariableText( ptText: 'Nome:', enText: 'Name:', ): historyItem['AGP_NOME'] ?? '', FFLocalizations.of(context).getVariableText( ptText: 'Vencimento', enText: 'Expiration', ): formatDate(historyItem['AGP_DT_VISITA']), FFLocalizations.of(context).getVariableText( ptText: 'Observação:', enText: 'Observation:', ): formatObs(historyItem['AGP_OBSERVACAO']), }; } String formatObs(String? obs) { if (obs == null || obs.isEmpty) { return FFLocalizations.of(context).getVariableText( ptText: 'Não fornecida', enText: 'No provided', ); } return obs; } String formatDate(String dateString) { DateTime dateTime = DateTime.parse(dateString); return "${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}"; } List> _buildStatusHashMap( BuildContext context, dynamic historyItem) { return [ { FFLocalizations.of(context).getVariableText( ptText: 'Visitante', enText: 'Visitor', ): FlutterFlowTheme.of(context).alternate2, }, _getStatusMap(context, historyItem) ]; } Map _getStatusMap(BuildContext context, dynamic json) { late Map statusColorMap; log(DateTime.parse(json['AGP_DT_VISITA']).toString()); log(DateTime.now().toString()); final DateTime now = DateTime.now(); final DateTime date = DateTime.parse(json['AGP_DT_VISITA']); final bool isExpired = now.isAfter(date); final String statusMap = json['AGP_STATUS']; switch (statusMap) { case 'AT': return isExpired ? { FFLocalizations.of(context).getVariableText( ptText: 'Vencido', enText: 'Expired', ): FlutterFlowTheme.of(context).error, } : { FFLocalizations.of(context).getVariableText( ptText: 'Ativo', enText: 'Active', ): FlutterFlowTheme.of(context).success, }; case 'CO': return { FFLocalizations.of(context).getVariableText( ptText: 'Concluido', enText: 'Completed', ): Colors.blue, }; case 'IN': return { FFLocalizations.of(context).getVariableText( ptText: 'Inativo', enText: 'Inactive', ): FlutterFlowTheme.of(context).error, }; case 'AA': return { FFLocalizations.of(context).getVariableText( ptText: 'Aguardando Aprovação', enText: 'Awaiting Approval', ): FlutterFlowTheme.of(context).warning, }; default: return { FFLocalizations.of(context).getVariableText( ptText: 'Desconhecido', enText: 'Unknown', ): FlutterFlowTheme.of(context).alternate2, }; } } } /// [FilterModel] class FilterModel extends FlutterFlowModel { FocusNode? textFieldFocusNode; TextEditingController? textController; String? Function(BuildContext, String?)? textControllerValidator; bool? checkboxValue1; bool? checkboxValue2; FormFieldController>? checkboxGroupValueController; List? get checkboxGroupValues => checkboxGroupValueController?.value; set checkboxGroupValues(List? v) => checkboxGroupValueController?.value = v; @override void initState(BuildContext context) {} @override void dispose() { textFieldFocusNode?.dispose(); textController?.dispose(); } } /// [FilterWidget] class FilterWidget extends StatefulWidget { final Map defaultSelections; final Map>> filterOptions; final Map filterTitles; const FilterWidget({ super.key, required this.defaultSelections, required this.filterOptions, required this.filterTitles, }); @override _FilterWidgetState createState() => _FilterWidgetState(); } /// [_FilterWidgetState] class _FilterWidgetState extends State { late FilterModel _model; late Map selected; @override void setState(VoidCallback callback) { super.setState(callback); _model.onUpdate(); } @override void initState() { super.initState(); _model = createModel(context, () => FilterModel()); _model.textController ??= TextEditingController(); _model.textFieldFocusNode ??= FocusNode(); selected = Map.from(widget.defaultSelections); } void _applyFilter() { Map filterResult = { 'search': _model.textController?.text == '' ? '.*' : _model.textController!.text.toLowerCase(), }; widget.filterOptions.forEach((key, options) { filterResult[key] = selected[key]!.isEmpty || selected[key]!.length < 1 ? '.*' : selected[key]!; }); setState(() { // Update the state with the new filter result selected = filterResult; }); context.pop(filterResult); } Widget _buildCheckboxListTile( String key, List> options, double fontsize) { double limitedInputFontSize = LimitedFontSizeUtil.getInputFontSize(context); return Column( children: [ Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( padding: const EdgeInsetsDirectional.fromSTEB(0.0, 3.0, 0.0, 0.0), child: Text( widget.filterTitles[key]!, textAlign: TextAlign.left, style: FlutterFlowTheme.of(context).bodyMedium.override( fontFamily: FlutterFlowTheme.of(context).bodyMediumFamily, fontSize: limitedInputFontSize, letterSpacing: 0.0, useGoogleFonts: GoogleFonts.asMap().containsKey( FlutterFlowTheme.of(context).bodyMediumFamily), color: FlutterFlowTheme.of(context).primaryText, ), ), ), ], ), ListView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: options.length, itemBuilder: (context, index) { final option = options[index]; return CheckboxListTile( title: Text( option['title']!, style: FlutterFlowTheme.of(context).bodyMedium.override( fontFamily: FlutterFlowTheme.of(context).bodyMediumFamily, letterSpacing: 0.0, fontSize: limitedInputFontSize, useGoogleFonts: GoogleFonts.asMap().containsKey( FlutterFlowTheme.of(context).bodyMediumFamily), color: FlutterFlowTheme.of(context).primaryText, ), ), dense: true, value: selected[key]!.contains(option['value']), onChanged: (bool? value) { setState(() { if (value == true) { if (!selected[key]!.contains(option['value'])) { selected[key] = option['value']; } } else { selected[key] = ''; } }); }, activeColor: FlutterFlowTheme.of(context).primary, checkColor: FlutterFlowTheme.of(context).info, checkboxShape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(100), ), enableFeedback: true, side: BorderSide( width: 5, color: FlutterFlowTheme.of(context).secondaryText, ), controlAffinity: ListTileControlAffinity.leading, ); }, ), ], ); } @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; return Center( child: Container( width: screenWidth - (screenWidth * 0.35), height: screenWidth, decoration: BoxDecoration( color: FlutterFlowTheme.of(context).primaryBackground, borderRadius: BorderRadius.circular(24.0), ), child: Padding( padding: const EdgeInsets.all(4.0), child: Column( children: [ Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( padding: const EdgeInsetsDirectional.fromSTEB( 10.0, 10.0, 0.0, 10.0), child: Text( FFLocalizations.of(context).getVariableText( ptText: 'Filtros', enText: 'Filters', ), style: FlutterFlowTheme.of(context) .headlineMedium .override( fontFamily: FlutterFlowTheme.of(context) .headlineMediumFamily, color: FlutterFlowTheme.of(context).primaryText, fontSize: LimitedFontSizeUtil.getHeaderFontSize(context), letterSpacing: 0.0, fontWeight: FontWeight.bold, useGoogleFonts: GoogleFonts.asMap().containsKey( FlutterFlowTheme.of(context) .headlineMediumFamily), ), ), ), ], ), Expanded( child: SingleChildScrollView( child: Container( padding: const EdgeInsets.all(10), child: Column( mainAxisSize: MainAxisSize.min, children: widget.filterOptions.keys.map((key) { return _buildCheckboxListTile( key, widget.filterOptions[key]!, 14); }).toList(), ), ), ), ), ElevatedButton( onPressed: _applyFilter, style: ElevatedButton.styleFrom( foregroundColor: FlutterFlowTheme.of(context).info, backgroundColor: FlutterFlowTheme.of(context).primary, ), child: Text( FFLocalizations.of(context).getVariableText( ptText: 'Aplicar', enText: 'Apply', ), style: FlutterFlowTheme.of(context).bodyMedium.override( fontFamily: FlutterFlowTheme.of(context).bodyMediumFamily, color: FlutterFlowTheme.of(context).info, fontSize: LimitedFontSizeUtil.getInputFontSize(context), letterSpacing: 0.0, fontWeight: FontWeight.bold, useGoogleFonts: GoogleFonts.asMap().containsKey( FlutterFlowTheme.of(context).bodyMediumFamily), )), ), ], ), ), ), ); } }