import 'package:ambito/src/entity/_general/filter/item_filter_repository.dart'; import 'package:ambito/src/entity/measure/measure_repository.dart'; import 'package:ambito/src/packages/ambito_notifier/notifier/filter_notifier.dart'; import 'package:ambito/src/pages/ambito_page.dart'; import 'package:ambito/src/widgets/appbar/ambito_appbar.dart'; import 'package:ambito/src/widgets/form/fields/field_title.dart'; import 'package:ambito/src/widgets/form/form_widget.dart'; import 'package:ambito/src/widgets/form/form_widget_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:get/get.dart'; import 'package:highlight_text/highlight_text.dart'; import 'package:screen_breakpoints/screen_breakpoints.dart'; import '../../../main.dart'; import '../../entity/entities.dart'; import '../../packages/ambito_theme/ambito_theme.dart'; class ActionsPage extends AmbitoPage { const ActionsPage({super.key}); @override final String path = 'massnahmendatenbank'; @override final String title = 'Maßnamendatenbank'; @override State createState() => ActionsPageState(); } class ActionsPageState extends State { Map visible = {}; List effort = [], effect = [], region = []; Set type = {}, areaType = {}, support = {}, months = {}; String? filterType, filterAreaType, filterSupport, filterMonths, searchText = ''; List? selectedMonths; List massnahmen = []; Map words = {}; String? preselectAreaType; String? preselectMeasureType; final TextEditingController _searchController = TextEditingController(); final FocusNode _searchFocusNode = FocusNode(); @override void initState() { ambitoFilterNotifier.addListener(_changeListener); setState(() { preselectAreaType = prefs.getString('selected_areaType'); if (preselectAreaType != null && preselectAreaType!.isNotEmpty) { filterAreaType = preselectAreaType; ambitoFilterNotifier.setFilter('areaType', preselectAreaType!); } }); _initializeData(); super.initState(); } void _changeListener() { setState(() { filterAreaType = ambitoFilterNotifier.getFilter('areaType'); filterType = ambitoFilterNotifier.getFilter('group'); filterSupport = ambitoFilterNotifier.getFilter('fundingProgram'); filterMonths = ambitoFilterNotifier.getFilter('month'); }); } @override void dispose() { //ambitoFilterNotifier.dispose(); super.dispose(); } void _initializeData() { massnahmen = MeasureRepository().getAllOrdered(); _updateFilterSets(); } void _updateFilterSets() { final updatedTypes = {}, updatedAreaTypes = {}; final updatedSupports = {}, updatedMonths = {}; for (final massnahme in massnahmen) { if (massnahme.actionGroup?.value != null) { updatedTypes.add(massnahme.actionGroup!.value!); } massnahme.factsheetAreaType?.forEach((aType) { if (aType.value != null) updatedAreaTypes.add(aType.value!); }); massnahme.fundingPrograms?.forEach((aType) { if (aType.value != null) updatedSupports.add(aType.value!); }); massnahme.timeFrame?.forEach((tfType) { if (tfType.value != null) updatedMonths.add(tfType.value!); }); } setState(() { type = updatedTypes; areaType = updatedAreaTypes; support = updatedSupports; months = updatedMonths; }); } void _onSearchTextChanged(String value) { setState(() { searchText = value; words = { for (var word in value.split(' ')) word: HighlightedWord( textStyle: Theme.of(context).textTheme.headlineSmall?.copyWith( color: Colors.white, backgroundColor: Colors.green.shade800)) }; }); } @override Widget build(BuildContext context) { final updatedTypes = {}; final updatedAreaTypes = {}; final updatedSupports = {}; final updatedMonths = {}; for (final massnahme in massnahmen) { if (massnahme.actionGroup?.value != null) { updatedTypes.add(massnahme.actionGroup!.value!); } massnahme.factsheetAreaType?.forEach((aType) { updatedAreaTypes.add(aType.value!); }); massnahme.fundingPrograms?.forEach((aType) { updatedSupports.add(aType.value!); }); massnahme.timeFrame?.forEach((tfType) { updatedMonths.add(tfType.value!); }); } setState(() { type.addAll(updatedTypes); areaType.addAll(updatedAreaTypes); support.addAll(updatedSupports); months.addAll(updatedMonths); }); final actionCards = massnahmen.map((massnahme) { List possibleMonths = List.generate( 12, (i) => FlutterI18n.translate( context, 'general.lists.months.${i + 1}', ), ); if (massnahme.timeFrame != null && massnahme.timeFrame.isNotEmpty) { possibleMonths = []; for (IdValueColor month in massnahme.timeFrame) { possibleMonths.add(month.value!); } } final typeMatches = filterType == null || massnahme.actionGroup?.value == filterType; final areaTypeMatches = filterAreaType == null || (massnahme.factsheetAreaType ?.any((aType) => aType.value == filterAreaType) ?? false); final supportMatches = filterSupport == null || (massnahme.fundingPrograms ?.any((aType) => aType.value == filterSupport) ?? false); final searchMatches = searchText == '' || ((massnahme.name.toLowerCase().contains(searchText?.toLowerCase())) ?? false) || ((massnahme.factsheetDefinition .toLowerCase() .contains(searchText?.toLowerCase())) ?? false); final monthMatches = (selectedMonths == [] || selectedMonths == null) || (_hasCommonItems(selectedMonths!, possibleMonths)); setState(() { visible[massnahme.id] = typeMatches && areaTypeMatches && supportMatches && searchMatches && monthMatches; }); return getCard(context, massnahme); }).toList(); return Scaffold( appBar: AmbitoAppbar( links: const ['dashboard', 'massnahmen'], breakpoint: Breakpoint.fromContext(context), ), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Align( alignment: Alignment.centerLeft, child: BreadCrumb( items: [ BreadCrumbItem( content: TextButton( onPressed: () { Get.offAndToNamed('/'); }, child: const Text('Start'), ), ), BreadCrumbItem( content: const Text( 'Maßnahmen', ), ), ], divider: const Icon(Icons.chevron_right), ), ), getSpacer(), _buildSearchBar(), getSpacer(), getSpacer(), Expanded( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(width: 300, child: _buildFilter(context)), const SizedBox(width: 16), Expanded( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: actionCards, ), ), ), ], ), ), getSpacer(), ], ), ), ); } Widget getSpacer() { return const SizedBox( height: 8, ); } Widget _buildSearchBar() { return Center( child: SearchBar( controller: _searchController, focusNode: _searchFocusNode, elevation: WidgetStateProperty.all(0.0), backgroundColor: WidgetStateProperty.all(Colors.white), side: WidgetStateProperty.all(const BorderSide(color: Colors.black)), leading: const Icon(Icons.search), trailing: [ if (_searchFocusNode.hasFocus && _searchController.text.isNotEmpty) IconButton( onPressed: () { _searchController.clear(); _searchFocusNode.unfocus(); _onSearchTextChanged(''); }, icon: const Icon(Icons.clear, color: Colors.grey, size: 20), ), ], onChanged: _onSearchTextChanged, ), ); } Widget _buildFilter(BuildContext context) { return FormWidget( type: FormWidgetType.vertical, fields: [ FieldTitle(text: 'Filter'), ItemFilterRepository().getDropDown('areaType', 'Ort der Maßnahme'), ItemFilterRepository().getDropDown('group', 'Art der Maßnahme'), ItemFilterRepository().getDropDown('fundingProgram', 'Förderprogramm'), ItemFilterRepository().getDropDown('month', 'Beginn der Maßnahme'), ], ); } Widget getCard(BuildContext context, Measure massnahme) { final image = massnahme.getThumbnail(); final AmbitoTheme theme = getTheme(context); return Visibility( visible: visible[massnahme.id] ?? false, child: InkWell( onHover: (hovered) {}, onTap: () async { Get.toNamed('/massnahme/${massnahme.id}'); }, highlightColor: Colors.transparent, splashColor: Colors.transparent, focusColor: Colors.transparent, hoverColor: Colors.transparent, child: Padding( padding: const EdgeInsets.only(bottom: 40), child: SizedBox( width: 800, child: Card( elevation: 0, color: theme.currentColorScheme.outline.withOpacity(.1), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.circular(8.0), child: (image != null) ? image : Container( width: 300, height: 150, decoration: BoxDecoration( color: theme.currentColorScheme.primary .withOpacity(.1), image: const DecorationImage( image: AssetImage('assets/images/logo_trans.png'), fit: BoxFit.fitHeight, ), ), ), ), Expanded( child: Padding( padding: EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextHighlight( text: massnahme.name!, words: words, textStyle: theme.textTheme.headlineSmall, ), const SizedBox(height: 8), TextHighlight( text: massnahme.factsheetDefinition ?? '', words: words, textStyle: theme.textTheme.bodyLarge, ), ], ), ), ), ], ), ), ), ), ), ); } bool _hasCommonItems(List listOne, List listTwo) { return listOne.toSet().intersection(listTwo.toSet()).isNotEmpty; } }