From 1d4041f92b30888cd9997aa165beeedd9965f3c2 Mon Sep 17 00:00:00 2001 From: "J. A. Messias" Date: Thu, 30 Jan 2025 12:14:24 -0300 Subject: [PATCH] WIP --- lib/features/docs/documentation_page.dart | 779 ++++++++++++++++++++++ 1 file changed, 779 insertions(+) create mode 100644 lib/features/docs/documentation_page.dart diff --git a/lib/features/docs/documentation_page.dart b/lib/features/docs/documentation_page.dart new file mode 100644 index 00000000..f2d04826 --- /dev/null +++ b/lib/features/docs/documentation_page.dart @@ -0,0 +1,779 @@ +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), + )), + ), + ], + ), + ), + ), + ); + } +}