From 646289b918febd891cc1456102974afad8730537 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Fri, 10 Nov 2023 16:26:16 +0100 Subject: [PATCH] places in AdminDatagrid --- lib/models/EventModel.dart | 2 +- lib/models/PlaceModel.dart | 57 ++++++++++++- lib/models/UserInfoModel.dart | 4 +- lib/pages/AdministrationPage.dart | 131 +++++++++++++++++++++++++++++- lib/pages/EventPage.dart | 2 +- lib/pages/MapPage.dart | 65 +++++++++------ lib/services/DataGridHelper.dart | 22 +++++ lib/services/DataService.dart | 55 +++++++------ lib/services/MapIconService.dart | 24 ++++++ 9 files changed, 303 insertions(+), 59 deletions(-) create mode 100644 lib/services/MapIconService.dart diff --git a/lib/models/EventModel.dart b/lib/models/EventModel.dart index 3fe8f33c..2863161d 100644 --- a/lib/models/EventModel.dart +++ b/lib/models/EventModel.dart @@ -161,7 +161,7 @@ class EventModel extends IPlutoRowModel { endDateColumn: PlutoCell(value: DateFormat('yyyy-MM-dd').format(endTime)), endTimeColumn: PlutoCell(value: DateFormat('HH:mm').format(endTime)), maxParticipantsColumn: PlutoCell(value: maxParticipants), - placeColumn: PlutoCell(value: place == null ? PlaceModel.WithouPlace : place!.toPlutoSelectString()), + placeColumn: PlutoCell(value: place == null ? PlaceModel.WithouValue : place!.toPlutoSelectString()), splitForMenWomenColumn: PlutoCell(value: splitForMenWomen.toString()), isGroupEventColumn: PlutoCell(value: isGroupEvent.toString()), parentEventColumn: PlutoCell(value: parentEventIds?.map((e) => e.toString()).join(",")??"") diff --git a/lib/models/PlaceModel.dart b/lib/models/PlaceModel.dart index 27d690ba..0bfe415a 100644 --- a/lib/models/PlaceModel.dart +++ b/lib/models/PlaceModel.dart @@ -1,4 +1,9 @@ -class PlaceModel { +import 'package:avapp/services/DataService.dart'; +import 'package:pluto_grid/pluto_grid.dart'; + +import 'PlutoAbstract.dart'; + +class PlaceModel extends IPlutoRowModel { dynamic latLng; int? id; String? title; @@ -6,7 +11,7 @@ class PlaceModel { String? type; bool isHidden = false; - static const String WithouPlace = "---"; + static const String WithouValue = "---"; static const String idColumn = "id"; static const String titleColumn = "title"; @@ -29,6 +34,27 @@ class PlaceModel { ); } + static PlaceModel fromPlutoJson(Map json) { + return PlaceModel( + latLng: json[coordinatesColumn], + id: json[idColumn] == -1 ? null : json[idColumn], + title: json[titleColumn], + description: json[descriptionColumn].isEmpty ? null : json[descriptionColumn], + type: json[typeColumn] == WithouValue ? null : json[typeColumn], + isHidden: json[isHiddenColumn] == "true" ? true : false, + ); + } + + Map toJson() => + { + idColumn: id, + titleColumn: title, + coordinatesColumn: {"latLng" : latLng }, + descriptionColumn: description, + typeColumn: type, + isHiddenColumn: isHidden + }; + PlaceModel({ this.latLng, required this.id, @@ -38,4 +64,31 @@ class PlaceModel { this.isHidden = false}); String toPlutoSelectString() => "$id:$title"; + + @override + Future deleteMethod() async { + await DataService.deletePlace(this); + } + + @override + String toBasicString() { + return title.toString(); + } + + @override + PlutoRow toPlutoRow() { + return PlutoRow(cells: { + idColumn: PlutoCell(value: id), + titleColumn: PlutoCell(value: title), + descriptionColumn: PlutoCell(value: description ?? ""), + coordinatesColumn: PlutoCell(value: latLng), + typeColumn: PlutoCell(value: type ?? WithouValue), + isHiddenColumn: PlutoCell(value: isHidden), + }); + } + + @override + Future updateMethod() async { + await DataService.updatePlace(this); + } } \ No newline at end of file diff --git a/lib/models/UserInfoModel.dart b/lib/models/UserInfoModel.dart index 40f6aa4f..04dbe61a 100644 --- a/lib/models/UserInfoModel.dart +++ b/lib/models/UserInfoModel.dart @@ -98,10 +98,10 @@ class UserInfoModel extends IPlutoRowModel { emailColumn: PlutoCell(value: email), nameColumn: PlutoCell(value: name), surnameColumn: PlutoCell(value: surname), - phoneColumn: PlutoCell(value: phone ?? PlaceModel.WithouPlace), + phoneColumn: PlutoCell(value: phone ?? PlaceModel.WithouValue), roleColumn: PlutoCell(value: role), accommodationColumn: PlutoCell( - value: accommodation ?? PlaceModel.WithouPlace), + value: accommodation ?? PlaceModel.WithouValue), sexColumn: PlutoCell(value: sex), isAdminColumn: PlutoCell(value: isAdmin.toString()), isReceptionAdminColumn: PlutoCell(value: isReceptionAdmin.toString()), diff --git a/lib/pages/AdministrationPage.dart b/lib/pages/AdministrationPage.dart index 0c723fa3..3c6eb818 100644 --- a/lib/pages/AdministrationPage.dart +++ b/lib/pages/AdministrationPage.dart @@ -11,6 +11,7 @@ import 'package:avapp/services/DataGridHelper.dart'; import 'package:avapp/services/DataService.dart'; import 'package:avapp/services/ImportHelper.dart'; import 'package:avapp/services/MailerSendHelper.dart'; +import 'package:avapp/services/MapIconService.dart'; import 'package:avapp/services/ToastHelper.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -33,6 +34,7 @@ class AdministrationPage extends StatefulWidget { class _AdministrationPageState extends State { List places = []; List columns = []; + List mapIcons = []; late SingleTableDataGrid usersDataGrid; @override @@ -50,8 +52,12 @@ class _AdministrationPageState extends State { Future loadData() async { var placesRaws = await DataService.getMapPlaces(); var placesStrings = placesRaws.map((p)=>p.toPlutoSelectString()).toList(); - placesStrings.add(PlaceModel.WithouPlace); + placesStrings.add(PlaceModel.WithouValue); places = placesStrings; + + mapIcons = MapIconHelper.type2Icon.keys.toList(); + mapIcons.add(PlaceModel.WithouValue); + setState(() {}); } @@ -173,7 +179,7 @@ class _AdministrationPageState extends State { ) ]); return DefaultTabController( - length: 5, + length: 6, child: Scaffold( appBar: AppBar( title: const Text("Admin"), @@ -196,6 +202,12 @@ class _AdministrationPageState extends State { Padding(padding: EdgeInsets.all(12), child: Text("Události")) ] ), + Row( + children: [ + Icon(Icons.pin_drop), + Padding(padding: EdgeInsets.all(12), child: Text("Místa")) + ] + ), Row( children: [ Icon(Icons.punch_clock_rounded), @@ -495,6 +507,121 @@ class _AdministrationPageState extends State { width: 300 ), ]).DataGrid(), + SingleTableDataGrid( + DataService.getPlaces, + PlaceModel.fromPlutoJson, + columns: [ + PlutoColumn( + title: "", + field: "delete", + type: PlutoColumnType.text(), + readOnly: true, + enableFilterMenuItem: false, + enableSorting: false, + enableDropToResize: false, + enableColumnDrag: false, + enableContextMenu: false, + cellPadding: EdgeInsets.zero, + width: 100, + renderer: (rendererContext) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + onPressed: () async{ + final id = rendererContext.row.cells[PlaceModel.idColumn]?.value as int?; + if (id == -1){ + rendererContext.stateManager.removeRows([rendererContext.row]); + return; + } + setState(() { + rendererContext.row.setState(rendererContext.row.state == PlutoRowState.none ? PlutoRowState.added : PlutoRowState.none); + }); + }, + icon: const Icon(Icons.delete_forever)), + IconButton( + onPressed: () async{ + var originRow = rendererContext.row; + var newRow = rendererContext.stateManager.getNewRows()[0]; + newRow.cells[PlaceModel.idColumn]?.value = -1; + newRow.cells[PlaceModel.titleColumn]?.value = originRow.cells[PlaceModel.titleColumn]?.value; + newRow.cells[PlaceModel.descriptionColumn]?.value = originRow.cells[PlaceModel.descriptionColumn]?.value; + newRow.cells[PlaceModel.coordinatesColumn]?.value = originRow.cells[PlaceModel.coordinatesColumn]?.value; + newRow.cells[PlaceModel.typeColumn]?.value = originRow.cells[PlaceModel.typeColumn]?.value; + newRow.cells[PlaceModel.isHiddenColumn]?.value = originRow.cells[PlaceModel.isHiddenColumn]?.value; + var currentIndex = rendererContext.stateManager.rows.indexOf(originRow); + rendererContext.stateManager.insertRows(currentIndex+1, [newRow]); + + setState(() { + newRow.setState(PlutoRowState.updated); + }); + }, + icon: const Icon(Icons.add)),] + ); + }), + PlutoColumn( + title: "Id", + field: PlaceModel.idColumn, + type: PlutoColumnType.number(defaultValue: -1), + readOnly: true, + width: 50, + renderer: (rendererContext) => DataGridHelper.idRenderer(rendererContext), + ), + PlutoColumn( + title: "Nadpis", + field: PlaceModel.titleColumn, + type: PlutoColumnType.text(), + width: 300 + ), + PlutoColumn( + title: "Popis", + field: PlaceModel.descriptionColumn, + type: PlutoColumnType.text(), + width: 300 + ), + PlutoColumn( + title: "Ikonka", + field: PlaceModel.typeColumn, + type: PlutoColumnType.select(mapIcons), + applyFormatterInEditing: false, + renderer: (rendererContext) => DataGridHelper.mapIconRenderer(rendererContext, setState), + //formatter: DataGridHelper.GetValueFromFormatted, + ), + PlutoColumn( + title: "Skryté", + field: PlaceModel.isHiddenColumn, + type: PlutoColumnType.select(places), + applyFormatterInEditing: true, + enableEditingMode: false, + width: 100, + renderer: (rendererContext) => DataGridHelper.checkBoxRenderer(rendererContext, setState), + ), + PlutoColumn( + width: 150, + title: "Pozice na mapě", + enableFilterMenuItem: false, + enableContextMenu: false, + enableSorting: false, + field: PlaceModel.coordinatesColumn, + type: PlutoColumnType.text(), + renderer: (rendererContext) { + return ElevatedButton( + onPressed: () async{ + var placeModel = PlaceModel.fromPlutoJson(rendererContext.row.toJson()); + Navigator.pushNamed(context, MapPage.ROUTE, arguments: placeModel).then((value) async { + if(value != null) + { + rendererContext.row.cells[PlaceModel.coordinatesColumn]?.value = value; + setState(() { + rendererContext.row.setState(PlutoRowState.updated); + }); + } + }); + }, + child: const Row(children: [Icon(Icons.edit), Padding(padding: EdgeInsets.all(6), child: Text("Editovat")) ]) + ); + }), + ]).DataGrid(), SingleTableDataGrid( DataService.getExclusiveGroups, ExclusiveGroupModel.fromPlutoJson, diff --git a/lib/pages/EventPage.dart b/lib/pages/EventPage.dart index dc379b5d..3181ec35 100644 --- a/lib/pages/EventPage.dart +++ b/lib/pages/EventPage.dart @@ -133,7 +133,7 @@ class _EventPageState extends State { padding: const EdgeInsets.all(8.0), alignment: Alignment.topRight, child: TextButton( - onPressed: () => Navigator.pushNamed(context, MapPage.ROUTE, arguments: _event!.place!.id).then((value) => loadData(_event!.id!)), + onPressed: () => Navigator.pushNamed(context, MapPage.ROUTE, arguments: _event!.place).then((value) => loadData(_event!.id!)), child: Text("Místo: ${_event?.place?.title??""}", style: normalTextStyle,)) )), Visibility( diff --git a/lib/pages/MapPage.dart b/lib/pages/MapPage.dart index f0a96ff1..668dd26d 100644 --- a/lib/pages/MapPage.dart +++ b/lib/pages/MapPage.dart @@ -1,5 +1,6 @@ import 'package:avapp/config.dart'; import 'package:avapp/services/DataService.dart'; +import 'package:avapp/services/MapIconService.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; @@ -67,6 +68,7 @@ class _MapPageState extends State { final List _selectedMarkers = []; static MarkerWithText? selectedMarker; String PageTitle = config.map_page; + bool isOnlyEditMode = false; /// Used to trigger showing/hiding of popups. final PopupController _popupLayerController = PopupController(); @@ -75,31 +77,27 @@ class _MapPageState extends State { void didChangeDependencies() { super.didChangeDependencies(); - final placeId = ModalRoute.of(context)?.settings.arguments as int?; - loadPlaces(placeId: placeId); - } + //reset static values + selectedMarker = null; - Map type2icon_map = { - "atm": "atm.svg", - "church": "church.svg", - "coffee": "coffee.svg", - "wine": "wine.svg", - "beer": "beer.svg", - "reception": "card.svg", - "food": "food.svg", - "sport": "ball.svg", - "lecture": "speaker.svg", - "workshop": "tools.svg", - "accommodation": "bed.svg", - "group": "conversation.svg", - "cross": "cross.svg", - }; + final placeModel = ModalRoute.of(context)?.settings.arguments as PlaceModel?; + if(placeModel == null || placeModel.latLng == null) { + loadPlaces(placeId: placeModel?.id); + } + else{ + PageTitle = placeModel.title ?? config.map_page; + addPlacesToMap([placeModel]); + runEditPositionMode(_markers.single); + isOnlyEditMode = true; + } + } - Widget type2icon(String placeType) { + Widget type2icon(String? placeType) { SvgPicture? fill; - if (type2icon_map.containsKey(placeType)) { + var iconLink = MapIconHelper.getIconAddress(placeType); + if (iconLink != null) { fill = SvgPicture.asset( - "assets/images/map/${type2icon_map[placeType]!}", + iconLink, colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn), ); } @@ -155,6 +153,10 @@ class _MapPageState extends State { else { places = await DataService.getMapPlaces(); } + addPlacesToMap(places); + } + + void addPlacesToMap(List places) { var mappedMarkers = places .map( (place) => MarkerWithText( @@ -162,7 +164,7 @@ class _MapPageState extends State { point: LatLng(place.latLng['lat'], place.latLng['lng']), width: 60, height: 60, - builder: (_) => type2icon(place.type ?? ""), + builder: (_) => type2icon(place.type), anchorPos: AnchorPos.align(AnchorAlign.top), editAction: runEditPositionMode, ), @@ -237,9 +239,12 @@ class _MapPageState extends State { onPressed: cancelNewPosition, child: const Text("Storno")), const Padding(padding: EdgeInsets.all(16.0)), - ElevatedButton( - onPressed: showAllGroups, - child: const Text("Zobrazit skupinky")), + Visibility( + visible: !isOnlyEditMode, + child: ElevatedButton( + onPressed: showAllGroups, + child: const Text("Zobrazit skupinky")), + ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: saveNewPosition, @@ -272,6 +277,11 @@ class _MapPageState extends State { } Future saveNewPosition() async { + if(isOnlyEditMode) + { + Navigator.pop(context, {"lat": selectedMarker!.point.latitude, "lng": selectedMarker!.point.longitude}); + return; + } await DataService.SaveLocation(selectedMarker!.place.id!, selectedMarker!.point.latitude, selectedMarker!.point.longitude); @@ -288,6 +298,11 @@ class _MapPageState extends State { } void cancelNewPosition() { + if(isOnlyEditMode) + { + Navigator.pop(context); + return; + } setState(() { selectedMarker = null; }); diff --git a/lib/services/DataGridHelper.dart b/lib/services/DataGridHelper.dart index 5d96e598..5c5a949b 100644 --- a/lib/services/DataGridHelper.dart +++ b/lib/services/DataGridHelper.dart @@ -1,4 +1,7 @@ +import 'package:avapp/models/PlaceModel.dart'; +import 'package:avapp/services/MapIconService.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:pluto_grid/pluto_grid.dart'; class DataGridHelper @@ -42,6 +45,25 @@ class DataGridHelper }); }, );} + static Widget mapIconRenderer(rendererContext, void Function(Function() set) setState) { + String? value = rendererContext.cell.value; + + var iconLink = MapIconHelper.getIconAddress(value); + if(iconLink != null) + { + var svgIcon = SizedBox( + width: 20, + height: 20, + child: SvgPicture.asset( + iconLink, + colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn), + ), + ); + return Row(children: [svgIcon, const SizedBox(width: 12,),Text(value!)],); + } + return const Text(PlaceModel.WithouValue); + } + static Widget idRenderer(rendererContext) { var value = rendererContext.cell.value == -1 ? "" : rendererContext.cell.value.toString(); return Text(value); diff --git a/lib/services/DataService.dart b/lib/services/DataService.dart index 44fe6021..227ef8c9 100644 --- a/lib/services/DataService.dart +++ b/lib/services/DataService.dart @@ -251,7 +251,12 @@ class DataService { } static Future> getMapPlaces() async { - var data = await _supabase.from('places').select().eq("is_hidden", false); + var data = await _supabase.from(PlaceModel.placeTable).select().eq(PlaceModel.isHiddenColumn, false); + return List.from(data.map((x) => PlaceModel.fromJson(x))); + } + + static Future> getPlaces() async { + var data = await _supabase.from(PlaceModel.placeTable).select(); return List.from(data.map((x) => PlaceModel.fromJson(x))); } @@ -262,7 +267,29 @@ class DataService { } static Future getPlace(int id) async { - var data = await _supabase.from('places').select().eq("id", id).single(); + var data = await _supabase.from(PlaceModel.placeTable).select().eq("id", id).single(); + return PlaceModel.fromJson(data); + } + + static Future deletePlace(PlaceModel placeModel) async { + ensureIsAdmin(); + ensureCanDeleteCritical(); + await _supabase.from(PlaceModel.placeTable).delete().eq("id", placeModel.id); + } + + static Future updatePlace(PlaceModel placeModel) async + { + ensureIsAdmin(); + var json = placeModel.toJson(); + Map data; + if(placeModel.id!=null) { + data = await _supabase.from(PlaceModel.placeTable).update(json).eq("id", placeModel.id).select().single(); + } + else + { + json.remove(PlaceModel.idColumn); + data = await _supabase.from(PlaceModel.placeTable).insert(json).select().single(); + } return PlaceModel.fromJson(data); } @@ -793,30 +820,6 @@ class DataService { .eq("id", data.id); } - static Future updatePlace(PlaceModel model) async { - ensureIsAdmin(); - if(model.id == null) - { - var data = await _supabase.from(PlaceModel.placeTable).insert({ - PlaceModel.titleColumn: model.title, - PlaceModel.descriptionColumn: model.description, - PlaceModel.isHiddenColumn: model.isHidden, - PlaceModel.coordinatesColumn: {"latLng" : model.latLng}, - PlaceModel.typeColumn: model.type, - }).select().single(); - return PlaceModel.fromJson(data); - } - var data = await _supabase.from(PlaceModel.placeTable).upsert({ - PlaceModel.idColumn: model.id, - PlaceModel.titleColumn: model.title, - PlaceModel.descriptionColumn: model.description, - PlaceModel.isHiddenColumn: model.isHidden, - PlaceModel.coordinatesColumn: {"latLng" : model.latLng}, - PlaceModel.typeColumn: model.type, - }).select().single(); - return PlaceModel.fromJson(data); - } - static Future updateInformation(InformationModel info) async { ensureIsAdmin(); if(info.id == null) diff --git a/lib/services/MapIconService.dart b/lib/services/MapIconService.dart new file mode 100644 index 00000000..ded4d694 --- /dev/null +++ b/lib/services/MapIconService.dart @@ -0,0 +1,24 @@ +class MapIconHelper { + static Map type2Icon = { + "atm": "atm.svg", + "church": "church.svg", + "coffee": "coffee.svg", + "wine": "wine.svg", + "beer": "beer.svg", + "reception": "card.svg", + "food": "food.svg", + "sport": "ball.svg", + "lecture": "speaker.svg", + "workshop": "tools.svg", + "accommodation": "bed.svg", + "group": "conversation.svg", + "cross": "cross.svg", + }; + + static String? getIconAddress(String? type) + { + if (type2Icon.containsKey(type)) { + return "assets/images/map/${type2Icon[type]!}"; + } + } +} \ No newline at end of file