From cb7baa446a6a98ea58eded8acb7d1dfb54f7a86f Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:06:30 +0100 Subject: [PATCH 001/159] packages up --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index dd9f5388..e924ba4f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,7 +32,7 @@ dependencies: sdk: flutter flutter: sdk: flutter - flutter_svg: ^2.0.15 + flutter_svg: ^2.0.16 fluttertoast: ^8.2.8 # The following adds the Cupertino Icons font to your application. @@ -58,7 +58,7 @@ dependencies: url_strategy: ^0.3.0 desktop_drop: ^0.5.0 csv: ^6.0.0 - sembast: ^3.7.5 + sembast: ^3.8.0 sembast_web: ^2.4.0+4 path_provider: ^2.1.5 select_dialog: ^2.0.1 From b27e214fac411445b00d593a70172502f33fd746 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 03:06:50 +0100 Subject: [PATCH 002/159] form page --- lib/AppRouter.dart | 3 + lib/AppRouter.gr.dart | 427 ++++++++++++++----------- lib/dataModelsEshop/ItemModel.dart | 56 ++++ lib/dataModelsEshop/ItemTypeModel.dart | 61 ++++ lib/dataModelsEshop/TbEshop.dart | 78 +++++ lib/dataServices/DbEshop.dart | 35 ++ lib/pages/FormPage.dart | 185 +++++++++++ lib/services/FormHelper.dart | 49 ++- lib/services/UuidConverter.dart | 61 ++++ 9 files changed, 760 insertions(+), 195 deletions(-) create mode 100644 lib/dataModelsEshop/ItemModel.dart create mode 100644 lib/dataModelsEshop/ItemTypeModel.dart create mode 100644 lib/dataModelsEshop/TbEshop.dart create mode 100644 lib/dataServices/DbEshop.dart create mode 100644 lib/pages/FormPage.dart create mode 100644 lib/services/UuidConverter.dart diff --git a/lib/AppRouter.dart b/lib/AppRouter.dart index 7031b38e..387b0137 100644 --- a/lib/AppRouter.dart +++ b/lib/AppRouter.dart @@ -5,6 +5,7 @@ import 'package:fstapp/pages/AdminDashboardPage.dart'; import 'package:fstapp/pages/CheckPage.dart'; import 'package:fstapp/pages/EventEditPage.dart'; import 'package:fstapp/pages/EventPage.dart'; +import 'package:fstapp/pages/FormPage.dart'; import 'package:fstapp/pages/HtmlEditorPage.dart'; import 'package:fstapp/pages/InfoPage.dart'; import 'package:fstapp/pages/InstallPage.dart'; @@ -42,6 +43,8 @@ class AppRouter extends RootStackRouter { AutoRoute(page: SettingsRoute.page, path: sl(SettingsPage.ROUTE)), AutoRoute(page: InstallRoute.page, path: sl(InstallPage.ROUTE)), AutoRoute(page: AdminDashboardRoute.page, path: sl(AdminDashboardPage.ROUTE)), + AutoRoute(page: AdminDashboardRoute.page, path: sl(AdminDashboardPage.ROUTE)), + AutoRoute(page: FormRoute.page, path: "/${FormPage.ROUTE}/:id"), AutoRoute(page: CheckRoute.page, path: "/:{$LINK}/${CheckPage.ROUTE}/:id"), AutoRoute(page: NewsFormRoute.page, path: "/:{$LINK}/${NewsFormPage.ROUTE}"), AutoRoute(page: HtmlEditorRoute.page, path: "/:{$LINK}/${HtmlEditorPage.ROUTE}"), diff --git a/lib/AppRouter.gr.dart b/lib/AppRouter.gr.dart index 824df641..4344ca8f 100644 --- a/lib/AppRouter.gr.dart +++ b/lib/AppRouter.gr.dart @@ -8,10 +8,10 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i25; -import 'package:flutter/foundation.dart' as _i27; -import 'package:flutter/material.dart' as _i26; -import 'package:fstapp/dataModels/PlaceModel.dart' as _i28; +import 'package:auto_route/auto_route.dart' as _i26; +import 'package:flutter/foundation.dart' as _i28; +import 'package:flutter/material.dart' as _i27; +import 'package:fstapp/dataModels/PlaceModel.dart' as _i29; import 'package:fstapp/pages/AdminDashboardPage.dart' deferred as _i1; import 'package:fstapp/pages/AdministrationOccasion/AdminPage.dart' deferred as _i2; @@ -19,29 +19,30 @@ import 'package:fstapp/pages/CheckPage.dart' deferred as _i3; import 'package:fstapp/pages/EventEditPage.dart' deferred as _i4; import 'package:fstapp/pages/EventPage.dart' deferred as _i5; import 'package:fstapp/pages/ForgotPasswordPage.dart' deferred as _i6; -import 'package:fstapp/pages/GamePage.dart' deferred as _i7; -import 'package:fstapp/pages/HtmlEditorPage.dart' deferred as _i8; -import 'package:fstapp/pages/InfoPage.dart' deferred as _i9; -import 'package:fstapp/pages/InstallPage.dart' deferred as _i10; -import 'package:fstapp/pages/LoginPage.dart' deferred as _i11; -import 'package:fstapp/pages/MapPage.dart' deferred as _i12; -import 'package:fstapp/pages/MySchedulePage.dart' deferred as _i13; -import 'package:fstapp/pages/NewsFormPage.dart' deferred as _i14; -import 'package:fstapp/pages/NewsPage.dart' deferred as _i15; -import 'package:fstapp/pages/OccasionHomePage.dart' deferred as _i16; -import 'package:fstapp/pages/ScheduleNavigationScreen.dart' deferred as _i18; -import 'package:fstapp/pages/SchedulePage.dart' deferred as _i19; -import 'package:fstapp/pages/SettingsPage.dart' deferred as _i20; -import 'package:fstapp/pages/SignupPage.dart' deferred as _i21; -import 'package:fstapp/pages/SignupPasswordPage.dart' deferred as _i17; -import 'package:fstapp/pages/SongPage.dart' deferred as _i22; -import 'package:fstapp/pages/TimetablePage.dart' deferred as _i23; -import 'package:fstapp/pages/UserPage.dart' deferred as _i24; +import 'package:fstapp/pages/FormPage.dart' deferred as _i7; +import 'package:fstapp/pages/GamePage.dart' deferred as _i8; +import 'package:fstapp/pages/HtmlEditorPage.dart' deferred as _i9; +import 'package:fstapp/pages/InfoPage.dart' deferred as _i10; +import 'package:fstapp/pages/InstallPage.dart' deferred as _i11; +import 'package:fstapp/pages/LoginPage.dart' deferred as _i12; +import 'package:fstapp/pages/MapPage.dart' deferred as _i13; +import 'package:fstapp/pages/MySchedulePage.dart' deferred as _i14; +import 'package:fstapp/pages/NewsFormPage.dart' deferred as _i15; +import 'package:fstapp/pages/NewsPage.dart' deferred as _i16; +import 'package:fstapp/pages/OccasionHomePage.dart' deferred as _i17; +import 'package:fstapp/pages/ScheduleNavigationScreen.dart' deferred as _i19; +import 'package:fstapp/pages/SchedulePage.dart' deferred as _i20; +import 'package:fstapp/pages/SettingsPage.dart' deferred as _i21; +import 'package:fstapp/pages/SignupPage.dart' deferred as _i22; +import 'package:fstapp/pages/SignupPasswordPage.dart' deferred as _i18; +import 'package:fstapp/pages/SongPage.dart' deferred as _i23; +import 'package:fstapp/pages/TimetablePage.dart' deferred as _i24; +import 'package:fstapp/pages/UserPage.dart' deferred as _i25; /// generated route for /// [_i1.AdminDashboardPage] -class AdminDashboardRoute extends _i25.PageRouteInfo { - const AdminDashboardRoute({List<_i25.PageRouteInfo>? children}) +class AdminDashboardRoute extends _i26.PageRouteInfo { + const AdminDashboardRoute({List<_i26.PageRouteInfo>? children}) : super( AdminDashboardRoute.name, initialChildren: children, @@ -49,10 +50,10 @@ class AdminDashboardRoute extends _i25.PageRouteInfo { static const String name = 'AdminDashboardRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( + return _i26.DeferredWidget( _i1.loadLibrary, () => _i1.AdminDashboardPage(), ); @@ -62,8 +63,8 @@ class AdminDashboardRoute extends _i25.PageRouteInfo { /// generated route for /// [_i2.AdminPage] -class AdminRoute extends _i25.PageRouteInfo { - const AdminRoute({List<_i25.PageRouteInfo>? children}) +class AdminRoute extends _i26.PageRouteInfo { + const AdminRoute({List<_i26.PageRouteInfo>? children}) : super( AdminRoute.name, initialChildren: children, @@ -71,10 +72,10 @@ class AdminRoute extends _i25.PageRouteInfo { static const String name = 'AdminRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( + return _i26.DeferredWidget( _i2.loadLibrary, () => _i2.AdminPage(), ); @@ -84,11 +85,11 @@ class AdminRoute extends _i25.PageRouteInfo { /// generated route for /// [_i3.CheckPage] -class CheckRoute extends _i25.PageRouteInfo { +class CheckRoute extends _i26.PageRouteInfo { CheckRoute({ required int id, - _i26.Key? key, - List<_i25.PageRouteInfo>? children, + _i27.Key? key, + List<_i26.PageRouteInfo>? children, }) : super( CheckRoute.name, args: CheckRouteArgs( @@ -101,13 +102,13 @@ class CheckRoute extends _i25.PageRouteInfo { static const String name = 'CheckRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => CheckRouteArgs(id: pathParams.getInt('id'))); - return _i25.DeferredWidget( + return _i26.DeferredWidget( _i3.loadLibrary, () => _i3.CheckPage( id: args.id, @@ -126,7 +127,7 @@ class CheckRouteArgs { final int id; - final _i26.Key? key; + final _i27.Key? key; @override String toString() { @@ -136,11 +137,11 @@ class CheckRouteArgs { /// generated route for /// [_i4.EventEditPage] -class EventEditRoute extends _i25.PageRouteInfo { +class EventEditRoute extends _i26.PageRouteInfo { EventEditRoute({ - _i26.Key? key, + _i27.Key? key, int? id, - List<_i25.PageRouteInfo>? children, + List<_i26.PageRouteInfo>? children, }) : super( EventEditRoute.name, args: EventEditRouteArgs( @@ -153,13 +154,13 @@ class EventEditRoute extends _i25.PageRouteInfo { static const String name = 'EventEditRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => EventEditRouteArgs(id: pathParams.optInt('id'))); - return _i25.DeferredWidget( + return _i26.DeferredWidget( _i4.loadLibrary, () => _i4.EventEditPage( key: args.key, @@ -176,7 +177,7 @@ class EventEditRouteArgs { this.id, }); - final _i26.Key? key; + final _i27.Key? key; final int? id; @@ -188,11 +189,11 @@ class EventEditRouteArgs { /// generated route for /// [_i5.EventPage] -class EventRoute extends _i25.PageRouteInfo { +class EventRoute extends _i26.PageRouteInfo { EventRoute({ int? id, - _i26.Key? key, - List<_i25.PageRouteInfo>? children, + _i27.Key? key, + List<_i26.PageRouteInfo>? children, }) : super( EventRoute.name, args: EventRouteArgs( @@ -205,13 +206,13 @@ class EventRoute extends _i25.PageRouteInfo { static const String name = 'EventRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => EventRouteArgs(id: pathParams.optInt('id'))); - return _i25.DeferredWidget( + return _i26.DeferredWidget( _i5.loadLibrary, () => _i5.EventPage( id: args.id, @@ -230,7 +231,7 @@ class EventRouteArgs { final int? id; - final _i26.Key? key; + final _i27.Key? key; @override String toString() { @@ -240,8 +241,8 @@ class EventRouteArgs { /// generated route for /// [_i6.ForgotPasswordPage] -class ForgotPasswordRoute extends _i25.PageRouteInfo { - const ForgotPasswordRoute({List<_i25.PageRouteInfo>? children}) +class ForgotPasswordRoute extends _i26.PageRouteInfo { + const ForgotPasswordRoute({List<_i26.PageRouteInfo>? children}) : super( ForgotPasswordRoute.name, initialChildren: children, @@ -249,10 +250,10 @@ class ForgotPasswordRoute extends _i25.PageRouteInfo { static const String name = 'ForgotPasswordRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( + return _i26.DeferredWidget( _i6.loadLibrary, () => _i6.ForgotPasswordPage(), ); @@ -261,11 +262,49 @@ class ForgotPasswordRoute extends _i25.PageRouteInfo { } /// generated route for -/// [_i7.GamePage] -class GameRoute extends _i25.PageRouteInfo { +/// [_i7.FormPage] +class FormRoute extends _i26.PageRouteInfo { + FormRoute({ + _i27.Key? key, + List<_i26.PageRouteInfo>? children, + }) : super( + FormRoute.name, + args: FormRouteArgs(key: key), + initialChildren: children, + ); + + static const String name = 'FormRoute'; + + static _i26.PageInfo page = _i26.PageInfo( + name, + builder: (data) { + final args = + data.argsAs(orElse: () => const FormRouteArgs()); + return _i26.DeferredWidget( + _i7.loadLibrary, + () => _i7.FormPage(key: args.key), + ); + }, + ); +} + +class FormRouteArgs { + const FormRouteArgs({this.key}); + + final _i27.Key? key; + + @override + String toString() { + return 'FormRouteArgs{key: $key}'; + } +} + +/// generated route for +/// [_i8.GamePage] +class GameRoute extends _i26.PageRouteInfo { GameRoute({ - _i26.Key? key, - List<_i25.PageRouteInfo>? children, + _i27.Key? key, + List<_i26.PageRouteInfo>? children, }) : super( GameRoute.name, args: GameRouteArgs(key: key), @@ -274,14 +313,14 @@ class GameRoute extends _i25.PageRouteInfo { static const String name = 'GameRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const GameRouteArgs()); - return _i25.DeferredWidget( - _i7.loadLibrary, - () => _i7.GamePage(key: args.key), + return _i26.DeferredWidget( + _i8.loadLibrary, + () => _i8.GamePage(key: args.key), ); }, ); @@ -290,7 +329,7 @@ class GameRoute extends _i25.PageRouteInfo { class GameRouteArgs { const GameRouteArgs({this.key}); - final _i26.Key? key; + final _i27.Key? key; @override String toString() { @@ -299,12 +338,12 @@ class GameRouteArgs { } /// generated route for -/// [_i8.HtmlEditorPage] -class HtmlEditorRoute extends _i25.PageRouteInfo { +/// [_i9.HtmlEditorPage] +class HtmlEditorRoute extends _i26.PageRouteInfo { HtmlEditorRoute({ Map? content, - _i26.Key? key, - List<_i25.PageRouteInfo>? children, + _i27.Key? key, + List<_i26.PageRouteInfo>? children, }) : super( HtmlEditorRoute.name, args: HtmlEditorRouteArgs( @@ -316,14 +355,14 @@ class HtmlEditorRoute extends _i25.PageRouteInfo { static const String name = 'HtmlEditorRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const HtmlEditorRouteArgs()); - return _i25.DeferredWidget( - _i8.loadLibrary, - () => _i8.HtmlEditorPage( + return _i26.DeferredWidget( + _i9.loadLibrary, + () => _i9.HtmlEditorPage( content: args.content, key: args.key, ), @@ -340,7 +379,7 @@ class HtmlEditorRouteArgs { final Map? content; - final _i26.Key? key; + final _i27.Key? key; @override String toString() { @@ -349,12 +388,12 @@ class HtmlEditorRouteArgs { } /// generated route for -/// [_i9.InfoPage] -class InfoRoute extends _i25.PageRouteInfo { +/// [_i10.InfoPage] +class InfoRoute extends _i26.PageRouteInfo { InfoRoute({ int? id, - _i27.Key? key, - List<_i25.PageRouteInfo>? children, + _i28.Key? key, + List<_i26.PageRouteInfo>? children, }) : super( InfoRoute.name, args: InfoRouteArgs( @@ -367,15 +406,15 @@ class InfoRoute extends _i25.PageRouteInfo { static const String name = 'InfoRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => InfoRouteArgs(id: pathParams.optInt('id'))); - return _i25.DeferredWidget( - _i9.loadLibrary, - () => _i9.InfoPage( + return _i26.DeferredWidget( + _i10.loadLibrary, + () => _i10.InfoPage( id: args.id, key: args.key, ), @@ -392,7 +431,7 @@ class InfoRouteArgs { final int? id; - final _i27.Key? key; + final _i28.Key? key; @override String toString() { @@ -401,9 +440,9 @@ class InfoRouteArgs { } /// generated route for -/// [_i10.InstallPage] -class InstallRoute extends _i25.PageRouteInfo { - const InstallRoute({List<_i25.PageRouteInfo>? children}) +/// [_i11.InstallPage] +class InstallRoute extends _i26.PageRouteInfo { + const InstallRoute({List<_i26.PageRouteInfo>? children}) : super( InstallRoute.name, initialChildren: children, @@ -411,21 +450,21 @@ class InstallRoute extends _i25.PageRouteInfo { static const String name = 'InstallRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i10.loadLibrary, - () => _i10.InstallPage(), + return _i26.DeferredWidget( + _i11.loadLibrary, + () => _i11.InstallPage(), ); }, ); } /// generated route for -/// [_i11.LoginPage] -class LoginRoute extends _i25.PageRouteInfo { - const LoginRoute({List<_i25.PageRouteInfo>? children}) +/// [_i12.LoginPage] +class LoginRoute extends _i26.PageRouteInfo { + const LoginRoute({List<_i26.PageRouteInfo>? children}) : super( LoginRoute.name, initialChildren: children, @@ -433,25 +472,25 @@ class LoginRoute extends _i25.PageRouteInfo { static const String name = 'LoginRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i11.loadLibrary, - () => _i11.LoginPage(), + return _i26.DeferredWidget( + _i12.loadLibrary, + () => _i12.LoginPage(), ); }, ); } /// generated route for -/// [_i12.MapPage] -class MapRoute extends _i25.PageRouteInfo { +/// [_i13.MapPage] +class MapRoute extends _i26.PageRouteInfo { MapRoute({ int? id, - _i28.PlaceModel? place, - _i26.Key? key, - List<_i25.PageRouteInfo>? children, + _i29.PlaceModel? place, + _i27.Key? key, + List<_i26.PageRouteInfo>? children, }) : super( MapRoute.name, args: MapRouteArgs( @@ -465,15 +504,15 @@ class MapRoute extends _i25.PageRouteInfo { static const String name = 'MapRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => MapRouteArgs(id: pathParams.optInt('id'))); - return _i25.DeferredWidget( - _i12.loadLibrary, - () => _i12.MapPage( + return _i26.DeferredWidget( + _i13.loadLibrary, + () => _i13.MapPage( id: args.id, place: args.place, key: args.key, @@ -492,9 +531,9 @@ class MapRouteArgs { final int? id; - final _i28.PlaceModel? place; + final _i29.PlaceModel? place; - final _i26.Key? key; + final _i27.Key? key; @override String toString() { @@ -503,9 +542,9 @@ class MapRouteArgs { } /// generated route for -/// [_i13.MySchedulePage] -class MyScheduleRoute extends _i25.PageRouteInfo { - const MyScheduleRoute({List<_i25.PageRouteInfo>? children}) +/// [_i14.MySchedulePage] +class MyScheduleRoute extends _i26.PageRouteInfo { + const MyScheduleRoute({List<_i26.PageRouteInfo>? children}) : super( MyScheduleRoute.name, initialChildren: children, @@ -513,21 +552,21 @@ class MyScheduleRoute extends _i25.PageRouteInfo { static const String name = 'MyScheduleRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i13.loadLibrary, - () => _i13.MySchedulePage(), + return _i26.DeferredWidget( + _i14.loadLibrary, + () => _i14.MySchedulePage(), ); }, ); } /// generated route for -/// [_i14.NewsFormPage] -class NewsFormRoute extends _i25.PageRouteInfo { - const NewsFormRoute({List<_i25.PageRouteInfo>? children}) +/// [_i15.NewsFormPage] +class NewsFormRoute extends _i26.PageRouteInfo { + const NewsFormRoute({List<_i26.PageRouteInfo>? children}) : super( NewsFormRoute.name, initialChildren: children, @@ -535,24 +574,24 @@ class NewsFormRoute extends _i25.PageRouteInfo { static const String name = 'NewsFormRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i14.loadLibrary, - () => _i14.NewsFormPage(), + return _i26.DeferredWidget( + _i15.loadLibrary, + () => _i15.NewsFormPage(), ); }, ); } /// generated route for -/// [_i15.NewsPage] -class NewsRoute extends _i25.PageRouteInfo { +/// [_i16.NewsPage] +class NewsRoute extends _i26.PageRouteInfo { NewsRoute({ - _i26.Key? key, - _i26.VoidCallback? onSetAsRead, - List<_i25.PageRouteInfo>? children, + _i27.Key? key, + _i27.VoidCallback? onSetAsRead, + List<_i26.PageRouteInfo>? children, }) : super( NewsRoute.name, args: NewsRouteArgs( @@ -564,14 +603,14 @@ class NewsRoute extends _i25.PageRouteInfo { static const String name = 'NewsRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const NewsRouteArgs()); - return _i25.DeferredWidget( - _i15.loadLibrary, - () => _i15.NewsPage( + return _i26.DeferredWidget( + _i16.loadLibrary, + () => _i16.NewsPage( key: args.key, onSetAsRead: args.onSetAsRead, ), @@ -586,9 +625,9 @@ class NewsRouteArgs { this.onSetAsRead, }); - final _i26.Key? key; + final _i27.Key? key; - final _i26.VoidCallback? onSetAsRead; + final _i27.VoidCallback? onSetAsRead; @override String toString() { @@ -597,9 +636,9 @@ class NewsRouteArgs { } /// generated route for -/// [_i16.OccasionHomePage] -class OccasionHomeRoute extends _i25.PageRouteInfo { - const OccasionHomeRoute({List<_i25.PageRouteInfo>? children}) +/// [_i17.OccasionHomePage] +class OccasionHomeRoute extends _i26.PageRouteInfo { + const OccasionHomeRoute({List<_i26.PageRouteInfo>? children}) : super( OccasionHomeRoute.name, initialChildren: children, @@ -607,21 +646,21 @@ class OccasionHomeRoute extends _i25.PageRouteInfo { static const String name = 'OccasionHomeRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i16.loadLibrary, - () => _i16.OccasionHomePage(), + return _i26.DeferredWidget( + _i17.loadLibrary, + () => _i17.OccasionHomePage(), ); }, ); } /// generated route for -/// [_i17.ResetPasswordPage] -class ResetPasswordRoute extends _i25.PageRouteInfo { - const ResetPasswordRoute({List<_i25.PageRouteInfo>? children}) +/// [_i18.ResetPasswordPage] +class ResetPasswordRoute extends _i26.PageRouteInfo { + const ResetPasswordRoute({List<_i26.PageRouteInfo>? children}) : super( ResetPasswordRoute.name, initialChildren: children, @@ -629,21 +668,21 @@ class ResetPasswordRoute extends _i25.PageRouteInfo { static const String name = 'ResetPasswordRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i17.loadLibrary, - () => _i17.ResetPasswordPage(), + return _i26.DeferredWidget( + _i18.loadLibrary, + () => _i18.ResetPasswordPage(), ); }, ); } /// generated route for -/// [_i18.ScheduleNavigationPage] -class ScheduleNavigationRoute extends _i25.PageRouteInfo { - const ScheduleNavigationRoute({List<_i25.PageRouteInfo>? children}) +/// [_i19.ScheduleNavigationPage] +class ScheduleNavigationRoute extends _i26.PageRouteInfo { + const ScheduleNavigationRoute({List<_i26.PageRouteInfo>? children}) : super( ScheduleNavigationRoute.name, initialChildren: children, @@ -651,21 +690,21 @@ class ScheduleNavigationRoute extends _i25.PageRouteInfo { static const String name = 'ScheduleNavigationRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i18.loadLibrary, - () => _i18.ScheduleNavigationPage(), + return _i26.DeferredWidget( + _i19.loadLibrary, + () => _i19.ScheduleNavigationPage(), ); }, ); } /// generated route for -/// [_i19.SchedulePage] -class ScheduleRoute extends _i25.PageRouteInfo { - const ScheduleRoute({List<_i25.PageRouteInfo>? children}) +/// [_i20.SchedulePage] +class ScheduleRoute extends _i26.PageRouteInfo { + const ScheduleRoute({List<_i26.PageRouteInfo>? children}) : super( ScheduleRoute.name, initialChildren: children, @@ -673,21 +712,21 @@ class ScheduleRoute extends _i25.PageRouteInfo { static const String name = 'ScheduleRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i19.loadLibrary, - () => _i19.SchedulePage(), + return _i26.DeferredWidget( + _i20.loadLibrary, + () => _i20.SchedulePage(), ); }, ); } /// generated route for -/// [_i20.SettingsPage] -class SettingsRoute extends _i25.PageRouteInfo { - const SettingsRoute({List<_i25.PageRouteInfo>? children}) +/// [_i21.SettingsPage] +class SettingsRoute extends _i26.PageRouteInfo { + const SettingsRoute({List<_i26.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -695,21 +734,21 @@ class SettingsRoute extends _i25.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i20.loadLibrary, - () => _i20.SettingsPage(), + return _i26.DeferredWidget( + _i21.loadLibrary, + () => _i21.SettingsPage(), ); }, ); } /// generated route for -/// [_i21.SignupPage] -class SignupRoute extends _i25.PageRouteInfo { - const SignupRoute({List<_i25.PageRouteInfo>? children}) +/// [_i22.SignupPage] +class SignupRoute extends _i26.PageRouteInfo { + const SignupRoute({List<_i26.PageRouteInfo>? children}) : super( SignupRoute.name, initialChildren: children, @@ -717,23 +756,23 @@ class SignupRoute extends _i25.PageRouteInfo { static const String name = 'SignupRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i21.loadLibrary, - () => _i21.SignupPage(), + return _i26.DeferredWidget( + _i22.loadLibrary, + () => _i22.SignupPage(), ); }, ); } /// generated route for -/// [_i22.SongbookPage] -class SongbookRoute extends _i25.PageRouteInfo { +/// [_i23.SongbookPage] +class SongbookRoute extends _i26.PageRouteInfo { SongbookRoute({ - _i26.Key? key, - List<_i25.PageRouteInfo>? children, + _i27.Key? key, + List<_i26.PageRouteInfo>? children, }) : super( SongbookRoute.name, args: SongbookRouteArgs(key: key), @@ -742,14 +781,14 @@ class SongbookRoute extends _i25.PageRouteInfo { static const String name = 'SongbookRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const SongbookRouteArgs()); - return _i25.DeferredWidget( - _i22.loadLibrary, - () => _i22.SongbookPage(key: args.key), + return _i26.DeferredWidget( + _i23.loadLibrary, + () => _i23.SongbookPage(key: args.key), ); }, ); @@ -758,7 +797,7 @@ class SongbookRoute extends _i25.PageRouteInfo { class SongbookRouteArgs { const SongbookRouteArgs({this.key}); - final _i26.Key? key; + final _i27.Key? key; @override String toString() { @@ -767,9 +806,9 @@ class SongbookRouteArgs { } /// generated route for -/// [_i23.TimetablePage] -class TimetableRoute extends _i25.PageRouteInfo { - const TimetableRoute({List<_i25.PageRouteInfo>? children}) +/// [_i24.TimetablePage] +class TimetableRoute extends _i26.PageRouteInfo { + const TimetableRoute({List<_i26.PageRouteInfo>? children}) : super( TimetableRoute.name, initialChildren: children, @@ -777,21 +816,21 @@ class TimetableRoute extends _i25.PageRouteInfo { static const String name = 'TimetableRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i23.loadLibrary, - () => _i23.TimetablePage(), + return _i26.DeferredWidget( + _i24.loadLibrary, + () => _i24.TimetablePage(), ); }, ); } /// generated route for -/// [_i24.UserPage] -class UserRoute extends _i25.PageRouteInfo { - const UserRoute({List<_i25.PageRouteInfo>? children}) +/// [_i25.UserPage] +class UserRoute extends _i26.PageRouteInfo { + const UserRoute({List<_i26.PageRouteInfo>? children}) : super( UserRoute.name, initialChildren: children, @@ -799,12 +838,12 @@ class UserRoute extends _i25.PageRouteInfo { static const String name = 'UserRoute'; - static _i25.PageInfo page = _i25.PageInfo( + static _i26.PageInfo page = _i26.PageInfo( name, builder: (data) { - return _i25.DeferredWidget( - _i24.loadLibrary, - () => _i24.UserPage(), + return _i26.DeferredWidget( + _i25.loadLibrary, + () => _i25.UserPage(), ); }, ); diff --git a/lib/dataModelsEshop/ItemModel.dart b/lib/dataModelsEshop/ItemModel.dart new file mode 100644 index 00000000..8ad7ba0c --- /dev/null +++ b/lib/dataModelsEshop/ItemModel.dart @@ -0,0 +1,56 @@ +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; + +class ItemModel { + int? id; + DateTime? createdAt; + DateTime? updatedAt; + String? title; + String? description; + double? price; + Map? data; + int? itemType; + int? occasion; + + static const String foodType = "food"; + static const String taxiType = "taxi"; + + factory ItemModel.fromJson(Map json) { + return ItemModel( + id: json[TbEshop.items.id], + createdAt: json[TbEshop.items.created_at] != null ? DateTime.parse(json[TbEshop.items.created_at]) : null, + updatedAt: json[TbEshop.items.updated_at] != null ? DateTime.parse(json[TbEshop.items.updated_at]) : null, + title: json[TbEshop.items.title], + description: json[TbEshop.items.description], + price: json[TbEshop.items.price] != null ? double.tryParse(json[TbEshop.items.price].toString()) : null, + data: json[TbEshop.items.data], + itemType: json[TbEshop.items.item_type], + occasion: json[TbEshop.items.occasion], + ); + } + + Map toJson() => { + TbEshop.items.id: id, + TbEshop.items.created_at: createdAt?.toIso8601String(), + TbEshop.items.updated_at: updatedAt?.toIso8601String(), + TbEshop.items.title: title, + TbEshop.items.description: description, + TbEshop.items.price: price, + TbEshop.items.data: data, + TbEshop.items.item_type: itemType, + TbEshop.items.occasion: occasion, + }; + + String toBasicString() => title ?? id.toString(); + + ItemModel({ + this.id, + this.createdAt, + this.updatedAt, + this.title, + this.description, + this.price, + this.data, + this.itemType, + this.occasion, + }); +} diff --git a/lib/dataModelsEshop/ItemTypeModel.dart b/lib/dataModelsEshop/ItemTypeModel.dart new file mode 100644 index 00000000..bb5fa12a --- /dev/null +++ b/lib/dataModelsEshop/ItemTypeModel.dart @@ -0,0 +1,61 @@ +import 'package:fstapp/dataModelsEshop/ItemModel.dart'; +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; + +class ItemTypeModel { + int? id; + DateTime? createdAt; + DateTime? updatedAt; + String? title; + String? description; + String? type; + Map? data; + int? occasion; + List? items; // Embedded list of items + + factory ItemTypeModel.fromJson(Map json) { + return ItemTypeModel( + id: json[TbEshop.item_types.id], + createdAt: json[TbEshop.item_types.created_at] != null + ? DateTime.parse(json[TbEshop.item_types.created_at]) + : null, + updatedAt: json[TbEshop.item_types.updated_at] != null + ? DateTime.parse(json[TbEshop.item_types.updated_at]) + : null, + title: json[TbEshop.item_types.title], + description: json[TbEshop.item_types.description], + type: json[TbEshop.item_types.type], + data: json[TbEshop.item_types.data], + occasion: json[TbEshop.item_types.occasion], + items: json[TbEshop.items.table] != null + ? List.from( + json[TbEshop.items.table].map((item) => ItemModel.fromJson(item))) + : null, + ); + } + + Map toJson() => { + TbEshop.item_types.id: id, + TbEshop.item_types.created_at: createdAt?.toIso8601String(), + TbEshop.item_types.updated_at: updatedAt?.toIso8601String(), + TbEshop.item_types.title: title, + TbEshop.item_types.description: description, + TbEshop.item_types.type: type, + TbEshop.item_types.data: data, + TbEshop.item_types.occasion: occasion, + TbEshop.items.table: items?.map((item) => item.toJson()).toList(), + }; + + String toBasicString() => title ?? id.toString(); + + ItemTypeModel({ + this.id, + this.createdAt, + this.updatedAt, + this.title, + this.description, + this.type, + this.data, + this.occasion, + this.items, + }); +} diff --git a/lib/dataModelsEshop/TbEshop.dart b/lib/dataModelsEshop/TbEshop.dart new file mode 100644 index 00000000..1b946db7 --- /dev/null +++ b/lib/dataModelsEshop/TbEshop.dart @@ -0,0 +1,78 @@ +class TbEshop { + static ItemTypesTb item_types = const ItemTypesTb(); + static ItemsTb items = const ItemsTb(); + static OrderItemTicketTb order_item_ticket = const OrderItemTicketTb(); + static OrdersTb orders = const OrdersTb(); + static PriceWavesTb price_waves = const PriceWavesTb(); + static TicketsTb tickets = const TicketsTb(); +} + +class ItemTypesTb { + const ItemTypesTb(); + String get table => "item_types"; + String get id => "id"; + String get created_at => "created_at"; + String get updated_at => "updated_at"; + String get title => "title"; + String get description => "description"; + String get type => "type"; + String get data => "data"; + String get occasion => "occasion"; +} + +class ItemsTb { + const ItemsTb(); + String get table => "items"; + String get id => "id"; + String get created_at => "created_at"; + String get updated_at => "updated_at"; + String get title => "title"; + String get description => "description"; + String get price => "price"; + String get data => "data"; + String get item_type => "item_type"; + String get occasion => "occasion"; +} + +class OrderItemTicketTb { + const OrderItemTicketTb(); + String get table => "order_item_ticket"; + String get id => "id"; + String get created_at => "created_at"; + String get order => "order"; + String get item => "item"; + String get ticket => "ticket"; +} + +class OrdersTb { + const OrdersTb(); + String get table => "orders"; + String get id => "id"; + String get created_at => "created_at"; + String get updated_at => "updated_at"; + String get price => "price"; + String get state => "state"; + String get data => "data"; + String get occasion => "occasion"; +} + +class PriceWavesTb { + const PriceWavesTb(); + String get table => "price_waves"; + String get id => "id"; + String get created_at => "created_at"; + String get start_time => "start_time"; + String get price => "price"; + String get item => "item"; +} + +class TicketsTb { + const TicketsTb(); + String get table => "tickets"; + String get id => "id"; + String get created_at => "created_at"; + String get updated_at => "updated_at"; + String get alias => "alias"; + String get state => "state"; + String get occasion => "occasion"; +} diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart new file mode 100644 index 00000000..898fbf69 --- /dev/null +++ b/lib/dataServices/DbEshop.dart @@ -0,0 +1,35 @@ +import 'package:collection/collection.dart'; +import 'package:fstapp/dataModelsEshop/ItemModel.dart'; +import 'package:fstapp/dataModelsEshop/ItemTypeModel.dart'; +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class DbEshop { + static final _supabase = Supabase.instance.client.schema("eshop"); + + static Future> getItems(int currentOccasion) async { + var data = await _supabase + .from(TbEshop.item_types.table) + .select( + "${TbEshop.item_types.id}," + "${TbEshop.item_types.type}," + "${TbEshop.item_types.title}," + "${TbEshop.items.table}(${TbEshop.items.id},${TbEshop.items.title},${TbEshop.items.price})" + ) + .eq(TbEshop.item_types.occasion, currentOccasion); + + var infoList = List.from( + data.map((x) { + var toReturn = ItemTypeModel.fromJson(x); + toReturn.items = toReturn.items?.sortedBy((i) => i.title ?? ""); + toReturn.items = toReturn.items?.sortedBy((i) => i.price ?? 0); + for (ItemModel v in toReturn.items??[]){ + v.title = v.price != null && v.price! > 0 ? "${v.title} (${v.price} KČ)" : v.title; + } + return toReturn; + })); + + return infoList; + } + +} diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart new file mode 100644 index 00000000..bdf23693 --- /dev/null +++ b/lib/pages/FormPage.dart @@ -0,0 +1,185 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:fstapp/dataModelsEshop/ItemModel.dart'; +import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/services/FormHelper.dart'; +import 'package:fstapp/styles/StylesConfig.dart'; +import 'package:fstapp/themeConfig.dart'; +import 'package:fstapp/widgets/ButtonsHelper.dart'; +import 'package:flutter/services.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:fstapp/services/ToastHelper.dart'; + +@RoutePage() +class FormPage extends StatefulWidget { + static const ROUTE = "form"; + + String? id; + FormPage({super.key}); + + @override + _FormPageState createState() => _FormPageState(); +} + +class _FormPageState extends State { + bool _isLoading = false; + bool _isSendSuccess = false; + Map? formData; + + Map? fields; + + final _formKey = GlobalKey(); + + @override + Future didChangeDependencies() async { + if (widget.id == null && context.routeData.hasPendingChildren) { + widget.id = context.routeData.pendingChildren[0].pathParams.getString("id"); + } + + await loadData(); + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: const Text("Form Page").tr(), + ), + body: Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: StylesConfig.appMaxWidth), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: _isSendSuccess + ? Padding( + padding: const EdgeInsets.fromLTRB(12, 88, 12, 12), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + children: [ + TextSpan( + style: TextStyle( + fontSize: 18, + color: ThemeConfig.blackColor(context)), + text: "Your order was successfully sent to your email {email}." + .tr(namedArgs: {"email": formData?["email"] ?? ""}), + ), + const WidgetSpan( + child: Padding( + padding: EdgeInsets.fromLTRB(6, 0, 0, 0), + child: Icon(Icons.check_circle), + ), + ), + ], + ), + ), + ) + : fields == null + ? const Center( + child: CircularProgressIndicator(), + ) + : FormBuilder( + key: _formKey, + child: AutofillGroup( + child: Column( + children: [ + ...FormHelper.getFormFields(fields?["fields"]), + const SizedBox(height: 16), + ButtonsHelper.bigButton( + context: context, + onPressed: _isLoading + ? null + : () async { + TextInput.finishAutofillContext(); + if (_formKey.currentState?.saveAndValidate() ?? false) { + setState(() { + _isLoading = true; + }); + var data = FormHelper.getDataFromForm( + _formKey, fields?["fields"]); + formData = data; + + // Simulate sending process + await Future.delayed(const Duration(seconds: 2)); + + setState(() { + _isSendSuccess = true; + _isLoading = false; + }); + + ToastHelper.Show( + context, + "Your order has been sent successfully!" + .tr()); + } + }, + label: "Send".tr(), + color: ThemeConfig.seed1, + textColor: Colors.white, + isEnabled: !_isLoading, + height: 50.0, + width: 250.0, + ), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } + + Future loadData() async { + setState(() { + _isLoading = true; + }); + + // Fetching items + var allItems = await DbEshop.getItems(13); + var foodType = allItems.firstWhereOrNull((a)=>a.type == ItemModel.foodType); + List> foodOptions = []; + + if(foodType != null){ + for (var f in foodType.items!) { + Map entry = { + FormHelper.metaOptionsName: f.title.toString(), + FormHelper.metaOptionsCode: f.id.toString() + }; + foodOptions.add(entry); + } + } + + + //var foodDefinition = {FormHelper.metaLabel: "Jidlo", FormHelper.metaOptionsType: ItemModel.foodType, FormHelper.metaOptions: foodOptions}; + + // Updating form fields + fields = { + "fields": [ + {FormHelper.metaType: FormHelper.fieldTypeName, FormHelper.IS_REQUIRED: true}, + {FormHelper.metaType: FormHelper.fieldTypeSurname, FormHelper.IS_REQUIRED: true}, + {FormHelper.metaType: FormHelper.fieldTypeEmail, FormHelper.IS_REQUIRED: true}, + {FormHelper.metaType: FormHelper.fieldTypeNote}, + if(foodType != null) + { + FormHelper.metaType: FormHelper.fieldTypeOptions, + FormHelper.metaOptions: foodOptions, + FormHelper.metaLabel: foodType.title, + FormHelper.metaOptionsType: ItemModel.foodType + }, + ] + }; + + setState(() { + _isLoading = false; + }); + } +} + diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 5c498bf3..9ff9b47f 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -12,11 +12,23 @@ class FormHelper { static const String fieldTypeEmail = "email"; static const String fieldTypeSex = "sex"; static const String fieldTypeBirthYear = "birth_year"; + static const String fieldTypeNote = "note"; + + + static const String metaType = "type"; + static const String metaLabel = "label"; + static const String metaOptions = "options"; + static const String metaOptionsType = "optionsType"; + static const String metaOptionsCode = "code"; + static const String metaOptionsName = "name"; + + static const String fieldTypeOptions = "options"; // Field Attribute Constants static const String IS_REQUIRED = "is_required"; // Labels and messages + static String noteLabel() => "Note".tr(); static String nameLabel() => "Name".tr(); static String surnameLabel() => "Surname".tr(); static String cityLabel() => "City".tr(); @@ -59,7 +71,9 @@ class FormHelper { // Create individual form field widget based on configuration static Widget createFormField(Map field) { final bool isRequiredField = field[IS_REQUIRED] ?? false; - switch (field["type"]) { + switch (field[metaType]) { + case fieldTypeNote: + return buildTextField(fieldTypeName, noteLabel(), isRequiredField, []); case fieldTypeName: return buildTextField(fieldTypeName, nameLabel(), isRequiredField, [AutofillHints.givenName]); case fieldTypeSurname: @@ -70,6 +84,8 @@ class FormHelper { return buildEmailField(isRequiredField); case fieldTypeSex: return buildRadioField(fieldTypeSex, sexLabel(), isRequiredField); + case fieldTypeOptions: + return buildGenericOptions(field[metaOptionsType], field[metaLabel], field[metaOptions]); case fieldTypeBirthYear: return buildBirthYearField(fieldTypeBirthYear, birthYearLabel(), isRequiredField); default: @@ -117,6 +133,29 @@ class FormHelper { ); } + static FormBuilderRadioGroup buildGenericOptions( + String name, String label, List optionsIn) { + List> options = []; + + for (var o in optionsIn) { + options.add(FormBuilderFieldOption( + value: FormOptionModel(o[metaOptionsCode], o[metaOptionsName]))); + } + + // Use the first option as the default initial value + final initialValue = options.isNotEmpty ? options.first.value : null; + + return FormBuilderRadioGroup( + name: name, + decoration: InputDecoration(labelText: label), + validator: FormBuilderValidators.required(), + options: options, + initialValue: initialValue, + orientation: OptionsOrientation.vertical, + wrapDirection: Axis.vertical, // Ensures buttons are arranged vertically + ); + } + static FormBuilderTextField buildBirthYearField(String name, String label, bool isRequired) { return FormBuilderTextField( name: name, @@ -150,4 +189,12 @@ class FormOptionModel { @override String toString() => name; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is FormOptionModel && runtimeType == other.runtimeType && code == other.code; + + @override + int get hashCode => code.hashCode; } \ No newline at end of file diff --git a/lib/services/UuidConverter.dart b/lib/services/UuidConverter.dart new file mode 100644 index 00000000..d570296b --- /dev/null +++ b/lib/services/UuidConverter.dart @@ -0,0 +1,61 @@ +class UuidConverter { + static const String base62Chars = + '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + + /// Converts a UUID to a Base62 encoded string + static String uuidToBase62(String uuid) { + // Remove dashes from UUID and parse as a base16 (hexadecimal) integer + final hexString = uuid.replaceAll('-', ''); + final bigIntValue = BigInt.parse(hexString, radix: 16); + + // Convert the integer to a Base62 string + return _toBase62(bigIntValue); + } + + /// Converts a Base62 string back to a UUID + static String base62ToUuid(String base62) { + // Convert the Base62 string back to a BigInt + final bigIntValue = _fromBase62(base62); + + // Convert the BigInt back to a hexadecimal string + final hexString = bigIntValue.toRadixString(16).padLeft(32, '0'); // UUID is 128 bits (32 hex digits) + + // Reformat as a UUID with dashes + return '${hexString.substring(0, 8)}-' + '${hexString.substring(8, 12)}-' + '${hexString.substring(12, 16)}-' + '${hexString.substring(16, 20)}-' + '${hexString.substring(20)}'; + } + + /// Converts a BigInt to a Base62 string + static String _toBase62(BigInt number) { + final base = base62Chars.length; + final result = StringBuffer(); + + while (number > BigInt.zero) { + final remainder = number % BigInt.from(base); + result.write(base62Chars[remainder.toInt()]); + number = number ~/ BigInt.from(base); + } + + return result.toString().split('').reversed.join(); // Reverse the string + } + + /// Converts a Base62 string to a BigInt + static BigInt _fromBase62(String base62) { + final base = base62Chars.length; + BigInt number = BigInt.zero; + + for (int i = 0; i < base62.length; i++) { + final char = base62[i]; + final value = base62Chars.indexOf(char); + if (value == -1) { + throw FormatException("Invalid character in Base62 string: $char"); + } + number = number * BigInt.from(base) + BigInt.from(value); + } + + return number; + } +} \ No newline at end of file From ebcfe234d67117e2be18139fcf0dd44ee70d6167 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 03:35:01 +0100 Subject: [PATCH 003/159] dynamic price --- assets/translations/en.json | 3 ++ lib/dataServices/DbEshop.dart | 6 ++-- lib/pages/FormPage.dart | 65 +++++++++++++++++++++++++---------- lib/services/FormHelper.dart | 16 ++++++--- lib/services/Utilities.dart | 19 ++++++++++ 5 files changed, 85 insertions(+), 24 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 75f95bb9..dcd5be92 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -296,5 +296,8 @@ "The processing has been cancelled.": "The processing has been cancelled.", "The processing has completed successfully.": "The processing has completed successfully.", "Saving changes": "Saving changes", + "Your order was successfully sent to your email {email}.": "Your order was successfully sent to your email {email}.", + "Your order has been sent successfully!": "Your order has been sent successfully!", + "Total Price: {price}": "Total Price: {price}", "_":"_" } \ No newline at end of file diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 898fbf69..af3b3ee3 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -1,13 +1,15 @@ import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; import 'package:fstapp/dataModelsEshop/ItemModel.dart'; import 'package:fstapp/dataModelsEshop/ItemTypeModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/services/Utilities.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class DbEshop { static final _supabase = Supabase.instance.client.schema("eshop"); - static Future> getItems(int currentOccasion) async { + static Future> getItems(BuildContext context, int currentOccasion) async { var data = await _supabase .from(TbEshop.item_types.table) .select( @@ -24,7 +26,7 @@ class DbEshop { toReturn.items = toReturn.items?.sortedBy((i) => i.title ?? ""); toReturn.items = toReturn.items?.sortedBy((i) => i.price ?? 0); for (ItemModel v in toReturn.items??[]){ - v.title = v.price != null && v.price! > 0 ? "${v.title} (${v.price} KČ)" : v.title; + v.title = v.price != null && v.price! > 0 ? "${v.title} (${Utilities.formatPrice(context, v.price!)})" : v.title; } return toReturn; })); diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index bdf23693..ed6e4347 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -5,6 +5,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:fstapp/dataModelsEshop/ItemModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/services/FormHelper.dart'; +import 'package:fstapp/services/Utilities.dart'; import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/ButtonsHelper.dart'; @@ -26,8 +27,8 @@ class FormPage extends StatefulWidget { class _FormPageState extends State { bool _isLoading = false; bool _isSendSuccess = false; + double _totalPrice = 0.0; // Total price Map? formData; - Map? fields; final _formKey = GlobalKey(); @@ -42,6 +43,23 @@ class _FormPageState extends State { super.didChangeDependencies(); } + void _updateTotalPrice() { + // Reset total price + _totalPrice = 0.0; + + // Calculate total price from selected options + for (var field in fields?["fields"] ?? []) { + if (field[FormHelper.metaType] == FormHelper.fieldTypeOptions) { + var selectedOption = _formKey.currentState?.fields[field[FormHelper.metaOptionsType]]?.value; + if (selectedOption is FormOptionModel) { + _totalPrice += selectedOption.price; + } + } + } + + setState(() {}); // Update the UI + } + @override Widget build(BuildContext context) { return Scaffold( @@ -67,7 +85,8 @@ class _FormPageState extends State { style: TextStyle( fontSize: 18, color: ThemeConfig.blackColor(context)), - text: "Your order was successfully sent to your email {email}." + text: + "Your order was successfully sent to your email {email}." .tr(namedArgs: {"email": formData?["email"] ?? ""}), ), const WidgetSpan( @@ -86,11 +105,21 @@ class _FormPageState extends State { ) : FormBuilder( key: _formKey, + onChanged: _updateTotalPrice, // Listen for form changes child: AutofillGroup( child: Column( children: [ ...FormHelper.getFormFields(fields?["fields"]), const SizedBox(height: 16), + if (_totalPrice > 0) + Text( + "Total Price: {price}".tr(namedArgs: {"price": Utilities.formatPrice(context, _totalPrice)}), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), ButtonsHelper.bigButton( context: context, onPressed: _isLoading @@ -143,23 +172,21 @@ class _FormPageState extends State { }); // Fetching items - var allItems = await DbEshop.getItems(13); - var foodType = allItems.firstWhereOrNull((a)=>a.type == ItemModel.foodType); - List> foodOptions = []; + var allItems = await DbEshop.getItems(context, 13); + var foodType = allItems.firstWhereOrNull((a) => a.type == ItemModel.foodType); + List> foodOptions = []; - if(foodType != null){ + if (foodType != null) { for (var f in foodType.items!) { - Map entry = { + Map entry = { FormHelper.metaOptionsName: f.title.toString(), - FormHelper.metaOptionsCode: f.id.toString() + FormHelper.metaOptionsCode: f.id.toString(), + FormHelper.metaOptionsPrice: f.price ?? 0.0 // Include price in the options }; foodOptions.add(entry); } } - - //var foodDefinition = {FormHelper.metaLabel: "Jidlo", FormHelper.metaOptionsType: ItemModel.foodType, FormHelper.metaOptions: foodOptions}; - // Updating form fields fields = { "fields": [ @@ -167,13 +194,13 @@ class _FormPageState extends State { {FormHelper.metaType: FormHelper.fieldTypeSurname, FormHelper.IS_REQUIRED: true}, {FormHelper.metaType: FormHelper.fieldTypeEmail, FormHelper.IS_REQUIRED: true}, {FormHelper.metaType: FormHelper.fieldTypeNote}, - if(foodType != null) - { - FormHelper.metaType: FormHelper.fieldTypeOptions, - FormHelper.metaOptions: foodOptions, - FormHelper.metaLabel: foodType.title, - FormHelper.metaOptionsType: ItemModel.foodType - }, + if (foodType != null) + { + FormHelper.metaType: FormHelper.fieldTypeOptions, + FormHelper.metaOptions: foodOptions, + FormHelper.metaLabel: foodType.title, + FormHelper.metaOptionsType: ItemModel.foodType + }, ] }; @@ -183,3 +210,5 @@ class _FormPageState extends State { } } + + diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 9ff9b47f..11e31687 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -21,6 +21,7 @@ class FormHelper { static const String metaOptionsType = "optionsType"; static const String metaOptionsCode = "code"; static const String metaOptionsName = "name"; + static const String metaOptionsPrice = "price"; static const String fieldTypeOptions = "options"; @@ -139,7 +140,11 @@ class FormHelper { for (var o in optionsIn) { options.add(FormBuilderFieldOption( - value: FormOptionModel(o[metaOptionsCode], o[metaOptionsName]))); + value: FormOptionModel( + o[metaOptionsCode], + o[metaOptionsName], + price: o[metaOptionsPrice] ?? 0.0, // Use price from the option or default to 0.0 + ))); } // Use the first option as the default initial value @@ -152,7 +157,7 @@ class FormHelper { options: options, initialValue: initialValue, orientation: OptionsOrientation.vertical, - wrapDirection: Axis.vertical, // Ensures buttons are arranged vertically + wrapDirection: Axis.vertical, ); } @@ -183,9 +188,10 @@ class FormHelper { } class FormOptionModel { - FormOptionModel(this.code, this.name); + FormOptionModel(this.code, this.name, {this.price = 0.0}); final String name; final String code; + final double price; @override String toString() => name; @@ -193,7 +199,9 @@ class FormOptionModel { @override bool operator ==(Object other) => identical(this, other) || - other is FormOptionModel && runtimeType == other.runtimeType && code == other.code; + other is FormOptionModel && + runtimeType == other.runtimeType && + code == other.code; @override int get hashCode => code.hashCode; diff --git a/lib/services/Utilities.dart b/lib/services/Utilities.dart index 4e69c0c2..eb442dc2 100644 --- a/lib/services/Utilities.dart +++ b/lib/services/Utilities.dart @@ -1,4 +1,23 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + class Utilities { + static formatPrice(BuildContext context, double price) { + // Get locale from context or fallback to Czech locale + final locale = EasyLocalization.of(context)?.locale.toString() ?? 'cs_CZ'; + + // Configure the currency formatter + final NumberFormat currencyFormatter = NumberFormat.currency( + locale: locale, + symbol: 'KČ', // Use the CZK symbol + ); + + return currencyFormatter.format(price); // Format the price as currency + } + + + static int naturalCompare(String a, String b) { final regex = RegExp(r'\d+|\D+'); final aMatches = regex.allMatches(a).map((m) => m.group(0)!).toList(); From 2d3848f5d4462823db7a27c6616d11bbd2528152 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 03:41:21 +0100 Subject: [PATCH 004/159] fix --- lib/pages/FormPage.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index ed6e4347..19a40186 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -87,7 +87,7 @@ class _FormPageState extends State { color: ThemeConfig.blackColor(context)), text: "Your order was successfully sent to your email {email}." - .tr(namedArgs: {"email": formData?["email"] ?? ""}), + .tr(namedArgs: {"email": formData?[FormHelper.fieldTypeEmail] ?? ""}), ), const WidgetSpan( child: Padding( @@ -195,12 +195,12 @@ class _FormPageState extends State { {FormHelper.metaType: FormHelper.fieldTypeEmail, FormHelper.IS_REQUIRED: true}, {FormHelper.metaType: FormHelper.fieldTypeNote}, if (foodType != null) - { - FormHelper.metaType: FormHelper.fieldTypeOptions, - FormHelper.metaOptions: foodOptions, - FormHelper.metaLabel: foodType.title, - FormHelper.metaOptionsType: ItemModel.foodType - }, + { + FormHelper.metaType: FormHelper.fieldTypeOptions, + FormHelper.metaOptions: foodOptions, + FormHelper.metaLabel: foodType.title, + FormHelper.metaOptionsType: ItemModel.foodType + }, ] }; From e3d68949411bbdf34080714935119aa923874099 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 05:06:56 +0100 Subject: [PATCH 005/159] gr --- lib/pages/FormPage.dart | 27 +++++++---- lib/services/FormHelper.dart | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 9 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 19a40186..af64124b 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -113,7 +113,9 @@ class _FormPageState extends State { const SizedBox(height: 16), if (_totalPrice > 0) Text( - "Total Price: {price}".tr(namedArgs: {"price": Utilities.formatPrice(context, _totalPrice)}), + "Total Price: {price}".tr(namedArgs: { + "price": Utilities.formatPrice(context, _totalPrice) + }), style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -181,7 +183,7 @@ class _FormPageState extends State { Map entry = { FormHelper.metaOptionsName: f.title.toString(), FormHelper.metaOptionsCode: f.id.toString(), - FormHelper.metaOptionsPrice: f.price ?? 0.0 // Include price in the options + FormHelper.metaOptionsPrice: f.price ?? 0.0, // Include price in the options }; foodOptions.add(entry); } @@ -189,19 +191,25 @@ class _FormPageState extends State { // Updating form fields fields = { - "fields": [ + FormHelper.metaFields: [ {FormHelper.metaType: FormHelper.fieldTypeName, FormHelper.IS_REQUIRED: true}, {FormHelper.metaType: FormHelper.fieldTypeSurname, FormHelper.IS_REQUIRED: true}, {FormHelper.metaType: FormHelper.fieldTypeEmail, FormHelper.IS_REQUIRED: true}, {FormHelper.metaType: FormHelper.fieldTypeNote}, - if (foodType != null) { - FormHelper.metaType: FormHelper.fieldTypeOptions, - FormHelper.metaOptions: foodOptions, - FormHelper.metaLabel: foodType.title, - FormHelper.metaOptionsType: ItemModel.foodType + FormHelper.metaType: FormHelper.fieldTypeTicket, + FormHelper.metaMaxTickets: 6, + FormHelper.metaFields: [ + if (foodType != null) + { + FormHelper.metaType: FormHelper.fieldTypeOptions, + FormHelper.metaOptions: foodOptions, + FormHelper.metaLabel: foodType.title, + FormHelper.metaOptionsType: ItemModel.foodType, + }, + ], }, - ] + ], }; setState(() { @@ -212,3 +220,4 @@ class _FormPageState extends State { + diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 11e31687..285eda04 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -3,6 +3,7 @@ import 'package:fstapp/dataModels/UserInfoModel.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:fstapp/themeConfig.dart'; class FormHelper { // Field Type Constants @@ -14,7 +15,10 @@ class FormHelper { static const String fieldTypeBirthYear = "birth_year"; static const String fieldTypeNote = "note"; + static const String fieldTypeTicket = "ticket"; + static const String metaMaxTickets = "max_tickets"; + static const String metaFields = "fields"; static const String metaType = "type"; static const String metaLabel = "label"; static const String metaOptions = "options"; @@ -89,11 +93,101 @@ class FormHelper { return buildGenericOptions(field[metaOptionsType], field[metaLabel], field[metaOptions]); case fieldTypeBirthYear: return buildBirthYearField(fieldTypeBirthYear, birthYearLabel(), isRequiredField); + case fieldTypeTicket: + return buildTicketField(field); default: return const SizedBox.shrink(); } } + static Widget buildTicketField(Map field) { + final maxTickets = field[metaMaxTickets] ?? 1; + final List> ticketFields = []; + final List> ticketKeys = []; + + // Add initial ticket + ticketFields.add({...field}); + ticketKeys.add(GlobalKey()); + + return StatefulBuilder( + builder: (context, setState) { + void addTicket() { + if (ticketFields.length < maxTickets) { + setState(() { + ticketFields.add({...field}); + ticketKeys.add(GlobalKey()); + }); + } + } + + void removeTicket(int index) { + if (ticketFields.length > 1) { + setState(() { + ticketFields.removeAt(index); + ticketKeys.removeAt(index); + }); + } + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (int i = 0; i < ticketFields.length; i++) + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Container( + decoration: BoxDecoration( + color: ThemeConfig.whiteColor(context), + border: Border.all(color: Theme.of(context).primaryColor), + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Ticket ${i + 1}".tr(), + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + if (i > 0) // Do not show remove icon for the first ticket + IconButton( + onPressed: () => removeTicket(i), + icon: Icon(Icons.delete, color: ThemeConfig.redColor(context)), + tooltip: "Delete Ticket".tr(), + ), + ], + ), + FormBuilder( + key: ticketKeys[i], + child: Column( + children: getFormFields(ticketFields[i][metaFields]), + ), + ), + ], + ), + ), + ), + if (ticketFields.length < maxTickets) + Align( + alignment: Alignment.center, + child: ElevatedButton.icon( + onPressed: addTicket, + icon: Icon(Icons.add, color: ThemeConfig.whiteColor(context)), + label: const Text("Add another ticket").tr(), + ), + ), + ], + ); + }, + ); + } + // Build a simple text field with optional validation static FormBuilderTextField buildTextField(String name, String label, bool isRequired, [List? autofillHints]) { return FormBuilderTextField( From 4507a8ddfb1fcf16ad8609e73981c214c17ac051 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 12:46:28 +0100 Subject: [PATCH 006/159] design --- lib/pages/FormPage.dart | 2 +- lib/services/FormHelper.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index af64124b..7bf61f40 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -152,7 +152,7 @@ class _FormPageState extends State { }, label: "Send".tr(), color: ThemeConfig.seed1, - textColor: Colors.white, + textColor: ThemeConfig.whiteColor(context), isEnabled: !_isLoading, height: 50.0, width: 250.0, diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 285eda04..9ae082e2 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -158,7 +158,7 @@ class FormHelper { if (i > 0) // Do not show remove icon for the first ticket IconButton( onPressed: () => removeTicket(i), - icon: Icon(Icons.delete, color: ThemeConfig.redColor(context)), + icon: Icon(Icons.delete), tooltip: "Delete Ticket".tr(), ), ], From e7c7f2ca3b6fb4813097728dd757014fc6e2bd4d Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 12:50:07 +0100 Subject: [PATCH 007/159] hardcoded removed --- lib/pages/FormPage.dart | 6 +++--- lib/pages/SignupPage.dart | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 7bf61f40..ff667ae4 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -48,7 +48,7 @@ class _FormPageState extends State { _totalPrice = 0.0; // Calculate total price from selected options - for (var field in fields?["fields"] ?? []) { + for (var field in fields?[FormHelper.metaFields] ?? []) { if (field[FormHelper.metaType] == FormHelper.fieldTypeOptions) { var selectedOption = _formKey.currentState?.fields[field[FormHelper.metaOptionsType]]?.value; if (selectedOption is FormOptionModel) { @@ -109,7 +109,7 @@ class _FormPageState extends State { child: AutofillGroup( child: Column( children: [ - ...FormHelper.getFormFields(fields?["fields"]), + ...FormHelper.getFormFields(fields?[FormHelper.metaFields]), const SizedBox(height: 16), if (_totalPrice > 0) Text( @@ -133,7 +133,7 @@ class _FormPageState extends State { _isLoading = true; }); var data = FormHelper.getDataFromForm( - _formKey, fields?["fields"]); + _formKey, fields?[FormHelper.metaFields]); formData = data; // Simulate sending process diff --git a/lib/pages/SignupPage.dart b/lib/pages/SignupPage.dart index ec41e563..ab7c7d00 100644 --- a/lib/pages/SignupPage.dart +++ b/lib/pages/SignupPage.dart @@ -28,14 +28,14 @@ class _SignupPageState extends State { Map? fieldsData; final dynamic fields = - {"fields": + {FormHelper.metaFields: [ - {"type":FormHelper.fieldTypeName, FormHelper.IS_REQUIRED: true}, - {"type":FormHelper.fieldTypeSurname, FormHelper.IS_REQUIRED: true}, - {"type":FormHelper.fieldTypeSex}, - {"type":FormHelper.fieldTypeEmail, FormHelper.IS_REQUIRED: true}, - {"type":FormHelper.fieldTypeCity, FormHelper.IS_REQUIRED: true}, - {"type":FormHelper.fieldTypeBirthYear}, + { FormHelper.metaType : FormHelper.fieldTypeName, FormHelper.IS_REQUIRED: true}, + { FormHelper.metaType : FormHelper.fieldTypeSurname, FormHelper.IS_REQUIRED: true}, + { FormHelper.metaType : FormHelper.fieldTypeSex}, + { FormHelper.metaType : FormHelper.fieldTypeEmail, FormHelper.IS_REQUIRED: true}, + { FormHelper.metaType : FormHelper.fieldTypeCity, FormHelper.IS_REQUIRED: true}, + { FormHelper.metaType : FormHelper.fieldTypeBirthYear}, ]}; final _formKey = GlobalKey(); @@ -85,7 +85,7 @@ class _SignupPageState extends State { child: AutofillGroup( child: Column( children: [ - ...FormHelper.getFormFields(fields["fields"]), + ...FormHelper.getFormFields(fields[FormHelper.metaFields]), const SizedBox( height: 16, ), @@ -97,7 +97,7 @@ class _SignupPageState extends State { setState(() { _isLoading = true; }); - var data = FormHelper.getDataFromForm(_formKey, fields["fields"]); + var data = FormHelper.getDataFromForm(_formKey, fields[FormHelper.metaFields]); fieldsData = data; var resp = await AuthService.register(data); if (resp["code"] == 200) { From e0acaf7095d47ea1c5b80d3a0dc2e35b0612e82a Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 12:53:59 +0100 Subject: [PATCH 008/159] hardcoded removed --- lib/services/FormHelper.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 9ae082e2..469c8907 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -54,7 +54,7 @@ class FormHelper { static Map getDataFromForm(GlobalKey key, dynamic fields) { Map toReturn = {}; for (var k in fields) { - toReturn[k["type"]] = getFieldData(key, k["type"]); + toReturn[k[metaType]] = getFieldData(key, k[metaType]); } return toReturn; } From a451f7145303c172d59a7194a638c8006f764a74 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:19:14 +0100 Subject: [PATCH 009/159] get ticket data --- lib/pages/FormPage.dart | 2 - lib/services/FormHelper.dart | 78 +++++++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index ff667ae4..cab6194b 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -151,8 +151,6 @@ class _FormPageState extends State { } }, label: "Send".tr(), - color: ThemeConfig.seed1, - textColor: ThemeConfig.whiteColor(context), isEnabled: !_isLoading, height: 50.0, width: 250.0, diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 469c8907..3a6d729b 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -45,6 +45,9 @@ class FormHelper { static String femaleLabel() => "Female".tr(); static String notSpecifiedLabel() => "Not specified".tr(); + static List> ticketValues = []; + static List> ticketKeys = []; + // Public method to generate form fields from configuration static List getFormFields(dynamic fields) { return fields.map((field) => createFormField(field)).toList(); @@ -54,25 +57,50 @@ class FormHelper { static Map getDataFromForm(GlobalKey key, dynamic fields) { Map toReturn = {}; for (var k in fields) { - toReturn[k[metaType]] = getFieldData(key, k[metaType]); + toReturn[k[metaType]] = getFieldData(key, k[metaType], ticketKeys: ticketKeys); } return toReturn; } // Determine the correct data from the form based on type - static dynamic getFieldData(GlobalKey formKey, String fieldType) { + static dynamic getFieldData(GlobalKey formKey, String fieldType, {List>? ticketKeys}) { var fieldValue = formKey.currentState?.fields[fieldType]?.value; + if (fieldType == fieldTypeSex) { - if(fieldValue == null){ + if (fieldValue == null) { return null; } return (fieldValue as FormOptionModel).code; } else if (fieldType == fieldTypeBirthYear) { return (fieldValue != null && fieldValue.isNotEmpty) ? int.tryParse(fieldValue) : null; + } else if (fieldType == fieldTypeTicket) { + // Collect ticket data from multiple ticket forms + List> tickets = []; + + if (ticketKeys != null) { + for (int i = 0; i < ticketKeys.length; i++) { + final ticketKey = ticketKeys[i]; + if (ticketKey.currentState == null) continue; + + Map ticketData = {}; + + for (FormBuilderFieldState, dynamic> ticketSubField in ticketKey.currentState?.fields.values ?? []) { + var subFieldType = ticketSubField.widget.name; + var subFieldValue = ticketSubField.value; + ticketData[subFieldType] = subFieldValue; + } + + tickets.add(ticketData); + } + } + + return tickets; // Return ticket data with all subfields and prices } + return fieldValue?.trim(); } + // Create individual form field widget based on configuration static Widget createFormField(Map field) { final bool isRequiredField = field[IS_REQUIRED] ?? false; @@ -94,36 +122,45 @@ class FormHelper { case fieldTypeBirthYear: return buildBirthYearField(fieldTypeBirthYear, birthYearLabel(), isRequiredField); case fieldTypeTicket: - return buildTicketField(field); + return buildTicketField(field, ticketValues, ticketKeys); default: return const SizedBox.shrink(); } } - static Widget buildTicketField(Map field) { + static Widget buildTicketField( + Map field, + List> ticketValues, + List> ticketKeys) { final maxTickets = field[metaMaxTickets] ?? 1; - final List> ticketFields = []; - final List> ticketKeys = []; - // Add initial ticket - ticketFields.add({...field}); - ticketKeys.add(GlobalKey()); + // Ensure ticketValues and ticketKeys are initialized + if (ticketValues.isEmpty) { + ticketValues.add({...field}); // Clone the field structure for the first ticket + ticketKeys.add(GlobalKey()); // Add a unique key for the first ticket + } return StatefulBuilder( builder: (context, setState) { void addTicket() { - if (ticketFields.length < maxTickets) { + if (ticketValues.length < maxTickets) { setState(() { - ticketFields.add({...field}); + // Add a new ticket form configuration + ticketValues.add({...field}); // Clone the field structure for the new ticket + + // Add a new unique key for the new ticket form ticketKeys.add(GlobalKey()); }); } } void removeTicket(int index) { - if (ticketFields.length > 1) { + if (ticketValues.length > 1) { setState(() { - ticketFields.removeAt(index); + // Remove the ticket field structure + ticketValues.removeAt(index); + + // Remove the corresponding key ticketKeys.removeAt(index); }); } @@ -132,7 +169,7 @@ class FormHelper { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - for (int i = 0; i < ticketFields.length; i++) + for (int i = 0; i < ticketValues.length; i++) Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Container( @@ -155,25 +192,25 @@ class FormHelper { fontSize: 16, ), ), - if (i > 0) // Do not show remove icon for the first ticket + if (i > 0) // Do not show the remove button for the first ticket IconButton( onPressed: () => removeTicket(i), - icon: Icon(Icons.delete), + icon: Icon(Icons.delete, color: Colors.red), tooltip: "Delete Ticket".tr(), ), ], ), FormBuilder( - key: ticketKeys[i], + key: ticketKeys[i], // Assign the corresponding key child: Column( - children: getFormFields(ticketFields[i][metaFields]), + children: getFormFields(ticketValues[i][metaFields]), ), ), ], ), ), ), - if (ticketFields.length < maxTickets) + if (ticketValues.length < maxTickets) Align( alignment: Alignment.center, child: ElevatedButton.icon( @@ -188,6 +225,7 @@ class FormHelper { ); } + // Build a simple text field with optional validation static FormBuilderTextField buildTextField(String name, String label, bool isRequired, [List? autofillHints]) { return FormBuilderTextField( From 06005f16c1a584a0a2e1b2122069c9c427fa9439 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:46:40 +0100 Subject: [PATCH 010/159] updating price --- lib/pages/FormPage.dart | 24 +++++++++++++++-------- lib/services/FormHelper.dart | 38 +++++++++++++++++------------------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index cab6194b..31e408e1 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -47,19 +47,28 @@ class _FormPageState extends State { // Reset total price _totalPrice = 0.0; - // Calculate total price from selected options + // Iterate over all fields and calculate total price for (var field in fields?[FormHelper.metaFields] ?? []) { + // Calculate price for regular options if (field[FormHelper.metaType] == FormHelper.fieldTypeOptions) { var selectedOption = _formKey.currentState?.fields[field[FormHelper.metaOptionsType]]?.value; if (selectedOption is FormOptionModel) { _totalPrice += selectedOption.price; } } + + // Calculate price for tickets + if (field[FormHelper.metaType] == FormHelper.fieldTypeTicket) { + for (var ticketData in FormHelper.getFieldData(_formKey, FormHelper.fieldTypeTicket, ticketKeys: FormHelper.ticketKeys) ?? []) { + _totalPrice += ticketData.values.first.price ?? 0.0; + } + } } setState(() {}); // Update the UI } + @override Widget build(BuildContext context) { return Scaffold( @@ -83,10 +92,10 @@ class _FormPageState extends State { children: [ TextSpan( style: TextStyle( - fontSize: 18, - color: ThemeConfig.blackColor(context)), - text: - "Your order was successfully sent to your email {email}." + fontSize: 18, + color: ThemeConfig.blackColor(context), + ), + text: "Your order was successfully sent to your email {email}." .tr(namedArgs: {"email": formData?[FormHelper.fieldTypeEmail] ?? ""}), ), const WidgetSpan( @@ -105,16 +114,15 @@ class _FormPageState extends State { ) : FormBuilder( key: _formKey, - onChanged: _updateTotalPrice, // Listen for form changes child: AutofillGroup( child: Column( children: [ - ...FormHelper.getFormFields(fields?[FormHelper.metaFields]), + ...FormHelper.getFormFields(fields?[FormHelper.metaFields], _updateTotalPrice), const SizedBox(height: 16), if (_totalPrice > 0) Text( "Total Price: {price}".tr(namedArgs: { - "price": Utilities.formatPrice(context, _totalPrice) + "price": Utilities.formatPrice(context, _totalPrice), }), style: TextStyle( fontSize: 18, diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 3a6d729b..63493f37 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -49,8 +49,8 @@ class FormHelper { static List> ticketKeys = []; // Public method to generate form fields from configuration - static List getFormFields(dynamic fields) { - return fields.map((field) => createFormField(field)).toList(); + static List getFormFields(dynamic fields, [void Function()? updateTotalPrice]) { + return fields.map((field) => createFormField(field, updateTotalPrice)).toList(); } // Retrieve form data by iterating over defined fields @@ -102,7 +102,7 @@ class FormHelper { // Create individual form field widget based on configuration - static Widget createFormField(Map field) { + static Widget createFormField(Map field, [void Function()? updateTotalPrice]) { final bool isRequiredField = field[IS_REQUIRED] ?? false; switch (field[metaType]) { case fieldTypeNote: @@ -122,7 +122,7 @@ class FormHelper { case fieldTypeBirthYear: return buildBirthYearField(fieldTypeBirthYear, birthYearLabel(), isRequiredField); case fieldTypeTicket: - return buildTicketField(field, ticketValues, ticketKeys); + return buildTicketField(field, ticketValues, ticketKeys, updateTotalPrice); default: return const SizedBox.shrink(); } @@ -131,13 +131,14 @@ class FormHelper { static Widget buildTicketField( Map field, List> ticketValues, - List> ticketKeys) { - final maxTickets = field[metaMaxTickets] ?? 1; + List> ticketKeys, + [void Function()? updateTotalPrice] // Pass the updateTotalPrice function + ) { + final maxTickets = field[FormHelper.metaMaxTickets] ?? 1; - // Ensure ticketValues and ticketKeys are initialized if (ticketValues.isEmpty) { - ticketValues.add({...field}); // Clone the field structure for the first ticket - ticketKeys.add(GlobalKey()); // Add a unique key for the first ticket + ticketValues.add({...field}); + ticketKeys.add(GlobalKey()); } return StatefulBuilder( @@ -145,25 +146,21 @@ class FormHelper { void addTicket() { if (ticketValues.length < maxTickets) { setState(() { - // Add a new ticket form configuration - ticketValues.add({...field}); // Clone the field structure for the new ticket - - // Add a new unique key for the new ticket form + ticketValues.add({...field}); ticketKeys.add(GlobalKey()); }); } + updateTotalPrice?.call(); // Update the total price when adding a ticket } void removeTicket(int index) { if (ticketValues.length > 1) { setState(() { - // Remove the ticket field structure ticketValues.removeAt(index); - - // Remove the corresponding key ticketKeys.removeAt(index); }); } + updateTotalPrice?.call(); // Update the total price when removing a ticket } return Column( @@ -192,18 +189,19 @@ class FormHelper { fontSize: 16, ), ), - if (i > 0) // Do not show the remove button for the first ticket + if (i > 0) IconButton( onPressed: () => removeTicket(i), - icon: Icon(Icons.delete, color: Colors.red), + icon: Icon(Icons.delete), tooltip: "Delete Ticket".tr(), ), ], ), FormBuilder( - key: ticketKeys[i], // Assign the corresponding key + key: ticketKeys[i], + onChanged: updateTotalPrice, // Trigger price update on change child: Column( - children: getFormFields(ticketValues[i][metaFields]), + children: FormHelper.getFormFields(ticketValues[i][FormHelper.metaFields]), ), ), ], From 92426d9a8e176b0f4d49d3c87d63f5ce5db997e9 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:59:44 +0100 Subject: [PATCH 011/159] design --- lib/services/FormHelper.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 63493f37..b40b920c 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -172,7 +172,7 @@ class FormHelper { child: Container( decoration: BoxDecoration( color: ThemeConfig.whiteColor(context), - border: Border.all(color: Theme.of(context).primaryColor), + border: Border.all(color: Theme.of(context).primaryColor, width: 2.0), borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.all(12), From a5d100b65b90b3420ec3eff3b4d0846c5cfc9e5e Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:31:34 +0100 Subject: [PATCH 012/159] translations, refactor --- assets/translations/en.json | 2 ++ lib/pages/FormPage.dart | 68 +++++++++++++++++++++++------------ lib/services/FormHelper.dart | 69 +++++++++++++++++++----------------- 3 files changed, 84 insertions(+), 55 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index dcd5be92..e4af9b3a 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -299,5 +299,7 @@ "Your order was successfully sent to your email {email}.": "Your order was successfully sent to your email {email}.", "Your order has been sent successfully!": "Your order has been sent successfully!", "Total Price: {price}": "Total Price: {price}", + "Ticket {number}": "Ticket {number}", + "Add another ticket": "Add another ticket", "_":"_" } \ No newline at end of file diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 31e408e1..78ec9238 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -3,6 +3,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:fstapp/dataModelsEshop/ItemModel.dart'; +import 'package:fstapp/dataModelsEshop/ItemTypeModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/services/FormHelper.dart'; import 'package:fstapp/services/Utilities.dart'; @@ -59,8 +60,18 @@ class _FormPageState extends State { // Calculate price for tickets if (field[FormHelper.metaType] == FormHelper.fieldTypeTicket) { - for (var ticketData in FormHelper.getFieldData(_formKey, FormHelper.fieldTypeTicket, ticketKeys: FormHelper.ticketKeys) ?? []) { - _totalPrice += ticketData.values.first.price ?? 0.0; + var ticketDataList = FormHelper.getFieldData( + _formKey, + FormHelper.fieldTypeTicket, + ticketKeys: FormHelper.ticketKeys + ) ?? []; + + for (var ticketData in ticketDataList) { + for (var ticketValue in ticketData.values) { + if (ticketValue is FormOptionModel) { + _totalPrice += ticketValue.price ?? 0.0; + } + } } } } @@ -174,6 +185,35 @@ class _FormPageState extends State { ); } + // Function to generate options for a specific item type and add them to the ticket fields + Map generateOptionsForItemType( + List allItems, + String itemType + ) { + var itemTypeModel = allItems.firstWhereOrNull((item) => item.type == itemType); + + if (itemTypeModel == null || itemTypeModel.items == null) { + return {}; + } + + List> options = []; + + for (var item in itemTypeModel.items!) { + options.add({ + FormHelper.metaOptionsName: item.title.toString(), + FormHelper.metaOptionsCode: item.id.toString(), + FormHelper.metaOptionsPrice: item.price ?? 0.0, // Include price in the options + }); + } + + return { + FormHelper.metaType: FormHelper.fieldTypeOptions, + FormHelper.metaOptions: options, + FormHelper.metaLabel: itemTypeModel.title, + FormHelper.metaOptionsType: itemType, + }; + } + Future loadData() async { setState(() { _isLoading = true; @@ -181,19 +221,8 @@ class _FormPageState extends State { // Fetching items var allItems = await DbEshop.getItems(context, 13); - var foodType = allItems.firstWhereOrNull((a) => a.type == ItemModel.foodType); - List> foodOptions = []; - - if (foodType != null) { - for (var f in foodType.items!) { - Map entry = { - FormHelper.metaOptionsName: f.title.toString(), - FormHelper.metaOptionsCode: f.id.toString(), - FormHelper.metaOptionsPrice: f.price ?? 0.0, // Include price in the options - }; - foodOptions.add(entry); - } - } + var foodOptionsField = generateOptionsForItemType(allItems, ItemModel.foodType); + var taxiOptionsField = generateOptionsForItemType(allItems, ItemModel.taxiType); // Updating form fields fields = { @@ -206,13 +235,8 @@ class _FormPageState extends State { FormHelper.metaType: FormHelper.fieldTypeTicket, FormHelper.metaMaxTickets: 6, FormHelper.metaFields: [ - if (foodType != null) - { - FormHelper.metaType: FormHelper.fieldTypeOptions, - FormHelper.metaOptions: foodOptions, - FormHelper.metaLabel: foodType.title, - FormHelper.metaOptionsType: ItemModel.foodType, - }, + if (taxiOptionsField.isNotEmpty) taxiOptionsField, + if (foodOptionsField.isNotEmpty) foodOptionsField, ], }, ], diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index b40b920c..4b01dcb1 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -169,42 +169,45 @@ class FormHelper { for (int i = 0; i < ticketValues.length; i++) Padding( padding: const EdgeInsets.symmetric(vertical: 8), - child: Container( - decoration: BoxDecoration( - color: ThemeConfig.whiteColor(context), - border: Border.all(color: Theme.of(context).primaryColor, width: 2.0), - borderRadius: BorderRadius.circular(8), - ), - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Ticket ${i + 1}".tr(), - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, + child: Align( + alignment: Alignment.center, // Center the ticket + child: Container( + decoration: BoxDecoration( + color: ThemeConfig.whiteColor(context), + border: Border.all(color: Theme.of(context).primaryColor, width: 2.0), + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Ticket {number}".tr(namedArgs: {"number": (i + 1).toString()}), // Use translated string + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), ), + if (i > 0) + IconButton( + onPressed: () => removeTicket(i), + icon: Icon(Icons.delete), + tooltip: "Delete".tr(), + ), + ], + ), + FormBuilder( + key: ticketKeys[i], // Assign the corresponding key + onChanged: updateTotalPrice, // Trigger price update on change + child: Column( + children: FormHelper.getFormFields(ticketValues[i][FormHelper.metaFields]), ), - if (i > 0) - IconButton( - onPressed: () => removeTicket(i), - icon: Icon(Icons.delete), - tooltip: "Delete Ticket".tr(), - ), - ], - ), - FormBuilder( - key: ticketKeys[i], - onChanged: updateTotalPrice, // Trigger price update on change - child: Column( - children: FormHelper.getFormFields(ticketValues[i][FormHelper.metaFields]), ), - ), - ], + ], + ), ), ), ), From a1cecb260b806efb00df0f7f3e551d0eaf95e64f Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:32:53 +0100 Subject: [PATCH 013/159] note --- lib/pages/FormPage.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 78ec9238..01e99fa7 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -237,6 +237,7 @@ class _FormPageState extends State { FormHelper.metaFields: [ if (taxiOptionsField.isNotEmpty) taxiOptionsField, if (foodOptionsField.isNotEmpty) foodOptionsField, + {FormHelper.metaType: FormHelper.fieldTypeNote}, ], }, ], From 5d8e3d74f16870092eb52cf8b97aa828272309ad Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:38:55 +0100 Subject: [PATCH 014/159] design --- lib/services/FormHelper.dart | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 4b01dcb1..1352bf9d 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -181,21 +181,29 @@ class FormHelper { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + Stack( children: [ - Text( - "Ticket {number}".tr(namedArgs: {"number": (i + 1).toString()}), // Use translated string - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Align( + alignment: Alignment.center, // Center the title across the entire row + child: Text( + "Ticket {number}".tr(namedArgs: {"number": (i + 1).toString()}), // Use translated string + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), ), ), if (i > 0) - IconButton( - onPressed: () => removeTicket(i), - icon: Icon(Icons.delete), - tooltip: "Delete".tr(), + Align( + alignment: Alignment.centerRight, // Align the delete button to the right + child: IconButton( + onPressed: () => removeTicket(i), + icon: Icon(Icons.delete), + tooltip: "Delete".tr(), + ), ), ], ), From 02e54df305f403202be333e473b32fecc3a0f2d3 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:32:37 +0100 Subject: [PATCH 015/159] note fix --- lib/services/FormHelper.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 1352bf9d..17950ed9 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -84,12 +84,10 @@ class FormHelper { Map ticketData = {}; - for (FormBuilderFieldState, dynamic> ticketSubField in ticketKey.currentState?.fields.values ?? []) { - var subFieldType = ticketSubField.widget.name; - var subFieldValue = ticketSubField.value; - ticketData[subFieldType] = subFieldValue; + for (MapEntry, dynamic>> ticketSubField in ticketKey.currentState?.fields.entries ?? []) { + var subFieldType = ticketSubField.key; + ticketData[subFieldType] = getFieldData(ticketKey, subFieldType, ticketKeys: ticketKeys); } - tickets.add(ticketData); } } @@ -97,7 +95,10 @@ class FormHelper { return tickets; // Return ticket data with all subfields and prices } - return fieldValue?.trim(); + if(fieldValue is String){ + return fieldValue.trim(); + } + return fieldValue; } @@ -106,7 +107,7 @@ class FormHelper { final bool isRequiredField = field[IS_REQUIRED] ?? false; switch (field[metaType]) { case fieldTypeNote: - return buildTextField(fieldTypeName, noteLabel(), isRequiredField, []); + return buildTextField(fieldTypeNote, noteLabel(), isRequiredField, []); case fieldTypeName: return buildTextField(fieldTypeName, nameLabel(), isRequiredField, [AutofillHints.givenName]); case fieldTypeSurname: From 9ae16452ffd3ed1d5e3045d9fc349f35f74fdc0d Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Fri, 29 Nov 2024 13:35:03 +0100 Subject: [PATCH 016/159] create_ticket_order --- lib/dataModels/FormOptionModel.dart | 32 ++++ lib/pages/FormPage.dart | 13 +- lib/services/FormHelper.dart | 31 +--- .../database/eshop/create_ticket_order.sql | 164 ++++++++++++++++++ scripts/database/eshop/validate_item_type.sql | 26 +++ 5 files changed, 237 insertions(+), 29 deletions(-) create mode 100644 lib/dataModels/FormOptionModel.dart create mode 100644 scripts/database/eshop/create_ticket_order.sql create mode 100644 scripts/database/eshop/validate_item_type.sql diff --git a/lib/dataModels/FormOptionModel.dart b/lib/dataModels/FormOptionModel.dart new file mode 100644 index 00000000..89052404 --- /dev/null +++ b/lib/dataModels/FormOptionModel.dart @@ -0,0 +1,32 @@ +class FormOptionModel { + static const String metaOptionsId = "code"; + static const String metaOptionsName = "name"; + static const String metaOptionsPrice = "price"; + + FormOptionModel(this.code, this.name, {this.price = 0.0}); + + final String name; + final String code; + final double price; + + @override + String toString() => name; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is FormOptionModel && + runtimeType == other.runtimeType && + code == other.code; + + @override + int get hashCode => code.hashCode; + + Map toJson() { + return { + metaOptionsName: name, + metaOptionsId: code, + metaOptionsPrice: price, + }; + } +} diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 01e99fa7..393f7649 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -1,7 +1,10 @@ +import 'dart:convert'; + import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:fstapp/dataModels/FormOptionModel.dart'; import 'package:fstapp/dataModelsEshop/ItemModel.dart'; import 'package:fstapp/dataModelsEshop/ItemTypeModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; @@ -153,7 +156,11 @@ class _FormPageState extends State { }); var data = FormHelper.getDataFromForm( _formKey, fields?[FormHelper.metaFields]); + // Convert to JSON + // Convert to JSON string + formData = data; + var stri = data.toString(); // Simulate sending process await Future.delayed(const Duration(seconds: 2)); @@ -200,9 +207,9 @@ class _FormPageState extends State { for (var item in itemTypeModel.items!) { options.add({ - FormHelper.metaOptionsName: item.title.toString(), - FormHelper.metaOptionsCode: item.id.toString(), - FormHelper.metaOptionsPrice: item.price ?? 0.0, // Include price in the options + FormOptionModel.metaOptionsName: item.title.toString(), + FormOptionModel.metaOptionsId: item.id.toString(), + FormOptionModel.metaOptionsPrice: item.price ?? 0.0, // Include price in the options }); } diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 17950ed9..56ef5ead 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -1,4 +1,5 @@ import 'package:easy_localization/easy_localization.dart'; +import 'package:fstapp/dataModels/FormOptionModel.dart'; import 'package:fstapp/dataModels/UserInfoModel.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; @@ -23,9 +24,7 @@ class FormHelper { static const String metaLabel = "label"; static const String metaOptions = "options"; static const String metaOptionsType = "optionsType"; - static const String metaOptionsCode = "code"; - static const String metaOptionsName = "name"; - static const String metaOptionsPrice = "price"; + static const String fieldTypeOptions = "options"; @@ -283,9 +282,9 @@ class FormHelper { for (var o in optionsIn) { options.add(FormBuilderFieldOption( value: FormOptionModel( - o[metaOptionsCode], - o[metaOptionsName], - price: o[metaOptionsPrice] ?? 0.0, // Use price from the option or default to 0.0 + o[FormOptionModel.metaOptionsId], + o[FormOptionModel.metaOptionsName], + price: o[FormOptionModel.metaOptionsPrice] ?? 0.0, // Use price from the option or default to 0.0 ))); } @@ -327,24 +326,4 @@ class FormHelper { keyboardType: TextInputType.number, ); } -} - -class FormOptionModel { - FormOptionModel(this.code, this.name, {this.price = 0.0}); - final String name; - final String code; - final double price; - - @override - String toString() => name; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is FormOptionModel && - runtimeType == other.runtimeType && - code == other.code; - - @override - int get hashCode => code.hashCode; } \ No newline at end of file diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql new file mode 100644 index 00000000..278abe4f --- /dev/null +++ b/scripts/database/eshop/create_ticket_order.sql @@ -0,0 +1,164 @@ +CREATE OR REPLACE FUNCTION eshop.create_ticket_order(input_data JSONB) +RETURNS JSONB +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + order_id BIGINT; + ticket_data JSONB; + spot_data RECORD; + spot_id BIGINT; + occasion_data RECORD; + now TIMESTAMP WITH TIME ZONE := NOW(); + calculated_price DOUBLE PRECISION := 0; + spot_secret UUID; + item_id BIGINT; + used_spots JSONB := '[]'::JSONB; + occasion_id BIGINT; + item_details JSONB := '[]'::JSONB; + item_data RECORD; + ticket_id BIGINT; +BEGIN + -- Validate input data and extract occasion + IF input_data IS NULL OR input_data->'occasion' IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1001, 'message', 'Invalid input data or missing occasion'); + END IF; + occasion_id := (input_data->'occasion')::BIGINT; + + -- Validate occasion and fetch organization + SELECT * INTO occasion_data + FROM public.occasions + WHERE id = occasion_id AND is_hidden = FALSE AND is_open = TRUE; + + IF occasion_data IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1002, 'message', 'Invalid or closed occasion'); + END IF; + + -- Process tickets + FOR ticket_data IN SELECT * FROM JSONB_ARRAY_ELEMENTS(input_data->'ticket') LOOP + -- Spot validation + SELECT * INTO spot_data + FROM eshop.spots + WHERE id = (ticket_data->'spot')::BIGINT AND occasion = occasion_id; + + IF spot_data IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1003, 'message', 'Invalid or unrelated spot'); + END IF; + + -- Check if spot is already used or its state is not NULL + IF used_spots @> JSONB_BUILD_ARRAY(spot_data.id) THEN + RETURN JSONB_BUILD_OBJECT('code', 1008, 'message', 'Spot already used'); + END IF; + IF spot_data.state IS NOT NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1010, 'message', 'Spot is already reserved or in use'); + END IF; + + -- Add spot to used spots + used_spots := used_spots || JSONB_BUILD_ARRAY(spot_data.id); + + -- Secret validation + spot_secret := (input_data->>'secret')::UUID; + IF spot_data.secret IS DISTINCT FROM spot_secret THEN + RETURN JSONB_BUILD_OBJECT('code', 1004, 'message', 'Invalid secret for spot'); + END IF; + IF spot_data.secret_expiration_time < now THEN + RETURN JSONB_BUILD_OBJECT('code', 1005, 'message', 'Secret expired'); + END IF; + + -- Create ticket first + INSERT INTO eshop.tickets (alias, state, occasion, created_at, updated_at) + VALUES (ticket_data->>'note', 'ordered', occasion_id, now, now) + RETURNING id INTO ticket_id; + + -- Process taxi, food, and spot items + FOREACH item_id IN ARRAY ARRAY[ + (ticket_data->'taxi'->>'id')::BIGINT, + (ticket_data->'food'->>'id')::BIGINT, + spot_data.item + ] LOOP + IF item_id IS NOT NULL THEN + SELECT i.*, it.type, COALESCE(pw.price, i.price) AS final_price + INTO item_data + FROM eshop.items i + LEFT JOIN eshop.item_types it ON i.item_type = it.id + LEFT JOIN ( + SELECT * FROM eshop.price_waves + WHERE start_time <= now + ORDER BY start_time DESC LIMIT 1 + ) pw ON pw.item = i.id + WHERE i.id = item_id AND i.occasion = occasion_id; + + IF item_data IS NULL THEN + RETURN JSONB_BUILD_OBJECT( + 'code', 1006, + 'message', 'Item not found or not part of occasion', + 'details', item_id + ); + END IF; + + -- Build item details, conditionally including spot-specific keys and ticket_id + item_details := item_details || JSONB_BUILD_OBJECT( + 'item_id', item_id, + 'ticket_id', ticket_id, + 'title', item_data.title, + 'type', item_data.type, + 'price', item_data.final_price, + 'spot_title', CASE WHEN item_id = spot_data.item THEN spot_data.title ELSE NULL END, + 'description', CASE WHEN item_id = spot_data.item THEN item_data.description ELSE NULL END + ); + + -- Add item price to calculated total + calculated_price := calculated_price + COALESCE(item_data.final_price, item_data.price, 0); + + -- Link ticket and item to the order + INSERT INTO eshop.order_item_ticket ("order", item, ticket) + VALUES (order_id, item_id, ticket_id); + END IF; + END LOOP; + END LOOP; + + -- Create order with calculated price + INSERT INTO eshop.orders (created_at, updated_at, price, state, data, occasion) + VALUES ( + now, now, calculated_price, 'ordered', + JSONB_BUILD_OBJECT( + 'name', input_data->>'name', + 'surname', input_data->>'surname', + 'email', input_data->>'email', + 'note', input_data->>'note', + 'price', calculated_price + ), + occasion_id + ) RETURNING id INTO order_id; + + -- Log order to orders_history with price + INSERT INTO eshop.orders_history (created_at, data, "order", state, price) + VALUES ( + now, + JSONB_BUILD_OBJECT('input_data', input_data, 'items', item_details, 'price', calculated_price), + order_id, + 'ordered', + calculated_price + ); + + -- Update spot states + FOR spot_id IN + SELECT value::BIGINT + FROM JSONB_ARRAY_ELEMENTS_TEXT(used_spots) AS value + LOOP + UPDATE eshop.spots + SET state = 'ordered', updated_at = now + WHERE id = spot_id; + END LOOP; + + -- Return success response with order ID and items + RETURN JSONB_BUILD_OBJECT( + 'code', 200, + 'order_id', order_id, + 'items', item_details + ); +EXCEPTION + WHEN OTHERS THEN + RETURN JSONB_BUILD_OBJECT('code', 1009, 'message', SQLERRM); +END; +$$; \ No newline at end of file diff --git a/scripts/database/eshop/validate_item_type.sql b/scripts/database/eshop/validate_item_type.sql new file mode 100644 index 00000000..4ad647fc --- /dev/null +++ b/scripts/database/eshop/validate_item_type.sql @@ -0,0 +1,26 @@ +CREATE OR REPLACE FUNCTION eshop.validate_item_type(item_id BIGINT, required_type TEXT) +RETURNS BOOLEAN +LANGUAGE plpgsql +AS $$ +DECLARE + item_type RECORD; +BEGIN + -- Retrieve the item's type + SELECT it.* + INTO item_type + FROM eshop.items i + JOIN eshop.item_types it ON i.item_type = it.id + WHERE i.id = item_id; + + -- Check if the type matches + IF item_type IS NULL THEN + RAISE EXCEPTION 'Item with ID % not found or has no type.', item_id; + END IF; + + IF item_type.type = required_type THEN + RETURN TRUE; + ELSE + RAISE EXCEPTION 'Item with ID % is not of type %. Found type: %', item_id, required_type, item_type.type; + END IF; +END; +$$; From 51b83c313299142a6624a4dcecc159692eedf20a Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:08:34 +0100 Subject: [PATCH 017/159] create_ticket_order fix --- .../database/eshop/create_ticket_order.sql | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index 278abe4f..b410ca1b 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -18,6 +18,7 @@ DECLARE item_details JSONB := '[]'::JSONB; item_data RECORD; ticket_id BIGINT; + order_item_ticket_id BIGINT; BEGIN -- Validate input data and extract occasion IF input_data IS NULL OR input_data->'occasion' IS NULL THEN @@ -45,11 +46,8 @@ BEGIN RETURN JSONB_BUILD_OBJECT('code', 1003, 'message', 'Invalid or unrelated spot'); END IF; - -- Check if spot is already used or its state is not NULL - IF used_spots @> JSONB_BUILD_ARRAY(spot_data.id) THEN - RETURN JSONB_BUILD_OBJECT('code', 1008, 'message', 'Spot already used'); - END IF; - IF spot_data.state IS NOT NULL THEN + -- Check if spot is already used (order_item_ticket is not NULL) + IF spot_data.order_item_ticket IS NOT NULL THEN RETURN JSONB_BUILD_OBJECT('code', 1010, 'message', 'Spot is already reserved or in use'); END IF; @@ -112,7 +110,15 @@ BEGIN -- Link ticket and item to the order INSERT INTO eshop.order_item_ticket ("order", item, ticket) - VALUES (order_id, item_id, ticket_id); + VALUES (order_id, item_id, ticket_id) + RETURNING id INTO order_item_ticket_id; + + -- Assign the order_item_ticket ID to the spot + IF item_id = spot_data.item THEN + UPDATE eshop.spots + SET order_item_ticket = order_item_ticket_id, updated_at = now + WHERE id = spot_data.id; + END IF; END IF; END LOOP; END LOOP; @@ -141,16 +147,6 @@ BEGIN calculated_price ); - -- Update spot states - FOR spot_id IN - SELECT value::BIGINT - FROM JSONB_ARRAY_ELEMENTS_TEXT(used_spots) AS value - LOOP - UPDATE eshop.spots - SET state = 'ordered', updated_at = now - WHERE id = spot_id; - END LOOP; - -- Return success response with order ID and items RETURN JSONB_BUILD_OBJECT( 'code', 200, @@ -161,4 +157,4 @@ EXCEPTION WHEN OTHERS THEN RETURN JSONB_BUILD_OBJECT('code', 1009, 'message', SQLERRM); END; -$$; \ No newline at end of file +$$; From 040838e13637e672c4e8e03604afbd30b3e7a83a Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Fri, 29 Nov 2024 19:03:04 +0100 Subject: [PATCH 018/159] form dynamically --- lib/dataModels/FormModel.dart | 55 +++++++++++++++++++--- lib/dataModels/Tb.dart | 14 ++++++ lib/dataServices/DbEshop.dart | 17 ++++++- lib/pages/FormPage.dart | 71 +++++++++++++++++++---------- scripts/database/eshop/get_form.sql | 40 ++++++++++++++++ 5 files changed, 164 insertions(+), 33 deletions(-) create mode 100644 scripts/database/eshop/get_form.sql diff --git a/lib/dataModels/FormModel.dart b/lib/dataModels/FormModel.dart index 10875b35..b9a709b9 100644 --- a/lib/dataModels/FormModel.dart +++ b/lib/dataModels/FormModel.dart @@ -1,14 +1,57 @@ -class FormModel{ +import 'package:fstapp/dataModels/Tb.dart'; + +class FormModel { int? id; - dynamic data; + DateTime? createdAt; + Map? data; + String? key; + int? occasion; + String? type; + int? bankAccount; + int? deadlineDuration; + bool? isOpen; + String? accountNumber; + + FormModel({ + this.id, + this.createdAt, + this.data, + this.key, + this.occasion, + this.type, + this.bankAccount, + this.deadlineDuration, + this.isOpen, + this.accountNumber, + }); factory FormModel.fromJson(Map json) { return FormModel( - id: json["id"], - data: json["data"], + id: json[Tb.forms.id], + createdAt: json[Tb.forms.created_at] != null + ? DateTime.parse(json[Tb.forms.created_at]) + : null, + data: json[Tb.forms.data], + key: json[Tb.forms.key], + occasion: json[Tb.forms.occasion], + type: json[Tb.forms.type], + bankAccount: json[Tb.forms.bank_account], + deadlineDuration: json[Tb.forms.deadline_duration], + isOpen: json[Tb.forms.is_open], + accountNumber: json['account_number'], // Assuming it's added to the RPC response ); } - FormModel({this.id, this.data}); + Map toJson() => { + Tb.forms.id: id, + Tb.forms.created_at: createdAt?.toIso8601String(), + Tb.forms.data: data, + Tb.forms.key: key, + Tb.forms.occasion: occasion, + Tb.forms.type: type, + Tb.forms.bank_account: bankAccount, + Tb.forms.deadline_duration: deadlineDuration, + Tb.forms.is_open: isOpen, + 'account_number': accountNumber, // Including in serialization + }; } - diff --git a/lib/dataModels/Tb.dart b/lib/dataModels/Tb.dart index 71200c8c..228b5f7b 100644 --- a/lib/dataModels/Tb.dart +++ b/lib/dataModels/Tb.dart @@ -24,6 +24,7 @@ class Tb { static IconsTb icons = const IconsTb(); static OrganizationsTb organizations = const OrganizationsTb(); static OrganizationUsersTb organization_users = const OrganizationUsersTb(); + static FormsTb forms = const FormsTb(); } class OccasionsTb { const OccasionsTb(); @@ -311,4 +312,17 @@ class OrganizationUsersTb{ String get organization => "organization"; String get user => "user"; String get is_admin => "is_admin"; +} +class FormsTb { + const FormsTb(); + String get table => "forms"; + String get id => "id"; + String get created_at => "created_at"; + String get data => "data"; + String get key => "key"; + String get occasion => "occasion"; + String get type => "type"; + String get bank_account => "bank_account"; + String get deadline_duration => "deadline_duration"; + String get is_open => "is_open"; } \ No newline at end of file diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index af3b3ee3..a7ca3929 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -1,5 +1,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; +import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataModelsEshop/ItemModel.dart'; import 'package:fstapp/dataModelsEshop/ItemTypeModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; @@ -7,10 +8,11 @@ import 'package:fstapp/services/Utilities.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class DbEshop { - static final _supabase = Supabase.instance.client.schema("eshop"); + static final _supabaseEshop = Supabase.instance.client.schema("eshop"); + static final _supabase = Supabase.instance.client; static Future> getItems(BuildContext context, int currentOccasion) async { - var data = await _supabase + var data = await _supabaseEshop .from(TbEshop.item_types.table) .select( "${TbEshop.item_types.id}," @@ -34,4 +36,15 @@ class DbEshop { return infoList; } + static Future getForm(String formKey) async { + final response = await _supabase + .rpc('get_form', params: {'form_key': formKey}); + + if(response["code"] == 200){ + var form = FormModel.fromJson(response["data"]); + return form; + } + return null; + } + } diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 393f7649..d27bc3da 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -10,6 +10,7 @@ import 'package:fstapp/dataModelsEshop/ItemTypeModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/services/FormHelper.dart'; import 'package:fstapp/services/Utilities.dart'; +import 'package:fstapp/services/UuidConverter.dart'; import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/ButtonsHelper.dart'; @@ -156,11 +157,8 @@ class _FormPageState extends State { }); var data = FormHelper.getDataFromForm( _formKey, fields?[FormHelper.metaFields]); - // Convert to JSON - // Convert to JSON string formData = data; - var stri = data.toString(); // Simulate sending process await Future.delayed(const Duration(seconds: 2)); @@ -226,29 +224,52 @@ class _FormPageState extends State { _isLoading = true; }); + // if(widget.id == null) { + // return; + // } + //var key = UuidConverter.base62ToUuid(widget.id!); + var form = await DbEshop.getForm("7f4e3892-a544-4385-b933-61117e9755c3"); + if(form == null) { + return; + } // Fetching items - var allItems = await DbEshop.getItems(context, 13); - var foodOptionsField = generateOptionsForItemType(allItems, ItemModel.foodType); - var taxiOptionsField = generateOptionsForItemType(allItems, ItemModel.taxiType); - - // Updating form fields - fields = { - FormHelper.metaFields: [ - {FormHelper.metaType: FormHelper.fieldTypeName, FormHelper.IS_REQUIRED: true}, - {FormHelper.metaType: FormHelper.fieldTypeSurname, FormHelper.IS_REQUIRED: true}, - {FormHelper.metaType: FormHelper.fieldTypeEmail, FormHelper.IS_REQUIRED: true}, - {FormHelper.metaType: FormHelper.fieldTypeNote}, - { - FormHelper.metaType: FormHelper.fieldTypeTicket, - FormHelper.metaMaxTickets: 6, - FormHelper.metaFields: [ - if (taxiOptionsField.isNotEmpty) taxiOptionsField, - if (foodOptionsField.isNotEmpty) foodOptionsField, - {FormHelper.metaType: FormHelper.fieldTypeNote}, - ], - }, - ], - }; + var allItems = await DbEshop.getItems(context, form.occasion!); + // New fields to replace existing ones + List updatedFields = []; + + // Loop through the fields in form.data + for (var field in form.data![FormHelper.metaFields]) { + // Check if the field is a ticket + if (field[FormHelper.metaType] == FormHelper.fieldTypeTicket) { + // Process the fields inside the ticket + List updatedTicketFields = []; + for (var ticketField in field[FormHelper.metaFields]) { + // Check if the ticket field has an optionsType + if (ticketField.containsKey(FormHelper.metaOptionsType)) { + var optionsType = ticketField[FormHelper.metaOptionsType]; + var generatedOptions = generateOptionsForItemType(allItems, optionsType); + updatedTicketFields.add(generatedOptions); + } else { + // Directly add the ticket field if no optionsType is present + updatedTicketFields.add(ticketField); + } + } + + // Replace the fields inside the ticket + updatedFields.add({ + FormHelper.metaType: field[FormHelper.metaType], + FormHelper.metaMaxTickets: field[FormHelper.metaMaxTickets], + FormHelper.metaFields: updatedTicketFields, + }); + } else { + // Directly add the field if it's not a ticket + updatedFields.add(field); + } + } + + form.data![FormHelper.metaFields] = updatedFields; + + fields = form.data; setState(() { _isLoading = false; diff --git a/scripts/database/eshop/get_form.sql b/scripts/database/eshop/get_form.sql new file mode 100644 index 00000000..747dc41f --- /dev/null +++ b/scripts/database/eshop/get_form.sql @@ -0,0 +1,40 @@ +CREATE OR REPLACE FUNCTION get_form(form_key UUID) +RETURNS JSON LANGUAGE plpgsql SECURITY DEFINER AS $$ +DECLARE + allData JSON; +BEGIN + -- Check if the form is open + IF NOT EXISTS ( + SELECT 1 + FROM public.forms + WHERE key = form_key AND is_open = true + ) THEN + -- Return an error if the form is not open + RETURN jsonb_build_object( + 'code', 400, + 'message', 'Form is not open.' + ); + END IF; + + -- Build the query to fetch data if the form is open + SELECT jsonb_build_object( + 'code', 200, + 'data', jsonb_build_object( + 'id', f.id, + 'created_at', f.created_at, + 'data', f.data, + 'type', f.type, + 'occasion', f.occasion, + 'deadline_duration', f.deadline_duration, + 'account_number', ba.account_number + ) + ) + INTO allData + FROM public.forms f + LEFT JOIN eshop.bank_accounts ba ON f.bank_account = ba.id + WHERE f.key = form_key; + + -- Return the result + RETURN allData; +END; +$$; From a4bf0fc9d0f860e3e2db8a4361a66435d7054b65 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Fri, 29 Nov 2024 22:45:45 +0100 Subject: [PATCH 019/159] ticket symbol --- .../database/eshop/create_ticket_order.sql | 10 +++-- .../database/eshop/generate_ticket_symbol.sql | 41 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 scripts/database/eshop/generate_ticket_symbol.sql diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index b410ca1b..c66db7ab 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -19,6 +19,7 @@ DECLARE item_data RECORD; ticket_id BIGINT; order_item_ticket_id BIGINT; + ticket_symbol TEXT; BEGIN -- Validate input data and extract occasion IF input_data IS NULL OR input_data->'occasion' IS NULL THEN @@ -63,9 +64,12 @@ BEGIN RETURN JSONB_BUILD_OBJECT('code', 1005, 'message', 'Secret expired'); END IF; - -- Create ticket first - INSERT INTO eshop.tickets (alias, state, occasion, created_at, updated_at) - VALUES (ticket_data->>'note', 'ordered', occasion_id, now, now) + -- Generate ticket symbol + ticket_symbol := generate_ticket_symbol(occasion_data.organization, occasion_id); + + -- Create ticket with ticket symbol + INSERT INTO eshop.tickets (state, occasion, ticket_symbol, created_at, updated_at) + VALUES ('ordered', occasion_id, ticket_symbol, now, now) RETURNING id INTO ticket_id; -- Process taxi, food, and spot items diff --git a/scripts/database/eshop/generate_ticket_symbol.sql b/scripts/database/eshop/generate_ticket_symbol.sql new file mode 100644 index 00000000..cf4091ba --- /dev/null +++ b/scripts/database/eshop/generate_ticket_symbol.sql @@ -0,0 +1,41 @@ +CREATE OR REPLACE FUNCTION generate_ticket_symbol(organization_id BIGINT, occasion_id BIGINT) +RETURNS TEXT AS $$ +DECLARE + visible_id TEXT; + org_suffix TEXT; + occasion_suffix TEXT; + numeric_part TEXT := '123456789'; + alphabetic_part TEXT := 'ACEFGHIJKLMNPQRUVWXY'; + attempt_count INT := 0; +BEGIN + -- Extract last two digits of organization_id and occasion_id + org_suffix := LPAD((organization_id % 100)::TEXT, 2, '0'); -- Ensure 2 digits + occasion_suffix := LPAD((occasion_id % 100)::TEXT, 2, '0'); -- Ensure 2 digits + + LOOP + -- Generate alternating numeric and alphabetic parts + visible_id := org_suffix || occasion_suffix || + SUBSTRING(numeric_part FROM FLOOR(RANDOM() * 10 + 1)::INT FOR 1) || + SUBSTRING(alphabetic_part FROM FLOOR(RANDOM() * 20 + 1)::INT FOR 1) || + SUBSTRING(numeric_part FROM FLOOR(RANDOM() * 10 + 1)::INT FOR 1) || + SUBSTRING(alphabetic_part FROM FLOOR(RANDOM() * 20 + 1)::INT FOR 1) || + SUBSTRING(numeric_part FROM FLOOR(RANDOM() * 10 + 1)::INT FOR 1) || + SUBSTRING(alphabetic_part FROM FLOOR(RANDOM() * 20 + 1)::INT FOR 1); + + -- Ensure uniqueness in the database (within the organization) + IF NOT EXISTS ( + SELECT 1 + FROM eshop.tickets + WHERE ticket_symbol = visible_id AND occasion = occasion_id + ) THEN + RETURN visible_id; -- Return the unique visible ID + END IF; + + -- Increment attempt count and fail after a fixed number of attempts + attempt_count := attempt_count + 1; + IF attempt_count > 10 THEN + RAISE EXCEPTION 'Failed to generate unique visible ID after 10 attempts'; + END IF; + END LOOP; +END; +$$ LANGUAGE plpgsql; From 82b780190e4a168880681b1dd47dc7755e5b3897 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Fri, 29 Nov 2024 23:41:44 +0100 Subject: [PATCH 020/159] payment info --- .../database/eshop/create_ticket_order.sql | 81 ++++++++++++++----- .../eshop/generate_variable_symbol.sql | 37 +++++++++ 2 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 scripts/database/eshop/generate_variable_symbol.sql diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index c66db7ab..d94d89ca 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -15,11 +15,16 @@ DECLARE item_id BIGINT; used_spots JSONB := '[]'::JSONB; occasion_id BIGINT; - item_details JSONB := '[]'::JSONB; + ticket_details JSONB := '[]'::JSONB; item_data RECORD; ticket_id BIGINT; order_item_ticket_id BIGINT; ticket_symbol TEXT; + ticket_items JSONB := '[]'::JSONB; + payment_info_id BIGINT; + generated_variable_symbol BIGINT; -- Explicitly renamed to avoid ambiguity + bank_account_id BIGINT; + form_id BIGINT; BEGIN -- Validate input data and extract occasion IF input_data IS NULL OR input_data->'occasion' IS NULL THEN @@ -27,15 +32,33 @@ BEGIN END IF; occasion_id := (input_data->'occasion')::BIGINT; + -- Extract form_id from input_data + IF input_data->'form' IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1002, 'message', 'Missing form ID in input data'); + END IF; + form_id := (input_data->'form')::BIGINT; + -- Validate occasion and fetch organization SELECT * INTO occasion_data FROM public.occasions WHERE id = occasion_id AND is_hidden = FALSE AND is_open = TRUE; IF occasion_data IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1002, 'message', 'Invalid or closed occasion'); + RETURN JSONB_BUILD_OBJECT('code', 1003, 'message', 'Invalid or closed occasion'); + END IF; + + -- Get bank_account_id for payment_info using form_id + SELECT bank_account INTO bank_account_id + FROM public.forms + WHERE id = form_id; + + IF bank_account_id IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1004, 'message', 'No bank account found for the given form'); END IF; + -- Generate variable symbol for payment + generated_variable_symbol := generate_variable_symbol(bank_account_id); + -- Process tickets FOR ticket_data IN SELECT * FROM JSONB_ARRAY_ELEMENTS(input_data->'ticket') LOOP -- Spot validation @@ -44,12 +67,12 @@ BEGIN WHERE id = (ticket_data->'spot')::BIGINT AND occasion = occasion_id; IF spot_data IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1003, 'message', 'Invalid or unrelated spot'); + RETURN JSONB_BUILD_OBJECT('code', 1005, 'message', 'Invalid or unrelated spot'); END IF; -- Check if spot is already used (order_item_ticket is not NULL) IF spot_data.order_item_ticket IS NOT NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1010, 'message', 'Spot is already reserved or in use'); + RETURN JSONB_BUILD_OBJECT('code', 1006, 'message', 'Spot is already reserved or in use'); END IF; -- Add spot to used spots @@ -58,10 +81,10 @@ BEGIN -- Secret validation spot_secret := (input_data->>'secret')::UUID; IF spot_data.secret IS DISTINCT FROM spot_secret THEN - RETURN JSONB_BUILD_OBJECT('code', 1004, 'message', 'Invalid secret for spot'); + RETURN JSONB_BUILD_OBJECT('code', 1007, 'message', 'Invalid secret for spot'); END IF; IF spot_data.secret_expiration_time < now THEN - RETURN JSONB_BUILD_OBJECT('code', 1005, 'message', 'Secret expired'); + RETURN JSONB_BUILD_OBJECT('code', 1008, 'message', 'Secret expired'); END IF; -- Generate ticket symbol @@ -72,6 +95,9 @@ BEGIN VALUES ('ordered', occasion_id, ticket_symbol, now, now) RETURNING id INTO ticket_id; + -- Initialize ticket items array + ticket_items := '[]'::JSONB; + -- Process taxi, food, and spot items FOREACH item_id IN ARRAY ARRAY[ (ticket_data->'taxi'->>'id')::BIGINT, @@ -92,16 +118,15 @@ BEGIN IF item_data IS NULL THEN RETURN JSONB_BUILD_OBJECT( - 'code', 1006, + 'code', 1009, 'message', 'Item not found or not part of occasion', 'details', item_id ); END IF; - -- Build item details, conditionally including spot-specific keys and ticket_id - item_details := item_details || JSONB_BUILD_OBJECT( + -- Add item details to ticket items + ticket_items := ticket_items || JSONB_BUILD_OBJECT( 'item_id', item_id, - 'ticket_id', ticket_id, 'title', item_data.title, 'type', item_data.type, 'price', item_data.final_price, @@ -125,10 +150,22 @@ BEGIN END IF; END IF; END LOOP; + + -- Add ticket with its items to ticket details + ticket_details := ticket_details || JSONB_BUILD_OBJECT( + 'ticket_id', ticket_id, + 'ticket_symbol', ticket_symbol, + 'items', ticket_items + ); END LOOP; - -- Create order with calculated price - INSERT INTO eshop.orders (created_at, updated_at, price, state, data, occasion) + -- Create payment info + INSERT INTO eshop.payment_info (bank_account, variable_symbol, amount, created_at) + VALUES (bank_account_id, generated_variable_symbol, calculated_price, now) + RETURNING id INTO payment_info_id; + + -- Create order with calculated price and payment info + INSERT INTO eshop.orders (created_at, updated_at, price, state, data, occasion, payment_info) VALUES ( now, now, calculated_price, 'ordered', JSONB_BUILD_OBJECT( @@ -138,27 +175,33 @@ BEGIN 'note', input_data->>'note', 'price', calculated_price ), - occasion_id + occasion_id, + payment_info_id ) RETURNING id INTO order_id; - -- Log order to orders_history with price + -- Log order to orders_history with price and tickets INSERT INTO eshop.orders_history (created_at, data, "order", state, price) VALUES ( now, - JSONB_BUILD_OBJECT('input_data', input_data, 'items', item_details, 'price', calculated_price), + JSONB_BUILD_OBJECT('input_data', input_data, 'tickets', ticket_details, 'price', calculated_price), order_id, 'ordered', calculated_price ); - -- Return success response with order ID and items + -- Return success response with order ID, tickets, and payment info RETURN JSONB_BUILD_OBJECT( 'code', 200, 'order_id', order_id, - 'items', item_details + 'tickets', ticket_details, + 'payment_info', JSONB_BUILD_OBJECT( + 'payment_info_id', payment_info_id, + 'variable_symbol', generated_variable_symbol, + 'amount', calculated_price + ) ); EXCEPTION WHEN OTHERS THEN - RETURN JSONB_BUILD_OBJECT('code', 1009, 'message', SQLERRM); + RETURN JSONB_BUILD_OBJECT('code', 1010, 'message', SQLERRM); END; -$$; +$$; \ No newline at end of file diff --git a/scripts/database/eshop/generate_variable_symbol.sql b/scripts/database/eshop/generate_variable_symbol.sql new file mode 100644 index 00000000..fe5bc5d3 --- /dev/null +++ b/scripts/database/eshop/generate_variable_symbol.sql @@ -0,0 +1,37 @@ +CREATE OR REPLACE FUNCTION generate_variable_symbol(bank_account_id BIGINT) +RETURNS BIGINT AS $$ +DECLARE + v_symbol BIGINT; + digit_count INT := 4; + attempt_count INT := 0; + max_attempts INT := 1; -- Maximum allowed attempts +BEGIN + LOOP + -- Generate a random number with the current digit count + v_symbol := FLOOR(RANDOM() * POWER(10, digit_count - 1)) + POWER(10, digit_count - 1); + + -- Ensure uniqueness within the bank_account + IF NOT EXISTS ( + SELECT 1 + FROM eshop.payment_info + WHERE v_symbol = v_symbol AND bank_account = bank_account_id + ) THEN + RETURN v_symbol; -- Return the unique variable symbol + END IF; + + -- Increment attempt count and adjust digit count after each failure + attempt_count := attempt_count + 1; + + -- Extend digit count after each failure + IF attempt_count >= max_attempts THEN + digit_count := digit_count + 1; + attempt_count := 0; -- Reset attempt count for the extended digit count + END IF; + + -- Fail entirely if the system cannot generate a unique symbol after many attempts + IF digit_count > 15 THEN -- Arbitrary upper limit for digit count + RAISE EXCEPTION 'Failed to generate unique variable symbol after too many attempts.'; + END IF; + END LOOP; +END; +$$ LANGUAGE plpgsql; From 3458681b872291260b8c9899f1f336aaf2e34f4d Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Fri, 29 Nov 2024 23:47:19 +0100 Subject: [PATCH 021/159] generate_ticket_symbol adjust --- scripts/database/eshop/generate_ticket_symbol.sql | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/database/eshop/generate_ticket_symbol.sql b/scripts/database/eshop/generate_ticket_symbol.sql index cf4091ba..f5023b4f 100644 --- a/scripts/database/eshop/generate_ticket_symbol.sql +++ b/scripts/database/eshop/generate_ticket_symbol.sql @@ -1,5 +1,8 @@ CREATE OR REPLACE FUNCTION generate_ticket_symbol(organization_id BIGINT, occasion_id BIGINT) -RETURNS TEXT AS $$ +RETURNS TEXT +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ DECLARE visible_id TEXT; org_suffix TEXT; @@ -38,4 +41,4 @@ BEGIN END IF; END LOOP; END; -$$ LANGUAGE plpgsql; +$$; From 7077047cfd1c5904bbf293006ba4f8c11de09d79 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 01:10:53 +0100 Subject: [PATCH 022/159] deadline --- .../database/eshop/create_ticket_order.sql | 60 ++++++++++++------- .../eshop/generate_variable_symbol.sql | 2 +- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index d94d89ca..5b871dc7 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -22,9 +22,11 @@ DECLARE ticket_symbol TEXT; ticket_items JSONB := '[]'::JSONB; payment_info_id BIGINT; - generated_variable_symbol BIGINT; -- Explicitly renamed to avoid ambiguity + generated_variable_symbol BIGINT; bank_account_id BIGINT; form_id BIGINT; + deadline TIMESTAMPTZ; + form_deadline_duration BIGINT; -- Renamed variable to avoid ambiguity BEGIN -- Validate input data and extract occasion IF input_data IS NULL OR input_data->'occasion' IS NULL THEN @@ -47,8 +49,8 @@ BEGIN RETURN JSONB_BUILD_OBJECT('code', 1003, 'message', 'Invalid or closed occasion'); END IF; - -- Get bank_account_id for payment_info using form_id - SELECT bank_account INTO bank_account_id + -- Get bank_account_id and deadline duration for payment_info using form_id + SELECT bank_account, deadline_duration_seconds INTO bank_account_id, form_deadline_duration FROM public.forms WHERE id = form_id; @@ -56,9 +58,35 @@ BEGIN RETURN JSONB_BUILD_OBJECT('code', 1004, 'message', 'No bank account found for the given form'); END IF; + -- Calculate deadline if form_deadline_duration is not null + IF form_deadline_duration IS NOT NULL THEN + deadline := now + make_interval(secs => form_deadline_duration); + ELSE + deadline := NULL; + END IF; + -- Generate variable symbol for payment generated_variable_symbol := generate_variable_symbol(bank_account_id); + -- Create payment info + INSERT INTO eshop.payment_info (bank_account, variable_symbol, amount, created_at, deadline) + VALUES (bank_account_id, generated_variable_symbol, calculated_price, now, deadline) + RETURNING id INTO payment_info_id; + + -- Create the order before processing tickets + INSERT INTO eshop.orders (created_at, updated_at, price, state, data, occasion, payment_info) + VALUES ( + now, now, 0, 'pending', + JSONB_BUILD_OBJECT( + 'name', input_data->>'name', + 'surname', input_data->>'surname', + 'email', input_data->>'email', + 'note', input_data->>'note' + ), + occasion_id, + payment_info_id + ) RETURNING id INTO order_id; + -- Process tickets FOR ticket_data IN SELECT * FROM JSONB_ARRAY_ELEMENTS(input_data->'ticket') LOOP -- Spot validation @@ -159,25 +187,10 @@ BEGIN ); END LOOP; - -- Create payment info - INSERT INTO eshop.payment_info (bank_account, variable_symbol, amount, created_at) - VALUES (bank_account_id, generated_variable_symbol, calculated_price, now) - RETURNING id INTO payment_info_id; - - -- Create order with calculated price and payment info - INSERT INTO eshop.orders (created_at, updated_at, price, state, data, occasion, payment_info) - VALUES ( - now, now, calculated_price, 'ordered', - JSONB_BUILD_OBJECT( - 'name', input_data->>'name', - 'surname', input_data->>'surname', - 'email', input_data->>'email', - 'note', input_data->>'note', - 'price', calculated_price - ), - occasion_id, - payment_info_id - ) RETURNING id INTO order_id; + -- Update the order with the calculated price + UPDATE eshop.orders + SET price = calculated_price, state = 'ordered' + WHERE id = order_id; -- Log order to orders_history with price and tickets INSERT INTO eshop.orders_history (created_at, data, "order", state, price) @@ -197,7 +210,8 @@ BEGIN 'payment_info', JSONB_BUILD_OBJECT( 'payment_info_id', payment_info_id, 'variable_symbol', generated_variable_symbol, - 'amount', calculated_price + 'amount', calculated_price, + 'deadline', deadline ) ); EXCEPTION diff --git a/scripts/database/eshop/generate_variable_symbol.sql b/scripts/database/eshop/generate_variable_symbol.sql index fe5bc5d3..c61b1b62 100644 --- a/scripts/database/eshop/generate_variable_symbol.sql +++ b/scripts/database/eshop/generate_variable_symbol.sql @@ -14,7 +14,7 @@ BEGIN IF NOT EXISTS ( SELECT 1 FROM eshop.payment_info - WHERE v_symbol = v_symbol AND bank_account = bank_account_id + WHERE variable_symbol = v_symbol AND bank_account = bank_account_id ) THEN RETURN v_symbol; -- Return the unique variable symbol END IF; From 35c44f8738e2fdd002c78c3a0ea733ddd22510be Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 01:39:06 +0100 Subject: [PATCH 023/159] fixes --- .../database/eshop/create_ticket_order.sql | 27 +++++++++---------- .../database/eshop/generate_ticket_symbol.sql | 19 ++++++------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index 5b871dc7..f1cec594 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -65,16 +65,8 @@ BEGIN deadline := NULL; END IF; - -- Generate variable symbol for payment - generated_variable_symbol := generate_variable_symbol(bank_account_id); - - -- Create payment info - INSERT INTO eshop.payment_info (bank_account, variable_symbol, amount, created_at, deadline) - VALUES (bank_account_id, generated_variable_symbol, calculated_price, now, deadline) - RETURNING id INTO payment_info_id; - -- Create the order before processing tickets - INSERT INTO eshop.orders (created_at, updated_at, price, state, data, occasion, payment_info) + INSERT INTO eshop.orders (created_at, updated_at, price, state, data, occasion) VALUES ( now, now, 0, 'pending', JSONB_BUILD_OBJECT( @@ -83,8 +75,7 @@ BEGIN 'email', input_data->>'email', 'note', input_data->>'note' ), - occasion_id, - payment_info_id + occasion_id ) RETURNING id INTO order_id; -- Process tickets @@ -187,9 +178,17 @@ BEGIN ); END LOOP; - -- Update the order with the calculated price + -- Generate variable symbol for payment + generated_variable_symbol := generate_variable_symbol(bank_account_id); + + -- Insert payment info after calculating price + INSERT INTO eshop.payment_info (bank_account, variable_symbol, amount, created_at, deadline) + VALUES (bank_account_id, generated_variable_symbol, calculated_price, now, deadline) + RETURNING id INTO payment_info_id; + + -- Update the order with the calculated price and payment info UPDATE eshop.orders - SET price = calculated_price, state = 'ordered' + SET price = calculated_price, state = 'ordered', payment_info = payment_info_id WHERE id = order_id; -- Log order to orders_history with price and tickets @@ -218,4 +217,4 @@ EXCEPTION WHEN OTHERS THEN RETURN JSONB_BUILD_OBJECT('code', 1010, 'message', SQLERRM); END; -$$; \ No newline at end of file +$$; diff --git a/scripts/database/eshop/generate_ticket_symbol.sql b/scripts/database/eshop/generate_ticket_symbol.sql index f5023b4f..19bff6e2 100644 --- a/scripts/database/eshop/generate_ticket_symbol.sql +++ b/scripts/database/eshop/generate_ticket_symbol.sql @@ -1,8 +1,5 @@ CREATE OR REPLACE FUNCTION generate_ticket_symbol(organization_id BIGINT, occasion_id BIGINT) -RETURNS TEXT -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ +RETURNS TEXT AS $$ DECLARE visible_id TEXT; org_suffix TEXT; @@ -15,17 +12,21 @@ BEGIN org_suffix := LPAD((organization_id % 100)::TEXT, 2, '0'); -- Ensure 2 digits occasion_suffix := LPAD((occasion_id % 100)::TEXT, 2, '0'); -- Ensure 2 digits + -- Replace '0' with 'X' in the suffixes + org_suffix := REPLACE(org_suffix, '0', 'X'); + occasion_suffix := REPLACE(occasion_suffix, '0', 'X'); + LOOP -- Generate alternating numeric and alphabetic parts visible_id := org_suffix || occasion_suffix || - SUBSTRING(numeric_part FROM FLOOR(RANDOM() * 10 + 1)::INT FOR 1) || + SUBSTRING(numeric_part FROM FLOOR(RANDOM() * 9 + 1)::INT FOR 1) || SUBSTRING(alphabetic_part FROM FLOOR(RANDOM() * 20 + 1)::INT FOR 1) || - SUBSTRING(numeric_part FROM FLOOR(RANDOM() * 10 + 1)::INT FOR 1) || + SUBSTRING(numeric_part FROM FLOOR(RANDOM() * 9 + 1)::INT FOR 1) || SUBSTRING(alphabetic_part FROM FLOOR(RANDOM() * 20 + 1)::INT FOR 1) || - SUBSTRING(numeric_part FROM FLOOR(RANDOM() * 10 + 1)::INT FOR 1) || + SUBSTRING(numeric_part FROM FLOOR(RANDOM() * 9 + 1)::INT FOR 1) || SUBSTRING(alphabetic_part FROM FLOOR(RANDOM() * 20 + 1)::INT FOR 1); - -- Ensure uniqueness in the database (within the organization) + -- Ensure uniqueness in the database (within the occasion) IF NOT EXISTS ( SELECT 1 FROM eshop.tickets @@ -41,4 +42,4 @@ BEGIN END IF; END LOOP; END; -$$; +$$ LANGUAGE plpgsql; From 1827f0ab5272ca07f5b02f922a682c6703bee85d Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 02:49:28 +0100 Subject: [PATCH 024/159] send-ticket-order --- supabase/functions/send-ticket-order/index.ts | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 supabase/functions/send-ticket-order/index.ts diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts new file mode 100644 index 00000000..ed19a82f --- /dev/null +++ b/supabase/functions/send-ticket-order/index.ts @@ -0,0 +1,111 @@ +import { sendEmailWithSubs } from "../_shared/emailClient.ts"; +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.38.4'; + +const _DEFAULT_EMAIL = Deno.env.get("DEFAULT_EMAIL")!; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +}; + +const supabaseAdmin = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! +); + +Deno.serve(async (req) => { + try { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + const reqData = await req.json(); + const userId = reqData.usr; // ID of the user to invite + const occasionId = reqData.oc; // ID of the occasion + + + const { data: answer, error: passwordSetError } = await supabaseAdmin.rpc("set_user_password", + { + usr: userId, + oc: occasionId, + password: code + }); + + const { data: occasionData, error: occasionError } = await supabaseAdmin + .from("occasions") + .select("organization") + .eq("id", occasionId) + .single(); + + if (occasionError) { + console.error("Occasion not found."); + return new Response(JSON.stringify({ error: "Occasion not found" }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 404, + }); + } + + const organizationId = occasionData.organization; + + const orgData = await supabaseAdmin + .from("organizations") + .select("data") + .eq("id", organizationId) + .single(); + + const orgConfig = orgData.data.data; + const appName = orgConfig.APP_NAME || ""; + const defaultUrl = orgConfig.DEFAULT_URL || ""; + + // Fetch email template based on the organization + const template = await supabaseAdmin + .from("email_templates") + .select() + .eq("organization", organizationId) + .eq("id", "SIGN_IN_CODE") + .single(); + + if (template.error || !template.data) { + console.error("Email template not found for the organization."); + return new Response(JSON.stringify({ error: "Email template not found" }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 404, + }); + } + + const subs = { + code: code, + email: occasionUser.data.data.email, // Access the user's email from the data field + }; + + await sendEmailWithSubs({ + to: occasionUser.data.data.email, // Use the user’s email from the data field + subject: template.data.subject, + content: template.data.html, + subs, + from: `${appName} | Festapp <${_DEFAULT_EMAIL}>`, + }); + + await supabaseAdmin + .from("log_emails") + .insert({ + "from": _DEFAULT_EMAIL, + "to": occasionUser.data.data.email, + "template": template.data.id, + "organization": organizationId + }); + + + return new Response(JSON.stringify({ "user": userId, "code": 200 }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 200, + }); + + } catch (error) { + console.error("Unexpected error:", error); + return new Response(JSON.stringify({ error: "Unexpected error occurred" }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 500, + }); + } +}); From bf33d100ae2cb669e22bca5183794c2077071dbe Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 03:09:00 +0100 Subject: [PATCH 025/159] deno function refactor --- supabase/functions/email/index.ts | 2 +- supabase/functions/notify/index.ts | 6 +++--- supabase/functions/register/index.ts | 2 +- .../functions/send-reset-password-link/index.ts | 16 ++++++++-------- supabase/functions/send-sign-in-code/index.ts | 14 +++++++------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/supabase/functions/email/index.ts b/supabase/functions/email/index.ts index 3b108756..c67f9845 100644 --- a/supabase/functions/email/index.ts +++ b/supabase/functions/email/index.ts @@ -71,7 +71,7 @@ Deno.serve(async (req) => { const template = await supabaseAdmin .from("email_templates") .select() - .eq("id", "RESET_PASSWORD") + .eq("code", "RESET_PASSWORD") .eq("organization", organization) .single(); diff --git a/supabase/functions/notify/index.ts b/supabase/functions/notify/index.ts index 81844678..d03deb9c 100644 --- a/supabase/functions/notify/index.ts +++ b/supabase/functions/notify/index.ts @@ -1,6 +1,6 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.38.4' -const _supabase = createClient( +const supabaseAdmin = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! ); @@ -11,7 +11,7 @@ Deno.serve(async (req) => { const url = 'https://onesignal.com/api/v1/notifications'; // Fetch organization data to get ONESIGNAL_APP_ID, ONESIGNAL_REST_API_KEY, and DEFAULT_URL - const orgData = await _supabase + const orgData = await supabaseAdmin .from("organizations") .select("data") .eq("id", organizationId) @@ -43,7 +43,7 @@ Deno.serve(async (req) => { } // Fetch the link for the current occasion - const currentLink = await _supabase + const currentLink = await supabaseAdmin .from("occasions") .select("link") .eq("id", record.occasion) diff --git a/supabase/functions/register/index.ts b/supabase/functions/register/index.ts index aa17a1c8..0ea34946 100644 --- a/supabase/functions/register/index.ts +++ b/supabase/functions/register/index.ts @@ -77,7 +77,7 @@ Deno.serve(async (req) => { const template = await supabaseAdmin .from("email_templates") .select() - .eq("id", "SIGN_IN_CODE") + .eq("code", "SIGN_IN_CODE") .eq("organization", organizationId) .single(); diff --git a/supabase/functions/send-reset-password-link/index.ts b/supabase/functions/send-reset-password-link/index.ts index ca756a07..515af0d9 100644 --- a/supabase/functions/send-reset-password-link/index.ts +++ b/supabase/functions/send-reset-password-link/index.ts @@ -2,7 +2,7 @@ import { sendEmailWithSubs } from "../_shared/emailClient.ts"; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.38.4'; const _DEFAULT_EMAIL = Deno.env.get("DEFAULT_EMAIL")!; -const _supabase = createClient( +const supabaseAdmin = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! ); @@ -21,7 +21,7 @@ Deno.serve(async (req) => { const userEmail = reqData.email ? reqData.email.toLowerCase() : "bujnmi@gmail.com"; const organizationId = reqData.organization; - const orgData = await _supabase + const orgData = await supabaseAdmin .from("organizations") .select("data") .eq("id", organizationId) @@ -39,7 +39,7 @@ Deno.serve(async (req) => { const appName = orgConfig.APP_NAME || "DefaultAppName"; const defaultUrl = orgConfig.DEFAULT_URL || "http://default.url"; - const userData = await _supabase + const userData = await supabaseAdmin .from("user_info") .select() .eq("organization", organizationId) @@ -56,22 +56,22 @@ Deno.serve(async (req) => { const userId = userData.data.id; const token = crypto.randomUUID(); - await _supabase + await supabaseAdmin .from("user_reset_token") .delete() .eq("user", userId); - await _supabase + await supabaseAdmin .from("user_reset_token") .insert({ "user": userId, "token": token, }); - const template = await _supabase + const template = await supabaseAdmin .from("email_templates") .select() - .eq("id", "RESET_PASSWORD") + .eq("code", "RESET_PASSWORD") .eq("organization", organizationId) .single(); @@ -96,7 +96,7 @@ Deno.serve(async (req) => { from: `${appName} | Festapp <${_DEFAULT_EMAIL}>`, }); - await _supabase + await supabaseAdmin .from("log_emails") .insert({ "from": _DEFAULT_EMAIL, diff --git a/supabase/functions/send-sign-in-code/index.ts b/supabase/functions/send-sign-in-code/index.ts index ab2d1606..a78e4174 100644 --- a/supabase/functions/send-sign-in-code/index.ts +++ b/supabase/functions/send-sign-in-code/index.ts @@ -19,7 +19,7 @@ Deno.serve(async (req) => { return new Response('ok', { headers: corsHeaders }); } - const supabase = createClient( + const supabaseUser = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_ANON_KEY') ?? '', { global: { headers: { Authorization: req.headers.get('Authorization')! } } } @@ -32,7 +32,7 @@ Deno.serve(async (req) => { const code = Math.floor(100000 + Math.random() * 900000).toString(); // this function will also check if requester is manager on the occasion or if requester is admin - const { data: answer, error: passwordSetError } = await supabase.rpc("set_user_password", + const { data: answer, error: passwordSetError } = await supabaseUser.rpc("set_user_password", { usr: userId, oc: occasionId, @@ -47,7 +47,7 @@ Deno.serve(async (req) => { }); } - const occasionUser = await supabase + const occasionUser = await supabaseAdmin .from("occasion_users") .select("data") .eq("user", userId) @@ -62,7 +62,7 @@ Deno.serve(async (req) => { }); } - const { data: occasionData, error: occasionError } = await supabase + const { data: occasionData, error: occasionError } = await supabaseAdmin .from("occasions") .select("organization") .eq("id", occasionId) @@ -89,11 +89,11 @@ Deno.serve(async (req) => { const defaultUrl = orgConfig.DEFAULT_URL || "http://default.url"; // Fetch email template based on the organization - const template = await supabase + const template = await supabaseAdmin .from("email_templates") .select() .eq("organization", organizationId) - .eq("id", "SIGN_IN_CODE") + .eq("code", "SIGN_IN_CODE") .single(); if (template.error || !template.data) { @@ -127,7 +127,7 @@ Deno.serve(async (req) => { }); occasionUser.data.data.is_invited = true; - const { error: updateError } = await supabase + const { error: updateError } = await supabaseAdmin .from("occasion_users") .update({ data: occasionUser.data.data }) // Preserve other data, update only is_invited .eq("user", userId) From 9d236c2a6b31a7be381ccca660c070801bd6c154 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 03:28:21 +0100 Subject: [PATCH 026/159] new pw fix eye --- lib/pages/SignupPasswordPage.dart | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/lib/pages/SignupPasswordPage.dart b/lib/pages/SignupPasswordPage.dart index d2c404f0..6e8dc3a0 100644 --- a/lib/pages/SignupPasswordPage.dart +++ b/lib/pages/SignupPasswordPage.dart @@ -7,8 +7,8 @@ import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/styles/StylesConfig.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; +import 'package:fstapp/widgets/PasswordField.dart'; @RoutePage() class ResetPasswordPage extends StatefulWidget { @@ -77,21 +77,7 @@ class _ResetPasswordPageState extends State { ), Padding( padding: const EdgeInsets.symmetric(horizontal: 15), - child: TextFormField( - controller: _passwordController, - autofillHints: const [AutofillHints.password], - keyboardType: TextInputType.text, - obscureText: true, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: "New Password".tr()), - validator: (String? value) { - if (value!.isEmpty) { - return "Fill the password!".tr(); - } - return null; - }, - )), + child: PasswordField(label: "New Password".tr(), controller: _passwordController, passwordType: AutofillHints.password)), const SizedBox( height: 16, ), From 5880ac9f45a918593eca760b7d5992faf12bd122 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 04:25:31 +0100 Subject: [PATCH 027/159] mail adjust --- supabase/functions/send-ticket-order/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index ed19a82f..04994bd0 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -61,12 +61,12 @@ Deno.serve(async (req) => { const template = await supabaseAdmin .from("email_templates") .select() - .eq("organization", organizationId) - .eq("id", "SIGN_IN_CODE") + .eq("occasion", occasionId) + .eq("code", "TICKET_ORDER_CONFIRMATION") .single(); if (template.error || !template.data) { - console.error("Email template not found for the organization."); + console.error("Email template not found for the occasion."); return new Response(JSON.stringify({ error: "Email template not found" }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 404, From f8b388f6a4a3bec7fb147ef6a6c5b06be20f48c1 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 04:51:26 +0100 Subject: [PATCH 028/159] adjust --- .../database/eshop/create_ticket_order.sql | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index f1cec594..e05a1fb5 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -8,13 +8,13 @@ DECLARE ticket_data JSONB; spot_data RECORD; spot_id BIGINT; - occasion_data RECORD; now TIMESTAMP WITH TIME ZONE := NOW(); calculated_price DOUBLE PRECISION := 0; spot_secret UUID; item_id BIGINT; used_spots JSONB := '[]'::JSONB; occasion_id BIGINT; + organization_id BIGINT; ticket_details JSONB := '[]'::JSONB; item_data RECORD; ticket_id BIGINT; @@ -24,38 +24,36 @@ DECLARE payment_info_id BIGINT; generated_variable_symbol BIGINT; bank_account_id BIGINT; - form_id BIGINT; + form_key UUID; deadline TIMESTAMPTZ; - form_deadline_duration BIGINT; -- Renamed variable to avoid ambiguity + form_deadline_duration BIGINT; BEGIN - -- Validate input data and extract occasion - IF input_data IS NULL OR input_data->'occasion' IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1001, 'message', 'Invalid input data or missing occasion'); + -- Validate input data and extract form key + IF input_data IS NULL OR input_data->'form' IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1001, 'message', 'Missing form key in input data'); END IF; - occasion_id := (input_data->'occasion')::BIGINT; + form_key := (input_data->>'form')::UUID; - -- Extract form_id from input_data - IF input_data->'form' IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1002, 'message', 'Missing form ID in input data'); - END IF; - form_id := (input_data->'form')::BIGINT; - - -- Validate occasion and fetch organization - SELECT * INTO occasion_data - FROM public.occasions - WHERE id = occasion_id AND is_hidden = FALSE AND is_open = TRUE; + -- Fetch form data, including occasion, bank account, and deadline duration + SELECT occasion, bank_account, deadline_duration_seconds + INTO occasion_id, bank_account_id, form_deadline_duration + FROM public.forms + WHERE key = form_key; - IF occasion_data IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1003, 'message', 'Invalid or closed occasion'); + IF occasion_id IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1002, 'message', 'Form is not linked to any occasion'); + END IF; + IF bank_account_id IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1003, 'message', 'Form is not linked to any bank account'); END IF; - -- Get bank_account_id and deadline duration for payment_info using form_id - SELECT bank_account, deadline_duration_seconds INTO bank_account_id, form_deadline_duration - FROM public.forms - WHERE id = form_id; + -- Fetch organization_id from the occasion + SELECT organization INTO organization_id + FROM public.occasions + WHERE id = occasion_id; - IF bank_account_id IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1004, 'message', 'No bank account found for the given form'); + IF organization_id IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1004, 'message', 'No organization found for the occasion'); END IF; -- Calculate deadline if form_deadline_duration is not null @@ -107,7 +105,7 @@ BEGIN END IF; -- Generate ticket symbol - ticket_symbol := generate_ticket_symbol(occasion_data.organization, occasion_id); + ticket_symbol := generate_ticket_symbol(organization_id, occasion_id); -- Create ticket with ticket symbol INSERT INTO eshop.tickets (state, occasion, ticket_symbol, created_at, updated_at) From 9beb2195f4e683d6731b9a8652a00ed8c36f5773 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 05:21:20 +0100 Subject: [PATCH 029/159] return more --- .../database/eshop/create_ticket_order.sql | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index e05a1fb5..1adeab74 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -15,6 +15,8 @@ DECLARE used_spots JSONB := '[]'::JSONB; occasion_id BIGINT; organization_id BIGINT; + occasion_title TEXT; + account_number TEXT; ticket_details JSONB := '[]'::JSONB; item_data RECORD; ticket_id BIGINT; @@ -32,6 +34,11 @@ BEGIN IF input_data IS NULL OR input_data->'form' IS NULL THEN RETURN JSONB_BUILD_OBJECT('code', 1001, 'message', 'Missing form key in input data'); END IF; + + IF input_data->>'email' IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1002, 'message', 'Missing email in input data'); + END IF; + form_key := (input_data->>'form')::UUID; -- Fetch form data, including occasion, bank account, and deadline duration @@ -41,19 +48,28 @@ BEGIN WHERE key = form_key; IF occasion_id IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1002, 'message', 'Form is not linked to any occasion'); + RETURN JSONB_BUILD_OBJECT('code', 1003, 'message', 'Form is not linked to any occasion'); END IF; IF bank_account_id IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1003, 'message', 'Form is not linked to any bank account'); + RETURN JSONB_BUILD_OBJECT('code', 1004, 'message', 'Form is not linked to any bank account'); END IF; - -- Fetch organization_id from the occasion - SELECT organization INTO organization_id + -- Fetch organization_id and occasion_title from the occasion + SELECT organization, title INTO organization_id, occasion_title FROM public.occasions WHERE id = occasion_id; IF organization_id IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1004, 'message', 'No organization found for the occasion'); + RETURN JSONB_BUILD_OBJECT('code', 1005, 'message', 'No organization found for the occasion'); + END IF; + + -- Fetch account number from the bank account + SELECT bank_accounts.account_number INTO account_number + FROM eshop.bank_accounts + WHERE id = bank_account_id; + + IF account_number IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 1006, 'message', 'No account number found for the bank account'); END IF; -- Calculate deadline if form_deadline_duration is not null @@ -67,12 +83,7 @@ BEGIN INSERT INTO eshop.orders (created_at, updated_at, price, state, data, occasion) VALUES ( now, now, 0, 'pending', - JSONB_BUILD_OBJECT( - 'name', input_data->>'name', - 'surname', input_data->>'surname', - 'email', input_data->>'email', - 'note', input_data->>'note' - ), + input_data, -- Store all input_data directly occasion_id ) RETURNING id INTO order_id; @@ -84,12 +95,12 @@ BEGIN WHERE id = (ticket_data->'spot')::BIGINT AND occasion = occasion_id; IF spot_data IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1005, 'message', 'Invalid or unrelated spot'); + RETURN JSONB_BUILD_OBJECT('code', 1007, 'message', 'Invalid or unrelated spot'); END IF; -- Check if spot is already used (order_item_ticket is not NULL) IF spot_data.order_item_ticket IS NOT NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 1006, 'message', 'Spot is already reserved or in use'); + RETURN JSONB_BUILD_OBJECT('code', 1008, 'message', 'Spot is already reserved or in use'); END IF; -- Add spot to used spots @@ -98,10 +109,10 @@ BEGIN -- Secret validation spot_secret := (input_data->>'secret')::UUID; IF spot_data.secret IS DISTINCT FROM spot_secret THEN - RETURN JSONB_BUILD_OBJECT('code', 1007, 'message', 'Invalid secret for spot'); + RETURN JSONB_BUILD_OBJECT('code', 1009, 'message', 'Invalid secret for spot'); END IF; IF spot_data.secret_expiration_time < now THEN - RETURN JSONB_BUILD_OBJECT('code', 1008, 'message', 'Secret expired'); + RETURN JSONB_BUILD_OBJECT('code', 1010, 'message', 'Secret expired'); END IF; -- Generate ticket symbol @@ -135,7 +146,7 @@ BEGIN IF item_data IS NULL THEN RETURN JSONB_BUILD_OBJECT( - 'code', 1009, + 'code', 1011, 'message', 'Item not found or not part of occasion', 'details', item_id ); @@ -208,11 +219,16 @@ BEGIN 'payment_info_id', payment_info_id, 'variable_symbol', generated_variable_symbol, 'amount', calculated_price, - 'deadline', deadline + 'deadline', deadline, + 'accountNumber', account_number + ), + 'occasion', JSONB_BUILD_OBJECT( + 'id', occasion_id, + 'occasionTitle', occasion_title ) ); EXCEPTION WHEN OTHERS THEN - RETURN JSONB_BUILD_OBJECT('code', 1010, 'message', SQLERRM); + RETURN JSONB_BUILD_OBJECT('code', 1012, 'message', SQLERRM); END; $$; From fd206172bbf6d15744e1c84e6ac0c7f179e3ba3c Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 07:03:31 +0100 Subject: [PATCH 030/159] sending order mail --- lib/dataServices/DbEshop.dart | 4 + .../database/eshop/create_ticket_order.sql | 6 +- scripts/database/eshop/validate_item_type.sql | 2 +- supabase/functions/_shared/emailClient.ts | 8 +- supabase/functions/send-ticket-order/index.ts | 126 ++++++++++-------- 5 files changed, 86 insertions(+), 60 deletions(-) diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index a7ca3929..206a3b1c 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -36,6 +36,10 @@ class DbEshop { return infoList; } + static Future sendTicketOrder(Map data) async { + return await _supabase.functions.invoke("send-ticket-order", body: {"orderDetails": {}}); + } + static Future getForm(String formKey) async { final response = await _supabase .rpc('get_form', params: {'form_key': formKey}); diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index 1adeab74..08f79f4a 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION eshop.create_ticket_order(input_data JSONB) +CREATE OR REPLACE FUNCTION create_ticket_order(input_data JSONB) RETURNS JSONB LANGUAGE plpgsql SECURITY DEFINER @@ -220,11 +220,11 @@ BEGIN 'variable_symbol', generated_variable_symbol, 'amount', calculated_price, 'deadline', deadline, - 'accountNumber', account_number + 'account_number', account_number ), 'occasion', JSONB_BUILD_OBJECT( 'id', occasion_id, - 'occasionTitle', occasion_title + 'occasion_title', occasion_title ) ); EXCEPTION diff --git a/scripts/database/eshop/validate_item_type.sql b/scripts/database/eshop/validate_item_type.sql index 4ad647fc..c6cda857 100644 --- a/scripts/database/eshop/validate_item_type.sql +++ b/scripts/database/eshop/validate_item_type.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION eshop.validate_item_type(item_id BIGINT, required_type TEXT) +CREATE OR REPLACE FUNCTION validate_item_type(item_id BIGINT, required_type TEXT) RETURNS BOOLEAN LANGUAGE plpgsql AS $$ diff --git a/supabase/functions/_shared/emailClient.ts b/supabase/functions/_shared/emailClient.ts index 3048be4f..55786e09 100644 --- a/supabase/functions/_shared/emailClient.ts +++ b/supabase/functions/_shared/emailClient.ts @@ -58,12 +58,14 @@ export async function sendEmailWithSubs({ from?: string; }) { // Replace placeholders in content with values from subs - let html = content; + let processedSubject = subject; + let processedHtml = content; for (const [key, value] of Object.entries(subs)) { const placeholder = `{{${key}}}`; - html = html.replaceAll(placeholder, value); + processedHtml = processedHtml.replaceAll(placeholder, value); + processedSubject = processedSubject.replaceAll(placeholder, value); } // Use the sendEmail function to send the processed email - await sendEmail({ to, subject, html, from }); + await sendEmail({ to, subject: processedSubject, html: processedHtml, from }); } diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index 04994bd0..1517e98d 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -20,83 +20,103 @@ Deno.serve(async (req) => { } const reqData = await req.json(); - const userId = reqData.usr; // ID of the user to invite - const occasionId = reqData.oc; // ID of the occasion - + const { orderDetails } = reqData; // Ticket details from request + + // Hardcoded orderDetails + const hardcodedOrderDetails = { + secret: "0fb80818-4c8d-4eb7-8205-859b1d786fb3", + form: "7f4e3892-a544-4385-b933-61117e9755c3", + name: "Jan", + surname: "Vicha", + email: "bujnmi@gmail.com", + note: "a", + ticket: [ + { + taxi: { + name: "Bez odvozu", + id: 11, + price: 0, + }, + food: { + name: "Ratatouille s br. kaší (bez lepku a masa) (KČ160.00)", + id: 3, + price: 160, + }, + note: null, + spot: 1, + }, + ], + }; - const { data: answer, error: passwordSetError } = await supabaseAdmin.rpc("set_user_password", - { - usr: userId, - oc: occasionId, - password: code + // Call the `create_ticket_order` function + const { data: ticketOrder, error: ticketError } = await supabaseAdmin.rpc("create_ticket_order", { + input_data: hardcodedOrderDetails, }); - const { data: occasionData, error: occasionError } = await supabaseAdmin - .from("occasions") - .select("organization") - .eq("id", occasionId) - .single(); - - if (occasionError) { - console.error("Occasion not found."); - return new Response(JSON.stringify({ error: "Occasion not found" }), { + if (ticketError || ticketOrder.code != 200) { + console.error("Error creating ticket order:", ticketError); + return new Response(JSON.stringify({ error: "Failed to create ticket order. Error code: " + ticketOrder.code }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, - status: 404, + status: 500, }); } - const organizationId = occasionData.organization; + const occasion = ticketOrder.occasion; + const paymentInfo = ticketOrder.payment_info; - const orgData = await supabaseAdmin - .from("organizations") - .select("data") - .eq("id", organizationId) - .single(); + const { data: occasionData, error: occasionError } = await supabaseAdmin + .from("occasions") + .select("organization") + .eq("id", occasion.id) + .single(); - const orgConfig = orgData.data.data; - const appName = orgConfig.APP_NAME || ""; - const defaultUrl = orgConfig.DEFAULT_URL || ""; + const organizationId = occasionData.organization; - // Fetch email template based on the organization const template = await supabaseAdmin - .from("email_templates") - .select() - .eq("occasion", occasionId) - .eq("code", "TICKET_ORDER_CONFIRMATION") - .single(); + .from("email_templates") + .select() + .eq("organization", organizationId) + .eq("occasion", occasion.id) + .eq("code", "TICKET_ORDER_CONFIRMATION") + .single(); if (template.error || !template.data) { - console.error("Email template not found for the occasion."); - return new Response(JSON.stringify({ error: "Email template not found" }), { - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, - status: 404, - }); - } + console.error("Email template not found for the occasion."); + return new Response(JSON.stringify({ error: "Email template not found" }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 404, + }); + } const subs = { - code: code, - email: occasionUser.data.data.email, // Access the user's email from the data field + occasionTitle: occasion.occasion_title, + price: paymentInfo.amount, + accountNumber: paymentInfo.account_number, + variableSymbol: paymentInfo.variable_symbol, + deadline: paymentInfo.deadline, + fullOrder: "nope", }; + // Send email using the substitutions await sendEmailWithSubs({ - to: occasionUser.data.data.email, // Use the user’s email from the data field + to: hardcodedOrderDetails.email, subject: template.data.subject, content: template.data.html, subs, - from: `${appName} | Festapp <${_DEFAULT_EMAIL}>`, - }); - - await supabaseAdmin - .from("log_emails") - .insert({ - "from": _DEFAULT_EMAIL, - "to": occasionUser.data.data.email, - "template": template.data.id, - "organization": organizationId + from: `${occasion.occasion_title} | Festapp <${_DEFAULT_EMAIL}>`, }); + await supabaseAdmin + .from("log_emails") + .insert({ + "from": _DEFAULT_EMAIL, + "to": hardcodedOrderDetails.email, + "template": "TICKET_ORDER_CONFIRMATION", + "organization": occasion.id, + "occasion": occasion.id, + }); - return new Response(JSON.stringify({ "user": userId, "code": 200 }), { + return new Response(JSON.stringify({ "ticketOrder": ticketOrder, "code": 200 }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 200, }); From 56be8ba87f93c704624aebe517564826deee5538 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 07:21:16 +0100 Subject: [PATCH 031/159] date formatting --- supabase/functions/send-ticket-order/index.ts | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index 1517e98d..46171a7a 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -13,6 +13,17 @@ const supabaseAdmin = createClient( Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! ); +function formatDatetime(datetime: string): string { + const date = new Date(datetime); + return new Intl.DateTimeFormat("cs-CZ", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }).format(date); +} + Deno.serve(async (req) => { try { if (req.method === 'OPTIONS') { @@ -20,9 +31,8 @@ Deno.serve(async (req) => { } const reqData = await req.json(); - const { orderDetails } = reqData; // Ticket details from request + const { orderDetails } = reqData; - // Hardcoded orderDetails const hardcodedOrderDetails = { secret: "0fb80818-4c8d-4eb7-8205-859b1d786fb3", form: "7f4e3892-a544-4385-b933-61117e9755c3", @@ -48,12 +58,11 @@ Deno.serve(async (req) => { ], }; - // Call the `create_ticket_order` function const { data: ticketOrder, error: ticketError } = await supabaseAdmin.rpc("create_ticket_order", { input_data: hardcodedOrderDetails, }); - if (ticketError || ticketOrder.code != 200) { + if (ticketError || ticketOrder.code !== 200) { console.error("Error creating ticket order:", ticketError); return new Response(JSON.stringify({ error: "Failed to create ticket order. Error code: " + ticketOrder.code }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, @@ -65,39 +74,40 @@ Deno.serve(async (req) => { const paymentInfo = ticketOrder.payment_info; const { data: occasionData, error: occasionError } = await supabaseAdmin - .from("occasions") - .select("organization") - .eq("id", occasion.id) - .single(); + .from("occasions") + .select("organization") + .eq("id", occasion.id) + .single(); const organizationId = occasionData.organization; const template = await supabaseAdmin - .from("email_templates") - .select() - .eq("organization", organizationId) - .eq("occasion", occasion.id) - .eq("code", "TICKET_ORDER_CONFIRMATION") - .single(); + .from("email_templates") + .select() + .eq("organization", organizationId) + .eq("occasion", occasion.id) + .eq("code", "TICKET_ORDER_CONFIRMATION") + .single(); if (template.error || !template.data) { - console.error("Email template not found for the occasion."); - return new Response(JSON.stringify({ error: "Email template not found" }), { - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, - status: 404, - }); - } + console.error("Email template not found for the occasion."); + return new Response(JSON.stringify({ error: "Email template not found" }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 404, + }); + } + + const formattedDeadline = formatDatetime(paymentInfo.deadline); const subs = { occasionTitle: occasion.occasion_title, price: paymentInfo.amount, accountNumber: paymentInfo.account_number, variableSymbol: paymentInfo.variable_symbol, - deadline: paymentInfo.deadline, + deadline: formattedDeadline, fullOrder: "nope", }; - // Send email using the substitutions await sendEmailWithSubs({ to: hardcodedOrderDetails.email, subject: template.data.subject, From ba70b195eb2000d474e9b50a9d97302c8fb37900 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 09:07:35 +0100 Subject: [PATCH 032/159] sending order --- lib/dataModels/FormModel.dart | 4 +- lib/dataModels/Tb.dart | 2 +- lib/dataServices/DbEshop.dart | 4 +- lib/pages/FormPage.dart | 30 +++++++- scripts/database/eshop/get_form.sql | 2 +- supabase/functions/_shared/emailClient.ts | 14 +++- supabase/functions/send-ticket-order/index.ts | 74 +++++++++++-------- 7 files changed, 89 insertions(+), 41 deletions(-) diff --git a/lib/dataModels/FormModel.dart b/lib/dataModels/FormModel.dart index b9a709b9..31d951c5 100644 --- a/lib/dataModels/FormModel.dart +++ b/lib/dataModels/FormModel.dart @@ -36,7 +36,7 @@ class FormModel { occasion: json[Tb.forms.occasion], type: json[Tb.forms.type], bankAccount: json[Tb.forms.bank_account], - deadlineDuration: json[Tb.forms.deadline_duration], + deadlineDuration: json[Tb.forms.deadline_duration_seconds], isOpen: json[Tb.forms.is_open], accountNumber: json['account_number'], // Assuming it's added to the RPC response ); @@ -50,7 +50,7 @@ class FormModel { Tb.forms.occasion: occasion, Tb.forms.type: type, Tb.forms.bank_account: bankAccount, - Tb.forms.deadline_duration: deadlineDuration, + Tb.forms.deadline_duration_seconds: deadlineDuration, Tb.forms.is_open: isOpen, 'account_number': accountNumber, // Including in serialization }; diff --git a/lib/dataModels/Tb.dart b/lib/dataModels/Tb.dart index 228b5f7b..fa0b6ad8 100644 --- a/lib/dataModels/Tb.dart +++ b/lib/dataModels/Tb.dart @@ -323,6 +323,6 @@ class FormsTb { String get occasion => "occasion"; String get type => "type"; String get bank_account => "bank_account"; - String get deadline_duration => "deadline_duration"; + String get deadline_duration_seconds => "deadline_duration_seconds"; String get is_open => "is_open"; } \ No newline at end of file diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 206a3b1c..ef07cf6d 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -36,8 +36,8 @@ class DbEshop { return infoList; } - static Future sendTicketOrder(Map data) async { - return await _supabase.functions.invoke("send-ticket-order", body: {"orderDetails": {}}); + static Future sendTicketOrder(Map data) async { + return await _supabase.functions.invoke("send-ticket-order", body: {"orderDetails": data}); } static Future getForm(String formKey) async { diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index d27bc3da..e19ae989 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -160,8 +160,34 @@ class _FormPageState extends State { formData = data; - // Simulate sending process - await Future.delayed(const Duration(seconds: 2)); + var response = await DbEshop.sendTicketOrder({ + "secret": "0fb80818-4c8d-4eb7-8205-859b1d786fb3", + "form": "7f4e3892-a544-4385-b933-61117e9755c3", + "name": "dfas", + "surname": "dfsa", + "email": "bujnmi@gmail.com", + "note": "a", + "ticket": [ + { + "taxi": { + "name": "Bez odvozu", + "id": 11, + "price": 0 + }, + "food": { + "name": "Ratatouille s br. kaší (bez lepku a masa) (KČ160.00)", + "id": 3, + "price": 160 + }, + "note": null, + "spot": 2 + } + ] + }); + + if(response.data["code"] != 200){ + ToastHelper.Show(context, "There was an error during ordering.", severity: ToastSeverity.NotOk); + } setState(() { _isSendSuccess = true; diff --git a/scripts/database/eshop/get_form.sql b/scripts/database/eshop/get_form.sql index 747dc41f..2accda67 100644 --- a/scripts/database/eshop/get_form.sql +++ b/scripts/database/eshop/get_form.sql @@ -25,7 +25,7 @@ BEGIN 'data', f.data, 'type', f.type, 'occasion', f.occasion, - 'deadline_duration', f.deadline_duration, + 'deadline_duration_seconds', f.deadline_duration_seconds, 'account_number', ba.account_number ) ) diff --git a/supabase/functions/_shared/emailClient.ts b/supabase/functions/_shared/emailClient.ts index 55786e09..07c10c62 100644 --- a/supabase/functions/_shared/emailClient.ts +++ b/supabase/functions/_shared/emailClient.ts @@ -1,4 +1,3 @@ -// _shared/emailClient.ts import { SMTPClient } from "https://deno.land/x/denomailer/mod.ts"; const _SMTP_HOSTNAME = Deno.env.get("SMTP_HOSTNAME")!; @@ -18,6 +17,12 @@ const smtpClient = new SMTPClient({ }, }); +// Sanitize HTML to avoid Gmail-specific issues +export function sanitizeHtml(html: string): string { + // Removes extra spaces and line breaks, and ensures email-friendly formatting + return html.replace(/(\r\n|\n|\r)/gm, "").replace(/ {2,}/g, " ").trim(); +} + export async function sendEmail({ to, subject, @@ -66,6 +71,9 @@ export async function sendEmailWithSubs({ processedSubject = processedSubject.replaceAll(placeholder, value); } - // Use the sendEmail function to send the processed email - await sendEmail({ to, subject: processedSubject, html: processedHtml, from }); + // Sanitize the processed HTML content + const sanitizedHtml = sanitizeHtml(processedHtml); + + // Use the sendEmail function to send the processed and sanitized email + await sendEmail({ to, subject: processedSubject, html: sanitizedHtml, from }); } diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index 46171a7a..c5102657 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -1,5 +1,5 @@ import { sendEmailWithSubs } from "../_shared/emailClient.ts"; -import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.38.4'; +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.46.2'; const _DEFAULT_EMAIL = Deno.env.get("DEFAULT_EMAIL")!; @@ -24,6 +24,43 @@ function formatDatetime(datetime: string): string { }).format(date); } +function generateFullOrder(orderDetails: any, tickets: any[]): string { + const { name, surname, email, note } = orderDetails; + + const orderHeader = ` +
+

Rekapitulace Vaší objednávky:

+

+ Jméno: ${name} + Příjmení: ${surname} + E-mail: ${email} + ${note ? `Poznámka: ${note}` : ""} +

+ `; + + const ticketsDetails = tickets + .map((ticket) => { + const ticketSymbol = ticket.ticket_symbol; + const seat = ticket.items.find((item: any) => item.type === "spot")?.spot_title || "N/A"; + const food = ticket.items.find((item: any) => item.type === "food")?.title; + const taxi = ticket.items.find((item: any) => item.type === "taxi")?.title; + const note = ticket.items.find((item: any) => item.note) || ""; + + return ` +

+ Vstupenka ${ticketSymbol} + Místo: ${seat} + ${food ? `Večeře: ${food}` : ""} + ${taxi ? `Odvoz: ${taxi}` : ""} + ${note ? `Poznámka: ${note}` : ""} +

+ `; + }) + .join(""); + + return `${orderHeader}${ticketsDetails}
`; +} + Deno.serve(async (req) => { try { if (req.method === 'OPTIONS') { @@ -33,33 +70,8 @@ Deno.serve(async (req) => { const reqData = await req.json(); const { orderDetails } = reqData; - const hardcodedOrderDetails = { - secret: "0fb80818-4c8d-4eb7-8205-859b1d786fb3", - form: "7f4e3892-a544-4385-b933-61117e9755c3", - name: "Jan", - surname: "Vicha", - email: "bujnmi@gmail.com", - note: "a", - ticket: [ - { - taxi: { - name: "Bez odvozu", - id: 11, - price: 0, - }, - food: { - name: "Ratatouille s br. kaší (bez lepku a masa) (KČ160.00)", - id: 3, - price: 160, - }, - note: null, - spot: 1, - }, - ], - }; - const { data: ticketOrder, error: ticketError } = await supabaseAdmin.rpc("create_ticket_order", { - input_data: hardcodedOrderDetails, + input_data: orderDetails, }); if (ticketError || ticketOrder.code !== 200) { @@ -99,17 +111,19 @@ Deno.serve(async (req) => { const formattedDeadline = formatDatetime(paymentInfo.deadline); + const fullOrder = generateFullOrder(orderDetails, ticketOrder.tickets); + const subs = { occasionTitle: occasion.occasion_title, price: paymentInfo.amount, accountNumber: paymentInfo.account_number, variableSymbol: paymentInfo.variable_symbol, deadline: formattedDeadline, - fullOrder: "nope", + fullOrder, }; await sendEmailWithSubs({ - to: hardcodedOrderDetails.email, + to: orderDetails.email, subject: template.data.subject, content: template.data.html, subs, @@ -120,7 +134,7 @@ Deno.serve(async (req) => { .from("log_emails") .insert({ "from": _DEFAULT_EMAIL, - "to": hardcodedOrderDetails.email, + "to": orderDetails.email, "template": "TICKET_ORDER_CONFIRMATION", "organization": occasion.id, "occasion": occasion.id, From 778db80237a888628b1c1952ac14a39f5c5b9194 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 16:51:45 +0100 Subject: [PATCH 033/159] supabase update --- supabase/functions/notify/index.ts | 2 +- supabase/functions/register/index.ts | 2 +- supabase/functions/send-reset-password-link/index.ts | 2 +- supabase/functions/send-sign-in-code/index.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/supabase/functions/notify/index.ts b/supabase/functions/notify/index.ts index d03deb9c..305a7810 100644 --- a/supabase/functions/notify/index.ts +++ b/supabase/functions/notify/index.ts @@ -1,4 +1,4 @@ -import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.38.4' +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.46.2' const supabaseAdmin = createClient( Deno.env.get('SUPABASE_URL')!, diff --git a/supabase/functions/register/index.ts b/supabase/functions/register/index.ts index 0ea34946..8b001b7c 100644 --- a/supabase/functions/register/index.ts +++ b/supabase/functions/register/index.ts @@ -1,5 +1,5 @@ import { sendEmailWithSubs } from "../_shared/emailClient.ts"; -import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.38.4'; +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.46.2'; const _DEFAULT_EMAIL = Deno.env.get("DEFAULT_EMAIL")!; diff --git a/supabase/functions/send-reset-password-link/index.ts b/supabase/functions/send-reset-password-link/index.ts index 515af0d9..9467978f 100644 --- a/supabase/functions/send-reset-password-link/index.ts +++ b/supabase/functions/send-reset-password-link/index.ts @@ -1,5 +1,5 @@ import { sendEmailWithSubs } from "../_shared/emailClient.ts"; -import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.38.4'; +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.46.2'; const _DEFAULT_EMAIL = Deno.env.get("DEFAULT_EMAIL")!; const supabaseAdmin = createClient( diff --git a/supabase/functions/send-sign-in-code/index.ts b/supabase/functions/send-sign-in-code/index.ts index a78e4174..350ae37d 100644 --- a/supabase/functions/send-sign-in-code/index.ts +++ b/supabase/functions/send-sign-in-code/index.ts @@ -1,5 +1,5 @@ import { sendEmailWithSubs } from "../_shared/emailClient.ts"; -import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.38.4'; +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.46.2'; const _DEFAULT_EMAIL = Deno.env.get("DEFAULT_EMAIL")!; From 5d6130a1268be41d396c26b152259f0c3e645823 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 16:59:46 +0100 Subject: [PATCH 034/159] adjust --- supabase/functions/send-ticket-order/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index c5102657..eab30a4c 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -29,7 +29,7 @@ function generateFullOrder(orderDetails: any, tickets: any[]): string { const orderHeader = `
-

Rekapitulace Vaší objednávky:

+

Rekapitulace Vaší objednávky:

Jméno: ${name} Příjmení: ${surname} From 51691e3af777f9dcad68da9775f39d998714588d Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 17:30:57 +0100 Subject: [PATCH 035/159] use real data --- lib/dataModels/FormOptionModel.dart | 12 +++---- .../AdministrationOccasion/ColumnHelper.dart | 2 +- lib/pages/FormPage.dart | 31 ++++--------------- lib/services/FormHelper.dart | 8 ++++- supabase/functions/send-ticket-order/index.ts | 4 +-- 5 files changed, 22 insertions(+), 35 deletions(-) diff --git a/lib/dataModels/FormOptionModel.dart b/lib/dataModels/FormOptionModel.dart index 89052404..83cb48a2 100644 --- a/lib/dataModels/FormOptionModel.dart +++ b/lib/dataModels/FormOptionModel.dart @@ -1,12 +1,12 @@ class FormOptionModel { - static const String metaOptionsId = "code"; + static const String metaOptionsId = "id"; static const String metaOptionsName = "name"; static const String metaOptionsPrice = "price"; - FormOptionModel(this.code, this.name, {this.price = 0.0}); + FormOptionModel(this.id, this.name, {this.price = 0.0}); final String name; - final String code; + final String id; final double price; @override @@ -17,15 +17,15 @@ class FormOptionModel { identical(this, other) || other is FormOptionModel && runtimeType == other.runtimeType && - code == other.code; + id == other.id; @override - int get hashCode => code.hashCode; + int get hashCode => id.hashCode; Map toJson() { return { metaOptionsName: name, - metaOptionsId: code, + metaOptionsId: id, metaOptionsPrice: price, }; } diff --git a/lib/pages/AdministrationOccasion/ColumnHelper.dart b/lib/pages/AdministrationOccasion/ColumnHelper.dart index e6ee277c..cf813ebf 100644 --- a/lib/pages/AdministrationOccasion/ColumnHelper.dart +++ b/lib/pages/AdministrationOccasion/ColumnHelper.dart @@ -166,7 +166,7 @@ class ColumnHelper { return columns; }, ACCOMMODATION: (Map data) { - var select = data[DbOccasions.serviceTypeAccommodation]?.map((a)=>a.code).toList(); + var select = data[DbOccasions.serviceTypeAccommodation]?.map((a)=>a.id).toList(); select ??= []; select.add(""); return [ diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index e19ae989..35bd1253 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -158,35 +158,16 @@ class _FormPageState extends State { var data = FormHelper.getDataFromForm( _formKey, fields?[FormHelper.metaFields]); + data["secret"] = "0fb80818-4c8d-4eb7-8205-859b1d786fb3"; + data["form"] = "7f4e3892-a544-4385-b933-61117e9755c3"; formData = data; - var response = await DbEshop.sendTicketOrder({ - "secret": "0fb80818-4c8d-4eb7-8205-859b1d786fb3", - "form": "7f4e3892-a544-4385-b933-61117e9755c3", - "name": "dfas", - "surname": "dfsa", - "email": "bujnmi@gmail.com", - "note": "a", - "ticket": [ - { - "taxi": { - "name": "Bez odvozu", - "id": 11, - "price": 0 - }, - "food": { - "name": "Ratatouille s br. kaší (bez lepku a masa) (KČ160.00)", - "id": 3, - "price": 160 - }, - "note": null, - "spot": 2 - } - ] - }); + + var response = await DbEshop.sendTicketOrder(data); if(response.data["code"] != 200){ - ToastHelper.Show(context, "There was an error during ordering.", severity: ToastSeverity.NotOk); + ToastHelper.Show(context, "There was an error during ordering. Error code: ${response.data["code"]}", severity: ToastSeverity.NotOk); + _isLoading = false; } setState(() { diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 56ef5ead..61efee04 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -15,6 +15,7 @@ class FormHelper { static const String fieldTypeSex = "sex"; static const String fieldTypeBirthYear = "birth_year"; static const String fieldTypeNote = "note"; + static const String fieldTypeSpot = "spot"; static const String fieldTypeTicket = "ticket"; @@ -36,6 +37,7 @@ class FormHelper { static String nameLabel() => "Name".tr(); static String surnameLabel() => "Surname".tr(); static String cityLabel() => "City".tr(); + static String spotLabel() => "Spot".tr(); static String emailLabel() => "E-mail".tr(); static String sexLabel() => "I am".tr(); static String birthYearLabel() => "Birth year".tr(); @@ -69,9 +71,11 @@ class FormHelper { if (fieldValue == null) { return null; } - return (fieldValue as FormOptionModel).code; + return (fieldValue as FormOptionModel).id; } else if (fieldType == fieldTypeBirthYear) { return (fieldValue != null && fieldValue.isNotEmpty) ? int.tryParse(fieldValue) : null; + } else if (fieldType == fieldTypeSpot) { + return (fieldValue != null && fieldValue.isNotEmpty) ? int.tryParse(fieldValue) : null; } else if (fieldType == fieldTypeTicket) { // Collect ticket data from multiple ticket forms List> tickets = []; @@ -113,6 +117,8 @@ class FormHelper { return buildTextField(fieldTypeSurname, surnameLabel(), isRequiredField, [AutofillHints.familyName]); case fieldTypeCity: return buildTextField(fieldTypeCity, cityLabel(), isRequiredField, [AutofillHints.addressCity]); + case fieldTypeSpot: + return buildTextField(fieldTypeSpot, cityLabel(), isRequiredField, [AutofillHints.addressCity]); case fieldTypeEmail: return buildEmailField(isRequiredField); case fieldTypeSex: diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index eab30a4c..c904b9d3 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -76,9 +76,9 @@ Deno.serve(async (req) => { if (ticketError || ticketOrder.code !== 200) { console.error("Error creating ticket order:", ticketError); - return new Response(JSON.stringify({ error: "Failed to create ticket order. Error code: " + ticketOrder.code }), { + return new Response(JSON.stringify({ "code": ticketOrder.code }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, - status: 500, + status: 200, }); } From 842e24cad4203adc2c3c6b55900af0da6305b53f Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 30 Nov 2024 18:04:36 +0100 Subject: [PATCH 036/159] error dsp fix --- lib/pages/FormPage.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 35bd1253..be97d62c 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -168,6 +168,7 @@ class _FormPageState extends State { if(response.data["code"] != 200){ ToastHelper.Show(context, "There was an error during ordering. Error code: ${response.data["code"]}", severity: ToastSeverity.NotOk); _isLoading = false; + return; } setState(() { From 2c2456cb8bb71936e825f493bb61f35758fbebd8 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 1 Dec 2024 01:47:35 +0100 Subject: [PATCH 037/159] qr code --- lib/pages/FormPage.dart | 6 +++-- supabase/functions/_shared/emailClient.ts | 20 +++++++++++--- supabase/functions/send-ticket-order/index.ts | 26 ++++++++++++++++++- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index be97d62c..96f8fd8a 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -167,12 +167,14 @@ class _FormPageState extends State { if(response.data["code"] != 200){ ToastHelper.Show(context, "There was an error during ordering. Error code: ${response.data["code"]}", severity: ToastSeverity.NotOk); - _isLoading = false; + setState(() { + _isLoading = false; + }); return; } setState(() { - _isSendSuccess = true; + //_isSendSuccess = true; _isLoading = false; }); diff --git a/supabase/functions/_shared/emailClient.ts b/supabase/functions/_shared/emailClient.ts index 07c10c62..92612322 100644 --- a/supabase/functions/_shared/emailClient.ts +++ b/supabase/functions/_shared/emailClient.ts @@ -19,7 +19,6 @@ const smtpClient = new SMTPClient({ // Sanitize HTML to avoid Gmail-specific issues export function sanitizeHtml(html: string): string { - // Removes extra spaces and line breaks, and ensures email-friendly formatting return html.replace(/(\r\n|\n|\r)/gm, "").replace(/ {2,}/g, " ").trim(); } @@ -27,11 +26,18 @@ export async function sendEmail({ to, subject, html, + attachments = [], from = `${_DEFAULT_EMAIL}`, }: { to: string; subject: string; html: string; + attachments?: Array<{ + contentType: string; + filename: string; + content: string | Uint8Array | ArrayBufferLike; + encoding: "binary" | "text" | "base64"; + }>; from?: string; }) { try { @@ -40,6 +46,7 @@ export async function sendEmail({ to, subject, html, + attachments }); console.log("Email sent successfully to:", to); } catch (error) { @@ -54,12 +61,19 @@ export async function sendEmailWithSubs({ subject, content, subs, + attachments = [], from = `${_DEFAULT_EMAIL}`, }: { to: string; subject: string; content: string; subs: Record; + attachments?: Array<{ + contentType: string; + filename: string; + content: string | Uint8Array | ArrayBufferLike; + encoding: "binary" | "text" | "base64"; + }>; from?: string; }) { // Replace placeholders in content with values from subs @@ -74,6 +88,6 @@ export async function sendEmailWithSubs({ // Sanitize the processed HTML content const sanitizedHtml = sanitizeHtml(processedHtml); - // Use the sendEmail function to send the processed and sanitized email - await sendEmail({ to, subject: processedSubject, html: sanitizedHtml, from }); + // Use the sendEmail function to send the processed and sanitized email with attachments + await sendEmail({ to, subject: processedSubject, html: sanitizedHtml, attachments, from }); } diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index c904b9d3..40e4d513 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -1,5 +1,6 @@ import { sendEmailWithSubs } from "../_shared/emailClient.ts"; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.46.2'; +import { qrcode } from 'https://deno.land/x/qrcode/mod.ts'; const _DEFAULT_EMAIL = Deno.env.get("DEFAULT_EMAIL")!; @@ -13,6 +14,21 @@ const supabaseAdmin = createClient( Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! ); +// Function to generate QR code as base64 +async function generateQrCode(paymentInfo: any): Promise { + const qrData = `bitcoin:${paymentInfo.account_number}?amount=${paymentInfo.amount}&label=Payment`; + + const base64QrCode = await qrcode(qrData, { size: 500 }); + console.log("QR Code Output:", base64QrCode); + + if (typeof base64QrCode !== "string") { + throw new Error("Unexpected QR code format returned by qrcode function."); + } + + const base64String = base64QrCode.split(",")[1]; // Remove the `data:image/...` prefix + return Uint8Array.from(atob(base64String), (c) => c.charCodeAt(0)); // Convert Base64 to binary +} + function formatDatetime(datetime: string): string { const date = new Date(datetime); return new Intl.DateTimeFormat("cs-CZ", { @@ -110,7 +126,7 @@ Deno.serve(async (req) => { } const formattedDeadline = formatDatetime(paymentInfo.deadline); - + const qrCode = await generateQrCode(paymentInfo); // Generate QR code as Uint8Array const fullOrder = generateFullOrder(orderDetails, ticketOrder.tickets); const subs = { @@ -128,6 +144,14 @@ Deno.serve(async (req) => { content: template.data.html, subs, from: `${occasion.occasion_title} | Festapp <${_DEFAULT_EMAIL}>`, + attachments: [ + { + filename: "payment-qr-code.gif", // Name of the file + content: qrCode, // Ensure qrCode is a Uint8Array + contentType: "image/gif", // MIME type for GIF + encoding: "binary", // Specify binary encoding + }, + ], }); await supabaseAdmin From 626df41196e964cf08fdb9b4668b2f9aaaf5376d Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 1 Dec 2024 02:20:14 +0100 Subject: [PATCH 038/159] qr code with data --- scripts/database/eshop/create_ticket_order.sql | 9 ++++++--- supabase/functions/send-ticket-order/index.ts | 11 ++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index 08f79f4a..9a9a9262 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -17,6 +17,7 @@ DECLARE organization_id BIGINT; occasion_title TEXT; account_number TEXT; + account_number_human_readable TEXT; ticket_details JSONB := '[]'::JSONB; item_data RECORD; ticket_id BIGINT; @@ -63,8 +64,9 @@ BEGIN RETURN JSONB_BUILD_OBJECT('code', 1005, 'message', 'No organization found for the occasion'); END IF; - -- Fetch account number from the bank account - SELECT bank_accounts.account_number INTO account_number + -- Fetch account number and account number human readable from the bank account + SELECT bank_accounts.account_number, bank_accounts.account_number_human_readable + INTO account_number, account_number_human_readable FROM eshop.bank_accounts WHERE id = bank_account_id; @@ -220,7 +222,8 @@ BEGIN 'variable_symbol', generated_variable_symbol, 'amount', calculated_price, 'deadline', deadline, - 'account_number', account_number + 'account_number', account_number, + 'account_number_human_readable', account_number_human_readable ), 'occasion', JSONB_BUILD_OBJECT( 'id', occasion_id, diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index 40e4d513..1acc2750 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -15,9 +15,10 @@ const supabaseAdmin = createClient( ); // Function to generate QR code as base64 -async function generateQrCode(paymentInfo: any): Promise { - const qrData = `bitcoin:${paymentInfo.account_number}?amount=${paymentInfo.amount}&label=Payment`; - +async function generateQrCode(paymentInfo: any, orderDetails: any): Promise { + const qrData = `SPD*1.0*ACC:${paymentInfo.account_number}*AM:${paymentInfo.amount.toFixed( + 2 + )}*CC:CZK*MSG:${orderDetails.name} ${orderDetails.surname}*X-VS:${paymentInfo.variable_symbol}`; const base64QrCode = await qrcode(qrData, { size: 500 }); console.log("QR Code Output:", base64QrCode); @@ -126,13 +127,13 @@ Deno.serve(async (req) => { } const formattedDeadline = formatDatetime(paymentInfo.deadline); - const qrCode = await generateQrCode(paymentInfo); // Generate QR code as Uint8Array + const qrCode = await generateQrCode(paymentInfo, orderDetails); const fullOrder = generateFullOrder(orderDetails, ticketOrder.tickets); const subs = { occasionTitle: occasion.occasion_title, price: paymentInfo.amount, - accountNumber: paymentInfo.account_number, + accountNumber: paymentInfo.account_number_human_readable, variableSymbol: paymentInfo.variable_symbol, deadline: formattedDeadline, fullOrder, From ee775f20f53528d51fe94dce6634dd2d3a3dfd54 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 1 Dec 2024 04:29:43 +0100 Subject: [PATCH 039/159] price waves removed, items.is_hidden added --- lib/dataModelsEshop/TbEshop.dart | 11 ------ .../database/cron/apply_planned_changes.sql | 35 +++++++++++++++++++ .../database/eshop/create_ticket_order.sql | 22 +++++++----- 3 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 scripts/database/cron/apply_planned_changes.sql diff --git a/lib/dataModelsEshop/TbEshop.dart b/lib/dataModelsEshop/TbEshop.dart index 1b946db7..2080b18b 100644 --- a/lib/dataModelsEshop/TbEshop.dart +++ b/lib/dataModelsEshop/TbEshop.dart @@ -3,7 +3,6 @@ class TbEshop { static ItemsTb items = const ItemsTb(); static OrderItemTicketTb order_item_ticket = const OrderItemTicketTb(); static OrdersTb orders = const OrdersTb(); - static PriceWavesTb price_waves = const PriceWavesTb(); static TicketsTb tickets = const TicketsTb(); } @@ -56,16 +55,6 @@ class OrdersTb { String get occasion => "occasion"; } -class PriceWavesTb { - const PriceWavesTb(); - String get table => "price_waves"; - String get id => "id"; - String get created_at => "created_at"; - String get start_time => "start_time"; - String get price => "price"; - String get item => "item"; -} - class TicketsTb { const TicketsTb(); String get table => "tickets"; diff --git a/scripts/database/cron/apply_planned_changes.sql b/scripts/database/cron/apply_planned_changes.sql new file mode 100644 index 00000000..b9748330 --- /dev/null +++ b/scripts/database/cron/apply_planned_changes.sql @@ -0,0 +1,35 @@ +CREATE OR REPLACE FUNCTION apply_planned_changes() +RETURNS VOID AS $$ +DECLARE + change RECORD; +BEGIN + FOR change IN + SELECT * + FROM eshop.planned_changes + WHERE change_time <= NOW() AND applied = FALSE + LOOP + IF change.change_type = 'items.price' THEN + -- Update item price + UPDATE eshop.items + SET price = change.new_value::NUMERIC + WHERE id = change.subject_id; + + ELSIF change.change_type = 'items.is_hidden' THEN + -- Update item visibility + UPDATE eshop.items + SET is_hidden = change.new_value::BOOLEAN + WHERE id = change.subject_id; + + ELSE + RAISE NOTICE 'Unknown change_type: %', change.change_type; + END IF; + + -- Mark the change as applied + UPDATE eshop.planned_changes + SET applied = TRUE + WHERE id = change.id; + END LOOP; +END; +$$ LANGUAGE plpgsql; + +SELECT cron.schedule('apply_planned_changes', '*/1 * * * *', 'CALL apply_planned_changes()'); \ No newline at end of file diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index 9a9a9262..f8608a1d 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -135,15 +135,10 @@ BEGIN spot_data.item ] LOOP IF item_id IS NOT NULL THEN - SELECT i.*, it.type, COALESCE(pw.price, i.price) AS final_price + SELECT i.*, it.type, i.price, i.is_hidden INTO item_data FROM eshop.items i LEFT JOIN eshop.item_types it ON i.item_type = it.id - LEFT JOIN ( - SELECT * FROM eshop.price_waves - WHERE start_time <= now - ORDER BY start_time DESC LIMIT 1 - ) pw ON pw.item = i.id WHERE i.id = item_id AND i.occasion = occasion_id; IF item_data IS NULL THEN @@ -154,18 +149,27 @@ BEGIN ); END IF; + -- Check if item is hidden + IF item_data.is_hidden THEN + RETURN JSONB_BUILD_OBJECT( + 'code', 1012, + 'message', 'Selected item is hidden and cannot be ordered', + 'item_id', item_id + ); + END IF; + -- Add item details to ticket items ticket_items := ticket_items || JSONB_BUILD_OBJECT( 'item_id', item_id, 'title', item_data.title, 'type', item_data.type, - 'price', item_data.final_price, + 'price', item_data.price, 'spot_title', CASE WHEN item_id = spot_data.item THEN spot_data.title ELSE NULL END, 'description', CASE WHEN item_id = spot_data.item THEN item_data.description ELSE NULL END ); -- Add item price to calculated total - calculated_price := calculated_price + COALESCE(item_data.final_price, item_data.price, 0); + calculated_price := calculated_price + COALESCE(item_data.price, 0); -- Link ticket and item to the order INSERT INTO eshop.order_item_ticket ("order", item, ticket) @@ -232,6 +236,6 @@ BEGIN ); EXCEPTION WHEN OTHERS THEN - RETURN JSONB_BUILD_OBJECT('code', 1012, 'message', SQLERRM); + RETURN JSONB_BUILD_OBJECT('code', 1013, 'message', SQLERRM); END; $$; From ddf438c537c08088fe64c95b40b67b11270ed54e Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 1 Dec 2024 04:46:27 +0100 Subject: [PATCH 040/159] is_hidden items --- lib/dataModelsEshop/ItemModel.dart | 4 ++++ lib/dataModelsEshop/TbEshop.dart | 1 + lib/dataServices/DbEshop.dart | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/dataModelsEshop/ItemModel.dart b/lib/dataModelsEshop/ItemModel.dart index 8ad7ba0c..63658afd 100644 --- a/lib/dataModelsEshop/ItemModel.dart +++ b/lib/dataModelsEshop/ItemModel.dart @@ -5,6 +5,7 @@ class ItemModel { DateTime? createdAt; DateTime? updatedAt; String? title; + bool? isHidden; String? description; double? price; Map? data; @@ -20,6 +21,7 @@ class ItemModel { createdAt: json[TbEshop.items.created_at] != null ? DateTime.parse(json[TbEshop.items.created_at]) : null, updatedAt: json[TbEshop.items.updated_at] != null ? DateTime.parse(json[TbEshop.items.updated_at]) : null, title: json[TbEshop.items.title], + isHidden: json[TbEshop.items.is_hidden], description: json[TbEshop.items.description], price: json[TbEshop.items.price] != null ? double.tryParse(json[TbEshop.items.price].toString()) : null, data: json[TbEshop.items.data], @@ -33,6 +35,7 @@ class ItemModel { TbEshop.items.created_at: createdAt?.toIso8601String(), TbEshop.items.updated_at: updatedAt?.toIso8601String(), TbEshop.items.title: title, + TbEshop.items.is_hidden: isHidden, TbEshop.items.description: description, TbEshop.items.price: price, TbEshop.items.data: data, @@ -47,6 +50,7 @@ class ItemModel { this.createdAt, this.updatedAt, this.title, + this.isHidden, this.description, this.price, this.data, diff --git a/lib/dataModelsEshop/TbEshop.dart b/lib/dataModelsEshop/TbEshop.dart index 2080b18b..4394706e 100644 --- a/lib/dataModelsEshop/TbEshop.dart +++ b/lib/dataModelsEshop/TbEshop.dart @@ -25,6 +25,7 @@ class ItemsTb { String get id => "id"; String get created_at => "created_at"; String get updated_at => "updated_at"; + String get is_hidden => "is_hidden"; String get title => "title"; String get description => "description"; String get price => "price"; diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index ef07cf6d..55044f87 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -20,7 +20,8 @@ class DbEshop { "${TbEshop.item_types.title}," "${TbEshop.items.table}(${TbEshop.items.id},${TbEshop.items.title},${TbEshop.items.price})" ) - .eq(TbEshop.item_types.occasion, currentOccasion); + .eq(TbEshop.item_types.occasion, currentOccasion) + .eq("${TbEshop.items.table}.${TbEshop.items.is_hidden}", false); var infoList = List.from( data.map((x) { From 3ddf7c6d91138332c7206610e18b37d2e2204d04 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 1 Dec 2024 04:54:29 +0100 Subject: [PATCH 041/159] apply_planned_changes fix --- scripts/database/cron/apply_planned_changes.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/database/cron/apply_planned_changes.sql b/scripts/database/cron/apply_planned_changes.sql index b9748330..c43e3e05 100644 --- a/scripts/database/cron/apply_planned_changes.sql +++ b/scripts/database/cron/apply_planned_changes.sql @@ -7,6 +7,7 @@ BEGIN SELECT * FROM eshop.planned_changes WHERE change_time <= NOW() AND applied = FALSE + ORDER BY change_time ASC LOOP IF change.change_type = 'items.price' THEN -- Update item price From c589f9daec670d36df49cdd8135f6b9256346873 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:52:15 +0100 Subject: [PATCH 042/159] secret expiration check removed --- scripts/database/eshop/create_ticket_order.sql | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index f8608a1d..e2acc3d6 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -113,9 +113,6 @@ BEGIN IF spot_data.secret IS DISTINCT FROM spot_secret THEN RETURN JSONB_BUILD_OBJECT('code', 1009, 'message', 'Invalid secret for spot'); END IF; - IF spot_data.secret_expiration_time < now THEN - RETURN JSONB_BUILD_OBJECT('code', 1010, 'message', 'Secret expired'); - END IF; -- Generate ticket symbol ticket_symbol := generate_ticket_symbol(organization_id, occasion_id); @@ -194,7 +191,7 @@ BEGIN END LOOP; -- Generate variable symbol for payment - generated_variable_symbol := generate_variable_symbol(bank_account_id); + generated_variable_symbol := generate_ticket_symbol(bank_account_id); -- Insert payment info after calculating price INSERT INTO eshop.payment_info (bank_account, variable_symbol, amount, created_at, deadline) From 3c2c77be0ea3415604309c495d9b59fded32bd2f Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:43:34 +0100 Subject: [PATCH 043/159] seat reservation widget --- .../seatReservation/model/BoxGroupModel.dart | 67 ++++ .../seatReservation/model/BoxModel.dart | 50 +++ .../model/SeatLayoutStateModel.dart | 19 ++ .../seatReservation/model/SeatModel.dart | 20 ++ .../seatReservation/utils/SeatState.dart | 20 ++ .../widgets/SeatLayoutWidget.dart | 76 +++++ .../seatReservation/widgets/SeatWidget.dart | 90 ++++++ lib/dataModels/FormModel.dart | 12 +- lib/dataModels/OccasionUserModel.dart | 6 +- lib/dataModels/Tb.dart | 1 + .../BlueprintConfiguration.dart | 28 ++ lib/dataModelsEshop/BlueprintModel.dart | 99 ++++++ lib/dataModelsEshop/BlueprintObject.dart | 52 ++++ lib/dataModelsEshop/TbEshop.dart | 14 + lib/dataServices/DbEshop.dart | 17 ++ lib/pages/FormPage.dart | 22 +- lib/pages/SignupPage.dart | 11 +- lib/services/FormHelper.dart | 52 +++- lib/styles/StylesConfig.dart | 2 + lib/widgets/SeatReservationWidget.dart | 288 ++++++++++++++++++ scripts/database/eshop/get_blueprint.sql | 91 ++++++ scripts/database/eshop/get_form.sql | 2 + 22 files changed, 1008 insertions(+), 31 deletions(-) create mode 100644 lib/components/seatReservation/model/BoxGroupModel.dart create mode 100644 lib/components/seatReservation/model/BoxModel.dart create mode 100644 lib/components/seatReservation/model/SeatLayoutStateModel.dart create mode 100644 lib/components/seatReservation/model/SeatModel.dart create mode 100644 lib/components/seatReservation/utils/SeatState.dart create mode 100644 lib/components/seatReservation/widgets/SeatLayoutWidget.dart create mode 100644 lib/components/seatReservation/widgets/SeatWidget.dart create mode 100644 lib/dataModelsEshop/BlueprintConfiguration.dart create mode 100644 lib/dataModelsEshop/BlueprintModel.dart create mode 100644 lib/dataModelsEshop/BlueprintObject.dart create mode 100644 lib/widgets/SeatReservationWidget.dart create mode 100644 scripts/database/eshop/get_blueprint.sql diff --git a/lib/components/seatReservation/model/BoxGroupModel.dart b/lib/components/seatReservation/model/BoxGroupModel.dart new file mode 100644 index 00000000..701935a5 --- /dev/null +++ b/lib/components/seatReservation/model/BoxGroupModel.dart @@ -0,0 +1,67 @@ +import 'package:fstapp/components/seatReservation/model/BoxModel.dart'; + +class BoxGroupModel{ + static const String boxGroupsTable = "box_groups"; + static const String idColumn = "id"; + static const String occasionColumn = "occasion"; + static const String nameColumn = "name"; + + int? id; + int? blueprintId; + String? name; + List? boxes = []; + + BoxGroupModel({ + this.id, + this.blueprintId, + this.name, + }); + + static BoxGroupModel fromJson(Map json) { + return BoxGroupModel( + id: json[idColumn], + blueprintId: json[occasionColumn], + name: json[nameColumn], + ); + } + + Map toJson() { + var map = { + occasionColumn: blueprintId, + nameColumn: name, + }; + if(id != null) + { + map[idColumn] = id; + } + return map; + } + + @override + toString() + { + return "${name}"; + } + List alphabet = [ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' + ]; + + String getNextBoxName() + { + return alphabet[boxes!.length]; + } + + @override + int get hashCode { + return id.hashCode; + } + + @override bool operator ==(Object other) { + if(other is BoxGroupModel) + { + return id == other.id; + } + return false; + } +} \ No newline at end of file diff --git a/lib/components/seatReservation/model/BoxModel.dart b/lib/components/seatReservation/model/BoxModel.dart new file mode 100644 index 00000000..e2588395 --- /dev/null +++ b/lib/components/seatReservation/model/BoxModel.dart @@ -0,0 +1,50 @@ + +import '../utils/SeatState.dart'; +import 'BoxGroupModel.dart'; + +class BoxModel{ + static const String soldType = "sold"; + static const String selectedType = "selected"; + static const String blackType = "black"; + static const String availableType = "available"; + + static const String boxTable = "boxes"; + + static const SeatState stateColumn = SeatState.empty; + + int? id; + String? name; + SeatState? type; + int? boxGroupId; + BoxGroupModel? boxGroup; + int? x; + int? y; + + BoxModel({ + this.id, + this.name, + this.type, + this.boxGroupId, + this.boxGroup, + this.x, + this.y + }); + + static Map States = { + SeatState.black: blackType, + SeatState.available: availableType, + SeatState.selected: selectedType, + SeatState.ordered: soldType, + }; + + @override + toString() + { + return "stůl ${boxGroup}, sedadlo ${name}"; + } + + toShortString() + { + return "${boxGroup??""}${name??""}"; + } +} \ No newline at end of file diff --git a/lib/components/seatReservation/model/SeatLayoutStateModel.dart b/lib/components/seatReservation/model/SeatLayoutStateModel.dart new file mode 100644 index 00000000..28c98e3d --- /dev/null +++ b/lib/components/seatReservation/model/SeatLayoutStateModel.dart @@ -0,0 +1,19 @@ + +import 'BoxModel.dart'; +import 'SeatModel.dart'; + +class SeatLayoutStateModel { + final int rows; + final int cols; + final List currentBoxes; + final List allBoxes; + final int seatSize; + + const SeatLayoutStateModel({ + required this.rows, + required this.cols, + required this.currentBoxes, + required this.allBoxes, + this.seatSize = 50, + }); +} diff --git a/lib/components/seatReservation/model/SeatModel.dart b/lib/components/seatReservation/model/SeatModel.dart new file mode 100644 index 00000000..9081fcde --- /dev/null +++ b/lib/components/seatReservation/model/SeatModel.dart @@ -0,0 +1,20 @@ +import '../utils/SeatState.dart'; +import 'BoxModel.dart'; + + +class SeatModel { + SeatState seatState; + final int rowI; + final int colI; + final int seatSize; + BoxModel? boxModel; + + SeatModel({ + required this.boxModel, + required this.seatState, + required this.rowI, + required this.colI, + this.seatSize = 50, + }); + +} diff --git a/lib/components/seatReservation/utils/SeatState.dart b/lib/components/seatReservation/utils/SeatState.dart new file mode 100644 index 00000000..b5315c63 --- /dev/null +++ b/lib/components/seatReservation/utils/SeatState.dart @@ -0,0 +1,20 @@ +/// current state of a seat +enum SeatState { + /// some user selected this seat + selected, + + /// current user selected this seat + selected_by_me, + + /// but it is available to be booked + available, + + /// this seat is already sold to other user + ordered, + + /// this seat is disabled to be booked for some reason + black, + + /// empty area e.g. aisle, staircase etc + empty, +} diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart new file mode 100644 index 00000000..85b25afd --- /dev/null +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -0,0 +1,76 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; + +import '../model/SeatLayoutStateModel.dart'; +import '../model/SeatModel.dart'; +import '../utils/SeatState.dart'; +import 'SeatWidget.dart'; + + + +class SeatLayoutWidget extends StatelessWidget { + + final SeatLayoutStateModel stateModel; + void Function(SeatModel model)? onSeatTap; + + SeatLayoutWidget({ + Key? key, + required this.stateModel, + this.onSeatTap, + }) : super(key: key); + + + @override + Widget build(BuildContext context) { + return InteractiveViewer( + alignment: Alignment.topLeft, + maxScale: 5, + minScale: 0.8, + boundaryMargin: const EdgeInsets.all(8), + constrained: false, + child: Column( + children: [ + ...List.generate(stateModel.rows, (rowI) => rowI) + .map( + (rowI) => Row( + children: [ + ...List.generate(stateModel.cols, (colI) => colI) + .map((colI) { + var seatModel = createSeat(colI, rowI); + if(seatModel.boxModel != null) + { + return Tooltip( + showDuration: const Duration(seconds: 0), + message: seatModel.boxModel!.toShortString()??"", + child: SeatWidget( + model: seatModel, + onSeatTap: onSeatTap, + ), + ); + } + + return SeatWidget( + model: seatModel, + onSeatTap: onSeatTap, + ); + }) + ], + ), + ) + ], + ), + ); + } + + SeatModel createSeat(int colI, int rowI) { + var model = SeatModel( + boxModel: stateModel.currentBoxes.firstWhereOrNull((b)=>b.x==colI&&b.y==rowI), + seatState: stateModel.currentBoxes.firstWhereOrNull((b)=>b.x==colI&&b.y==rowI)?.type??SeatState.empty, + rowI: rowI, + colI: colI, + seatSize: stateModel.seatSize, + ); + stateModel.allBoxes.add(model); + return model; + } +} diff --git a/lib/components/seatReservation/widgets/SeatWidget.dart b/lib/components/seatReservation/widgets/SeatWidget.dart new file mode 100644 index 00000000..3228ccd9 --- /dev/null +++ b/lib/components/seatReservation/widgets/SeatWidget.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; + +import '../model/SeatModel.dart'; +import '../utils/SeatState.dart'; + +class SeatWidget extends StatefulWidget { + final SeatModel model; + final void Function(SeatModel model)? onSeatTap; + + const SeatWidget({ + Key? key, + required this.model, + this.onSeatTap, + }) : super(key: key); + + @override + State createState() => _SeatWidgetState(); + + static double padding = 2.0; + /// Static method to create a seat widget with a given seat state and size. + /// This allows external calls to render a seat without relying on the `SeatModel`. + static Widget buildSeat({ + required SeatState state, + double size = 40.0 + }) { + // Adjust padding for sold, selected, and available states + final bool hasPadding = state == SeatState.ordered || + state == SeatState.selected || + state == SeatState.available; + + return Container( + color: _getSeatColor(SeatState.empty), + height: size, + width: size, + child: Container( + margin: hasPadding ? EdgeInsets.all(padding) : EdgeInsets.zero, + decoration: BoxDecoration( + color: _getSeatColor(state), + borderRadius: BorderRadius.circular(hasPadding ? padding : 0.0), + ), + ), + ); + } + + /// Helper method to get seat color based on its state. + static Color _getSeatColor(SeatState state) { + switch (state) { + case SeatState.available: + return Colors.green; + case SeatState.selected: + return Colors.blue; + case SeatState.black: + return Colors.black; + case SeatState.ordered: + return Colors.red; + case SeatState.empty: + return Colors.grey.shade300; + default: + return Colors.grey.shade300; + } + } +} + +class _SeatWidgetState extends State { + late SeatState seatState; + + @override + void initState() { + super.initState(); + seatState = widget.model.seatState; + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + if (widget.onSeatTap != null) { + widget.onSeatTap!(widget.model); + setState(() { + seatState = widget.model.seatState; + }); + } + }, + child: SeatWidget.buildSeat( + state: seatState, + size: widget.model.seatSize.toDouble(), + ), + ); + } +} diff --git a/lib/dataModels/FormModel.dart b/lib/dataModels/FormModel.dart index 31d951c5..3520b332 100644 --- a/lib/dataModels/FormModel.dart +++ b/lib/dataModels/FormModel.dart @@ -4,8 +4,9 @@ class FormModel { int? id; DateTime? createdAt; Map? data; - String? key; + String? formKey; int? occasion; + int? blueprint; String? type; int? bankAccount; int? deadlineDuration; @@ -16,8 +17,9 @@ class FormModel { this.id, this.createdAt, this.data, - this.key, + this.formKey, this.occasion, + this.blueprint, this.type, this.bankAccount, this.deadlineDuration, @@ -32,8 +34,9 @@ class FormModel { ? DateTime.parse(json[Tb.forms.created_at]) : null, data: json[Tb.forms.data], - key: json[Tb.forms.key], + formKey: json[Tb.forms.key], occasion: json[Tb.forms.occasion], + blueprint: json[Tb.forms.blueprint], type: json[Tb.forms.type], bankAccount: json[Tb.forms.bank_account], deadlineDuration: json[Tb.forms.deadline_duration_seconds], @@ -46,8 +49,9 @@ class FormModel { Tb.forms.id: id, Tb.forms.created_at: createdAt?.toIso8601String(), Tb.forms.data: data, - Tb.forms.key: key, + Tb.forms.key: formKey, Tb.forms.occasion: occasion, + Tb.forms.blueprint: blueprint, Tb.forms.type: type, Tb.forms.bank_account: bankAccount, Tb.forms.deadline_duration_seconds: deadlineDuration, diff --git a/lib/dataModels/OccasionUserModel.dart b/lib/dataModels/OccasionUserModel.dart index 35eda887..fcf9ea57 100644 --- a/lib/dataModels/OccasionUserModel.dart +++ b/lib/dataModels/OccasionUserModel.dart @@ -103,7 +103,7 @@ class OccasionUserModel extends IPlutoRowModel { Map serviceToOneColumnPlutoRow(Map? services, String serviceType) { Map serviceCells = {}; for (var entry in services?[serviceType]?.entries ?? []) { - serviceCells[serviceType + entry.key] = PlutoCell(value: entry.value); + serviceCells[serviceType + entry.formKey] = PlutoCell(value: entry.value); } return serviceCells; } @@ -119,7 +119,7 @@ class OccasionUserModel extends IPlutoRowModel { if(value.isEmpty){ return emptyResult; } - var first = value.first.key; + var first = value.first.formKey; cell = PlutoCell(value: first); return { serviceType : cell }; } @@ -127,7 +127,7 @@ class OccasionUserModel extends IPlutoRowModel { Map servicesToPlutoRow(Map? services, String serviceType) { Map serviceCells = {}; for (var entry in services?[serviceType]?.entries ?? []) { - serviceCells[serviceType + entry.key] = PlutoCell(value: entry.value); + serviceCells[serviceType + entry.formKey] = PlutoCell(value: entry.value); } return serviceCells; } diff --git a/lib/dataModels/Tb.dart b/lib/dataModels/Tb.dart index fa0b6ad8..27c92900 100644 --- a/lib/dataModels/Tb.dart +++ b/lib/dataModels/Tb.dart @@ -321,6 +321,7 @@ class FormsTb { String get data => "data"; String get key => "key"; String get occasion => "occasion"; + String get blueprint => "blueprint"; String get type => "type"; String get bank_account => "bank_account"; String get deadline_duration_seconds => "deadline_duration_seconds"; diff --git a/lib/dataModelsEshop/BlueprintConfiguration.dart b/lib/dataModelsEshop/BlueprintConfiguration.dart new file mode 100644 index 00000000..0a7f2721 --- /dev/null +++ b/lib/dataModelsEshop/BlueprintConfiguration.dart @@ -0,0 +1,28 @@ +class BlueprintConfiguration { + static const String metaDimensions = "dimensions"; + static const String metaWidth = "width"; + static const String metaHeight = "height"; + + int? width; + int? height; + + factory BlueprintConfiguration.fromJson(Map json) { + final dimensions = json[metaDimensions] as Map?; + return BlueprintConfiguration( + width: dimensions?[metaWidth], + height: dimensions?[metaHeight], + ); + } + + Map toJson() => { + metaDimensions: { + metaWidth: width, + metaHeight: height, + }, + }; + + BlueprintConfiguration({ + this.width, + this.height, + }); +} diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart new file mode 100644 index 00000000..3bd0cf13 --- /dev/null +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -0,0 +1,99 @@ +import 'package:fstapp/components/seatReservation/model/BoxModel.dart'; +import 'package:fstapp/components/seatReservation/utils/SeatState.dart'; +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'BlueprintConfiguration.dart'; +import 'BlueprintObject.dart'; + +class BlueprintModel { + static const String metaSpots = "spots"; + + int? id; + DateTime? createdAt; + Map? data; + String? title; + int? organization; + BlueprintConfiguration? configuration; + List? objects; + List? spots; + + factory BlueprintModel.fromJson(Map json) { + return BlueprintModel( + id: json[TbEshop.blueprints.id], + createdAt: json[TbEshop.blueprints.created_at] != null + ? DateTime.parse(json[TbEshop.blueprints.created_at]) + : null, + data: json[TbEshop.blueprints.data], + title: json[TbEshop.blueprints.title], + organization: json[TbEshop.blueprints.organization], + configuration: json[TbEshop.blueprints.configuration] != null + ? BlueprintConfiguration.fromJson(json[TbEshop.blueprints.configuration]) + : null, + objects: json[TbEshop.blueprints.objects] != null + ? List.from( + json[TbEshop.blueprints.objects].map((obj) => BlueprintObject.fromJson(obj))) + : null, + spots: json[metaSpots] != null + ? List.from( + json[metaSpots].map((spot) => BlueprintObject.fromJson(spot))) + : null, + ); + } + + Map toJson() => { + TbEshop.blueprints.id: id, + TbEshop.blueprints.created_at: createdAt?.toIso8601String(), + TbEshop.blueprints.data: data, + TbEshop.blueprints.title: title, + TbEshop.blueprints.organization: organization, + TbEshop.blueprints.configuration: configuration?.toJson(), + TbEshop.blueprints.objects: objects?.map((obj) => obj.toJson()).toList(), + metaSpots: spots?.map((spot) => spot.toJson()).toList(), + }; + + String toBasicString() => title ?? id.toString(); + + List toBoxModels() { + if (objects == null) return []; + + return objects!.map((object) { + if (object.type == "spot") { + // Find the matching spot in the spots list + final matchingSpot = spots?.firstWhere( + (spot) => spot.id == object.spot, + ); + return BoxModel( + id: object.spot, // Use object spot ID + name: matchingSpot?.title ?? object.title, // Spot title or object title + type: BoxModel.States.entries + .firstWhere( + (entry) => entry.value == matchingSpot?.state, + orElse: () => MapEntry(SeatState.available, BoxModel.availableType)).key, // Map state from spot + x: object.x, + y: object.y, + ); + } else if (object.type == "table") { + return BoxModel( + id: object.spot, // Use object spot ID or null + name: object.title, // Use object title + type: SeatState.black, // "table" type always results in black state + x: object.x, + y: object.y, + ); + } + // If the object type is neither "spot" nor "table", skip it + return null; + }).whereType().toList(); // Filter out null values + } + + + BlueprintModel({ + this.id, + this.createdAt, + this.data, + this.title, + this.organization, + this.configuration, + this.objects, + this.spots, + }); +} diff --git a/lib/dataModelsEshop/BlueprintObject.dart b/lib/dataModelsEshop/BlueprintObject.dart new file mode 100644 index 00000000..348afd3d --- /dev/null +++ b/lib/dataModelsEshop/BlueprintObject.dart @@ -0,0 +1,52 @@ +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; + +class BlueprintObject { + static const String metaX = "x"; + static const String metaY = "y"; + static const String metaSpot = "spot"; + static const String metaType = "type"; + static const String metaState = "state"; + static const String metaTitle = "title"; + static const String metaId = "id"; + + + int? x; + int? y; + int? id; + int? spot; + String? type; + String? state; + String? title; + + factory BlueprintObject.fromJson(Map json) { + return BlueprintObject( + x: json[metaX], + y: json[metaY], + id: json[metaId], + spot: json[metaSpot], + type: json[metaType], + state: json[metaState], + title: json[metaTitle], + ); + } + + Map toJson() => { + metaX: x, + metaY: y, + metaId: id, + metaSpot: spot, + metaType: type, + metaState: state, + metaTitle: title, + }; + + BlueprintObject({ + this.x, + this.y, + this.id, + this.spot, + this.type, + this.state, + this.title, + }); +} diff --git a/lib/dataModelsEshop/TbEshop.dart b/lib/dataModelsEshop/TbEshop.dart index 4394706e..659b3ad6 100644 --- a/lib/dataModelsEshop/TbEshop.dart +++ b/lib/dataModelsEshop/TbEshop.dart @@ -4,6 +4,7 @@ class TbEshop { static OrderItemTicketTb order_item_ticket = const OrderItemTicketTb(); static OrdersTb orders = const OrdersTb(); static TicketsTb tickets = const TicketsTb(); + static BlueprintTb blueprints = const BlueprintTb(); } class ItemTypesTb { @@ -66,3 +67,16 @@ class TicketsTb { String get state => "state"; String get occasion => "occasion"; } + +class BlueprintTb { + const BlueprintTb(); + String get table => "blueprints"; + String get id => "id"; + String get created_at => "created_at"; + String get data => "data"; + String get title => "title"; + String get organization => "organization"; + String get configuration => "configuration"; + String get objects => "objects"; + String get groups => "groups"; +} diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 55044f87..b8c6f1a7 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -1,6 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; import 'package:fstapp/dataModels/FormModel.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; import 'package:fstapp/dataModelsEshop/ItemModel.dart'; import 'package:fstapp/dataModelsEshop/ItemTypeModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; @@ -52,4 +53,20 @@ class DbEshop { return null; } + static Future getBlueprint(String mySecret, String formKey, int blueprintId) async { + final response = await _supabase.rpc( + 'get_blueprint', + params: { + 'my_secret': mySecret, + 'form_key': formKey, + 'blueprint_id': blueprintId, + }, + ); + + if (response["code"] != 200) { + return null; + } + + return BlueprintModel.fromJson(response["data"]); + } } diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 96f8fd8a..cce4e7a8 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -4,6 +4,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataModels/FormOptionModel.dart'; import 'package:fstapp/dataModelsEshop/ItemModel.dart'; import 'package:fstapp/dataModelsEshop/ItemTypeModel.dart'; @@ -34,7 +35,7 @@ class _FormPageState extends State { bool _isSendSuccess = false; double _totalPrice = 0.0; // Total price Map? formData; - Map? fields; + FormModel? form; final _formKey = GlobalKey(); @@ -53,7 +54,7 @@ class _FormPageState extends State { _totalPrice = 0.0; // Iterate over all fields and calculate total price - for (var field in fields?[FormHelper.metaFields] ?? []) { + for (var field in form?.data?[FormHelper.metaFields] ?? []) { // Calculate price for regular options if (field[FormHelper.metaType] == FormHelper.fieldTypeOptions) { var selectedOption = _formKey.currentState?.fields[field[FormHelper.metaOptionsType]]?.value; @@ -73,7 +74,7 @@ class _FormPageState extends State { for (var ticketData in ticketDataList) { for (var ticketValue in ticketData.values) { if (ticketValue is FormOptionModel) { - _totalPrice += ticketValue.price ?? 0.0; + _totalPrice += ticketValue.price; } } } @@ -123,7 +124,7 @@ class _FormPageState extends State { ), ), ) - : fields == null + : form?.data == null ? const Center( child: CircularProgressIndicator(), ) @@ -132,7 +133,7 @@ class _FormPageState extends State { child: AutofillGroup( child: Column( children: [ - ...FormHelper.getFormFields(fields?[FormHelper.metaFields], _updateTotalPrice), + ...FormHelper.getAllFormFields(context, form!, _updateTotalPrice), const SizedBox(height: 16), if (_totalPrice > 0) Text( @@ -156,7 +157,7 @@ class _FormPageState extends State { _isLoading = true; }); var data = FormHelper.getDataFromForm( - _formKey, fields?[FormHelper.metaFields]); + _formKey, form?.data?[FormHelper.metaFields]); data["secret"] = "0fb80818-4c8d-4eb7-8205-859b1d786fb3"; data["form"] = "7f4e3892-a544-4385-b933-61117e9755c3"; @@ -238,17 +239,17 @@ class _FormPageState extends State { // return; // } //var key = UuidConverter.base62ToUuid(widget.id!); - var form = await DbEshop.getForm("7f4e3892-a544-4385-b933-61117e9755c3"); + form = await DbEshop.getForm("7f4e3892-a544-4385-b933-61117e9755c3"); if(form == null) { return; } // Fetching items - var allItems = await DbEshop.getItems(context, form.occasion!); + var allItems = await DbEshop.getItems(context, form!.occasion!); // New fields to replace existing ones List updatedFields = []; // Loop through the fields in form.data - for (var field in form.data![FormHelper.metaFields]) { + for (var field in form?.data![FormHelper.metaFields]) { // Check if the field is a ticket if (field[FormHelper.metaType] == FormHelper.fieldTypeTicket) { // Process the fields inside the ticket @@ -277,9 +278,8 @@ class _FormPageState extends State { } } - form.data![FormHelper.metaFields] = updatedFields; - fields = form.data; + form?.data![FormHelper.metaFields] = updatedFields; setState(() { _isLoading = false; diff --git a/lib/pages/SignupPage.dart b/lib/pages/SignupPage.dart index ab7c7d00..e9e9bfd7 100644 --- a/lib/pages/SignupPage.dart +++ b/lib/pages/SignupPage.dart @@ -1,6 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/cupertino.dart'; import 'package:fstapp/RouterService.dart'; +import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataServices/AuthService.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:fstapp/services/FormHelper.dart'; @@ -10,7 +11,6 @@ import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/ButtonsHelper.dart'; -import 'package:fstapp/styles/StylesConfig.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; @RoutePage() @@ -27,8 +27,7 @@ class _SignupPageState extends State { bool _isRegistrationSuccess = false; Map? fieldsData; - final dynamic fields = - {FormHelper.metaFields: + final FormModel form = FormModel(data: {FormHelper.metaFields: [ { FormHelper.metaType : FormHelper.fieldTypeName, FormHelper.IS_REQUIRED: true}, { FormHelper.metaType : FormHelper.fieldTypeSurname, FormHelper.IS_REQUIRED: true}, @@ -36,7 +35,7 @@ class _SignupPageState extends State { { FormHelper.metaType : FormHelper.fieldTypeEmail, FormHelper.IS_REQUIRED: true}, { FormHelper.metaType : FormHelper.fieldTypeCity, FormHelper.IS_REQUIRED: true}, { FormHelper.metaType : FormHelper.fieldTypeBirthYear}, - ]}; + ]}); final _formKey = GlobalKey(); @override @@ -85,7 +84,7 @@ class _SignupPageState extends State { child: AutofillGroup( child: Column( children: [ - ...FormHelper.getFormFields(fields[FormHelper.metaFields]), + ...FormHelper.getAllFormFields(context, form.data![FormHelper.metaFields]), const SizedBox( height: 16, ), @@ -97,7 +96,7 @@ class _SignupPageState extends State { setState(() { _isLoading = true; }); - var data = FormHelper.getDataFromForm(_formKey, fields[FormHelper.metaFields]); + var data = FormHelper.getDataFromForm(_formKey, form.data![FormHelper.metaFields]); fieldsData = data; var resp = await AuthService.register(data); if (resp["code"] == 200) { diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 61efee04..cdd9e12e 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -1,10 +1,13 @@ import 'package:easy_localization/easy_localization.dart'; +import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataModels/FormOptionModel.dart'; import 'package:fstapp/dataModels/UserInfoModel.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; +import 'package:fstapp/widgets/SeatReservationWidget.dart'; class FormHelper { // Field Type Constants @@ -49,9 +52,17 @@ class FormHelper { static List> ticketValues = []; static List> ticketKeys = []; - // Public method to generate form fields from configuration - static List getFormFields(dynamic fields, [void Function()? updateTotalPrice]) { - return fields.map((field) => createFormField(field, updateTotalPrice)).toList(); + //static String secret = UniqueKey().toString(); + + static String secret = "0fb80818-4c8d-4eb7-8205-859b1d786fb3"; + + + static List getAllFormFields(BuildContext context, FormModel form, [void Function()? updateTotalPrice]) { + return form.data?[FormHelper.metaFields].map((field) => createFormField(context, form, field, updateTotalPrice)).toList(); + } + + static List getFormFields(BuildContext context, FormModel form, dynamic fields, [void Function()? updateTotalPrice]) { + return fields.map((field) => createFormField(context, form, field, updateTotalPrice)).toList(); } // Retrieve form data by iterating over defined fields @@ -106,7 +117,7 @@ class FormHelper { // Create individual form field widget based on configuration - static Widget createFormField(Map field, [void Function()? updateTotalPrice]) { + static Widget createFormField(BuildContext context, FormModel form, Map field, [void Function()? updateTotalPrice]) { final bool isRequiredField = field[IS_REQUIRED] ?? false; switch (field[metaType]) { case fieldTypeNote: @@ -118,7 +129,7 @@ class FormHelper { case fieldTypeCity: return buildTextField(fieldTypeCity, cityLabel(), isRequiredField, [AutofillHints.addressCity]); case fieldTypeSpot: - return buildTextField(fieldTypeSpot, cityLabel(), isRequiredField, [AutofillHints.addressCity]); + return buildSpotField(context, form, fieldTypeSpot, spotLabel()); case fieldTypeEmail: return buildEmailField(isRequiredField); case fieldTypeSex: @@ -128,13 +139,14 @@ class FormHelper { case fieldTypeBirthYear: return buildBirthYearField(fieldTypeBirthYear, birthYearLabel(), isRequiredField); case fieldTypeTicket: - return buildTicketField(field, ticketValues, ticketKeys, updateTotalPrice); + return buildTicketField(form, field, ticketValues, ticketKeys, updateTotalPrice); default: return const SizedBox.shrink(); } } static Widget buildTicketField( + FormModel form, Map field, List> ticketValues, List> ticketKeys, @@ -217,7 +229,7 @@ class FormHelper { key: ticketKeys[i], // Assign the corresponding key onChanged: updateTotalPrice, // Trigger price update on change child: Column( - children: FormHelper.getFormFields(ticketValues[i][FormHelper.metaFields]), + children: getFormFields(context, form, ticketValues[i][FormHelper.metaFields]), ), ), ], @@ -241,6 +253,32 @@ class FormHelper { } + // Build a simple text field with optional validation + static FormBuilderTextField buildSpotField(BuildContext context, FormModel form, String name, String label) { + return FormBuilderTextField( + name: name, + enableInteractiveSelection: false, + readOnly: true, + decoration: InputDecoration(labelText: label, suffixIcon: Icon(Icons.event_seat), labelStyle: StylesConfig.textStyleBig), + validator: FormBuilderValidators.compose([ + FormBuilderValidators.required(), + ]), + onTap: () async { + await showGeneralDialog( + context: context, + barrierColor: Colors.black12.withOpacity(0.6), // Background color + barrierDismissible: false, + barrierLabel: 'Dialog', + transitionDuration: const Duration(milliseconds: 300), + pageBuilder: (context, __, ___) { + return SeatReservationWidget(secret: secret, formKey: form.formKey!, blueprintId: form.blueprint!); + }, + ); + //_formKey.currentState?.fields[TicketModel.boxColumn]!.didChange(selectedSeats.firstOrNull?.toString()); + }, + ); + } + // Build a simple text field with optional validation static FormBuilderTextField buildTextField(String name, String label, bool isRequired, [List? autofillHints]) { return FormBuilderTextField( diff --git a/lib/styles/StylesConfig.dart b/lib/styles/StylesConfig.dart index e9d0fe79..44440d86 100644 --- a/lib/styles/StylesConfig.dart +++ b/lib/styles/StylesConfig.dart @@ -20,6 +20,8 @@ class StylesConfig { // Dimensions static const double appMaxWidth = 820; + static TextStyle textStyleBig = TextStyle(fontWeight: FontWeight.w900, fontSize: 16); + // Button styles static ButtonStyle mainPageButtonStyle(BuildContext context) => OutlinedButton.styleFrom( diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart new file mode 100644 index 00000000..e3fe0e0d --- /dev/null +++ b/lib/widgets/SeatReservationWidget.dart @@ -0,0 +1,288 @@ +import 'package:flutter/material.dart'; +import 'package:fstapp/components/seatReservation/widgets/SeatWidget.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; +import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/dataServices/RightsService.dart'; +import 'package:fstapp/services/DialogHelper.dart'; +import 'package:fstapp/services/ToastHelper.dart'; + +import '../components/seatReservation/model/BoxGroupModel.dart'; +import '../components/seatReservation/model/BoxModel.dart'; +import '../components/seatReservation/model/SeatLayoutStateModel.dart'; +import '../components/seatReservation/model/SeatModel.dart'; +import '../components/seatReservation/utils/SeatState.dart'; +import '../components/seatReservation/widgets/SeatLayoutWidget.dart'; + +class SeatReservationWidget extends StatefulWidget { + final int blueprintId; + final String secret; + final String formKey; + + SeatReservationWidget({Key? key, required this.blueprintId, required this.secret, required this.formKey}) : super(key: key); + + @override + State createState() => _SeatReservationWidgetState(); +} + +class _SeatReservationWidgetState extends State { + Set selectedSeats = {}; + BlueprintModel? blueprint; + _SeatReservationWidgetState(); + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + loadData(); + } + + List? currentBoxes; + BoxGroupModel? currentBoxGroup; + + int currentWidth = 20; + int currentHeight = 20; + + List changedBoxes = []; + List allBoxes = []; + + static int boxSize = 15; + selectionMode currentSelectionMode = selectionMode.normal; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 440), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox( + height: 16, + ), + Text(blueprint?.title ?? "", style: const TextStyle(fontWeight: FontWeight.bold)), + const SizedBox( + height: 16, + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SeatWidget.buildSeat( + state: SeatState.ordered, + size: 15.0, + ), + const SizedBox(width: 8), + const Text("obsazené"), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () { + if (currentBoxGroup != null) { + currentSelectionMode = selectionMode.addAvailable; + } + }, + child: SeatWidget.buildSeat( + state: SeatState.available, + size: 15.0, + ), + ), + const SizedBox(width: 8), + const Text("dostupné"), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SeatWidget.buildSeat( + state: SeatState.selected, + size: 15.0, + ), + const SizedBox(width: 8), + const Text("vybrané"), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () { + if (currentBoxGroup != null) { + currentSelectionMode = selectionMode.addBlack; + } + }, + child: SeatWidget.buildSeat( + state: SeatState.black, + size: 15.0, + ), + ), + const SizedBox(width: 8), + const Text("stůl"), + ], + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + Visibility( + visible: currentBoxes != null, + child: Flexible( + child: SizedBox( + width: currentWidth * boxSize.toDouble(), + height: currentHeight * boxSize.toDouble(), + child: SeatLayoutWidget( + onSeatTap: (model) { + if (currentSelectionMode == selectionMode.addBlack) { + model.seatState = SeatState.black; + changedBoxes.add(model); + } else if (currentSelectionMode == selectionMode.addAvailable) { + model.seatState = SeatState.available; + model.boxModel = model.boxModel ?? BoxModel(x: model.colI, y: model.rowI); + model.boxModel!.boxGroupId = currentBoxGroup!.id; + model.boxModel!.name = currentBoxGroup!.getNextBoxName(); + currentBoxGroup!.boxes!.add(model.boxModel!); + + changedBoxes.add(model); + ToastHelper.Show(context, "Přidáno sedadlo ${model.boxModel!.name}."); + } else if (currentSelectionMode == selectionMode.normal) { + if (model.seatState == SeatState.selected) { + model.seatState = SeatState.available; + changedBoxes.remove(model); + return; + } else if (model.seatState != SeatState.available) { + return; + } + + // available + var alreadySelected = allBoxes.where((b) => b.seatState == SeatState.selected); + if (alreadySelected.isNotEmpty) { + ToastHelper.Show(context, "Je možné vybrat pouze jedno místo!"); + return; + } + model.seatState = SeatState.selected; + changedBoxes.add(model); + } + }, + stateModel: SeatLayoutStateModel( + rows: currentHeight, + cols: currentWidth, + seatSize: boxSize, + currentBoxes: currentBoxes ?? [], + allBoxes: allBoxes, + ), + ), + ), + ), + ), + const SizedBox( + height: 12, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Visibility( + visible: RightsService.isEditor(), + child: ElevatedButton( + onPressed: () async { + Set tables = {}; + tables.addAll(currentBoxes!.where((element) => element.boxGroup != null).map((e) => e.boxGroup!)); + var newTableName = ((tables.length + 1)).toString(); + var result = await DialogHelper.showConfirmationDialogAsync(context, "Přidat stůl", "Chcete přidat stůl ${newTableName}?"); + if (!result) { + return; + } + var newBox = BoxGroupModel(name: newTableName, blueprintId: blueprint?.id); + // currentBoxGroup = await DataService.updateBoxGroup(newBox); + ToastHelper.Show(context, "Vytvořen stůl ${newTableName}."); + }, + child: const Text("přidat stůl"), + ), + ), + const SizedBox(width: 12), + Visibility( + visible: RightsService.isEditor(), + child: ElevatedButton( + onPressed: () async { + Set tables = {}; + tables.addAll(currentBoxes!.where((element) => element.boxGroup != null).map((e) => e.boxGroup!)); + // var selectedTable = await DialogHelper.chooseBoxGroup(context, tables.toList()); + var selectedTable = null; + + if (selectedTable == null) { + return; + } + var result = await DialogHelper.showConfirmationDialogAsync(context, "Přidání ke stolu", "Chcete přidat židle ke stolu ${selectedTable.name!}?"); + if (!result) { + return; + } + currentBoxGroup = selectedTable; + ToastHelper.Show(context, "Nyní přidáváte ke stolu ${selectedTable.name}."); + }, + child: const Text("přidat ke stolu"), + ), + ), + const SizedBox(width: 12), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text("zpět"), + ), + const SizedBox(width: 12), + ElevatedButton( + onPressed: () { + var changed = changedBoxes.map((e) { + var newBoxModel = e.boxModel ?? BoxModel(x: e.colI, y: e.rowI); + newBoxModel.type = e.seatState; + return newBoxModel; + }).toList(); + + // only non-selected + selectedSeats.clear(); + selectedSeats.addAll(changed.where((element) => element.type == BoxModel.selectedType)); + changed = changed.where((element) => element.type != BoxModel.selectedType).toList(); + + // DataService.updateBoxes(changed); + Navigator.pop(context); + }, + child: const Text("uložit"), + ), + ], + ), + const SizedBox(height: 12), + ], + ), + ), + ), + ), + ); + } + + void loadData() async { + blueprint = await DbEshop.getBlueprint(widget.secret, widget.formKey, widget.blueprintId); + if (blueprint == null) { + return; + } + + setState(() { + currentBoxes = blueprint!.toBoxModels(); + currentHeight = blueprint!.configuration!.height!; + currentWidth = blueprint!.configuration!.width!; + }); + } +} + +enum selectionMode { + normal, + addBlack, + addAvailable, +} diff --git a/scripts/database/eshop/get_blueprint.sql b/scripts/database/eshop/get_blueprint.sql new file mode 100644 index 00000000..7690f9f0 --- /dev/null +++ b/scripts/database/eshop/get_blueprint.sql @@ -0,0 +1,91 @@ +CREATE OR REPLACE FUNCTION get_blueprint( + my_secret UUID, + form_key UUID, + blueprint_id BIGINT +) +RETURNS JSONB +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + blueprintData JSONB; + spotsData JSONB; +BEGIN + -- Validate the form and blueprint association + IF NOT EXISTS ( + SELECT 1 + FROM public.forms f + WHERE f.key = form_key + AND f.is_open = true + AND EXISTS ( + SELECT 1 + FROM eshop.blueprints b + WHERE b.id = blueprint_id + AND f.blueprint = blueprint_id + ) + ) THEN + RETURN jsonb_build_object( + 'code', 400, + 'message', 'Invalid form key or blueprint association.' + ); + END IF; + + + + -- Fetch blueprint details + SELECT jsonb_build_object( + 'id', b.id, + 'created_at', b.created_at, + 'data', b.data, + 'title', b.title, + 'configuration', b.configuration, + 'objects', b.objects + ) + INTO blueprintData + FROM eshop.blueprints b + WHERE b.id = blueprint_id; + + -- Extract valid spot IDs from blueprint.objects + WITH valid_spots AS ( + SELECT (obj->>'spot')::BIGINT AS spot_id + FROM jsonb_array_elements( + (SELECT b.objects FROM eshop.blueprints b WHERE b.id = blueprint_id) + ) obj + WHERE obj->>'type' = 'spot' + AND obj->>'spot' IS NOT NULL + ) + -- Fetch enriched spots data + SELECT jsonb_agg(jsonb_build_object( + 'id', s.id, + 'title', s.title, + 'state', CASE + WHEN s.order_item_ticket IS NOT NULL THEN 'ordered' + WHEN s.secret IS NOT NULL AND s.secret_expiration_time > now() THEN + CASE + WHEN s.secret = my_secret THEN 'selected_by_me' + ELSE 'selected' + END + ELSE 'available' + END + )) + INTO spotsData + FROM eshop.spots s + JOIN valid_spots v ON s.id = v.spot_id + WHERE s.occasion = ( + SELECT occasion FROM public.forms WHERE key = form_key + ); + + -- Add spots data to the blueprint object + blueprintData = jsonb_set( + blueprintData, + '{spots}', + COALESCE(spotsData, '[]'::jsonb) + ); + + -- Return combined data + RETURN jsonb_build_object( + 'code', 200, + 'data', blueprintData + ); +END; +$$; \ No newline at end of file diff --git a/scripts/database/eshop/get_form.sql b/scripts/database/eshop/get_form.sql index 2accda67..7a554f76 100644 --- a/scripts/database/eshop/get_form.sql +++ b/scripts/database/eshop/get_form.sql @@ -21,10 +21,12 @@ BEGIN 'code', 200, 'data', jsonb_build_object( 'id', f.id, + 'key', f.key, 'created_at', f.created_at, 'data', f.data, 'type', f.type, 'occasion', f.occasion, + 'blueprint', f.blueprint, 'deadline_duration_seconds', f.deadline_duration_seconds, 'account_number', ba.account_number ) From 5eedd4c52fa821b9dda206e24004dea9254860ce Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:45:02 +0100 Subject: [PATCH 044/159] little cleanup --- lib/dataModels/OccasionUserModel.dart | 3 --- lib/dataModelsEshop/BlueprintObject.dart | 2 -- 2 files changed, 5 deletions(-) diff --git a/lib/dataModels/OccasionUserModel.dart b/lib/dataModels/OccasionUserModel.dart index fcf9ea57..a56488a8 100644 --- a/lib/dataModels/OccasionUserModel.dart +++ b/lib/dataModels/OccasionUserModel.dart @@ -254,9 +254,6 @@ class OccasionUserModel extends IPlutoRowModel { } bool compareField(Map u, dynamic dataField, dynamic uField) { - if(!compareStrings(u[uField], data?[dataField])){ - var x= 1; - } return compareStrings(u[uField], data?[dataField]); } diff --git a/lib/dataModelsEshop/BlueprintObject.dart b/lib/dataModelsEshop/BlueprintObject.dart index 348afd3d..f47acb13 100644 --- a/lib/dataModelsEshop/BlueprintObject.dart +++ b/lib/dataModelsEshop/BlueprintObject.dart @@ -1,5 +1,3 @@ -import 'package:fstapp/dataModelsEshop/TbEshop.dart'; - class BlueprintObject { static const String metaX = "x"; static const String metaY = "y"; From 75a59605d65d73f0a61f11fb61236153980aeb35 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:13:57 +0100 Subject: [PATCH 045/159] autoresize --- .../widgets/SeatLayoutWidget.dart | 114 +++++++++++------- lib/widgets/SeatReservationWidget.dart | 79 ++++++------ 2 files changed, 107 insertions(+), 86 deletions(-) diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index 85b25afd..e713a997 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -1,76 +1,100 @@ -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; import '../model/SeatLayoutStateModel.dart'; import '../model/SeatModel.dart'; import '../utils/SeatState.dart'; import 'SeatWidget.dart'; - - -class SeatLayoutWidget extends StatelessWidget { - +class SeatLayoutWidget extends StatefulWidget { final SeatLayoutStateModel stateModel; - void Function(SeatModel model)? onSeatTap; + final void Function(SeatModel model)? onSeatTap; - SeatLayoutWidget({ + const SeatLayoutWidget({ Key? key, required this.stateModel, this.onSeatTap, }) : super(key: key); + @override + _SeatLayoutWidgetState createState() => _SeatLayoutWidgetState(); +} + +class _SeatLayoutWidgetState extends State { + final TransformationController _controller = TransformationController(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _fitLayout(); + }); + } + + void _fitLayout() { + if (!mounted) return; + + final RenderBox renderBox = context.findRenderObject() as RenderBox; + double widgetWidth = renderBox.size.width; + double widgetHeight = renderBox.size.height; + + int layoutWidth = widget.stateModel.cols * widget.stateModel.seatSize; + int layoutHeight = widget.stateModel.rows * widget.stateModel.seatSize; + + double scaleX = widgetWidth / layoutWidth; + double scaleY = widgetHeight / layoutHeight; + double scaleFactor = scaleX < scaleY ? scaleX : scaleY; + + setState(() { + _controller.value = Matrix4.identity() + ..scale(scaleFactor) + ..setTranslationRaw( + (widgetWidth - layoutWidth * scaleFactor) / 2, + (widgetHeight - layoutHeight * scaleFactor) / 2, + 0, + ); + }); + } @override Widget build(BuildContext context) { return InteractiveViewer( - alignment: Alignment.topLeft, + minScale: 0.1, maxScale: 5, - minScale: 0.8, - boundaryMargin: const EdgeInsets.all(8), + boundaryMargin: const EdgeInsets.all(double.infinity), constrained: false, + transformationController: _controller, child: Column( - children: [ - ...List.generate(stateModel.rows, (rowI) => rowI) - .map( - (rowI) => Row( - children: [ - ...List.generate(stateModel.cols, (colI) => colI) - .map((colI) { - var seatModel = createSeat(colI, rowI); - if(seatModel.boxModel != null) - { - return Tooltip( - showDuration: const Duration(seconds: 0), - message: seatModel.boxModel!.toShortString()??"", - child: SeatWidget( - model: seatModel, - onSeatTap: onSeatTap, - ), - ); - } - - return SeatWidget( - model: seatModel, - onSeatTap: onSeatTap, - ); - }) - ], + mainAxisSize: MainAxisSize.min, + children: List.generate(widget.stateModel.rows, (rowI) { + return Row( + mainAxisSize: MainAxisSize.min, + children: List.generate(widget.stateModel.cols, (colI) { + final seatModel = _createSeat(colI, rowI); + return Tooltip( + showDuration: const Duration(seconds: 0), + message: seatModel.boxModel?.toShortString() ?? "", + child: SeatWidget( + model: seatModel, + onSeatTap: widget.onSeatTap, ), - ) - ], + ); + }), + ); + }), ), ); } - SeatModel createSeat(int colI, int rowI) { - var model = SeatModel( - boxModel: stateModel.currentBoxes.firstWhereOrNull((b)=>b.x==colI&&b.y==rowI), - seatState: stateModel.currentBoxes.firstWhereOrNull((b)=>b.x==colI&&b.y==rowI)?.type??SeatState.empty, + SeatModel _createSeat(int colI, int rowI) { + var boxModel = widget.stateModel.currentBoxes + .firstWhereOrNull((b) => b.x == colI && b.y == rowI); + return SeatModel( + boxModel: boxModel, + seatState: boxModel?.type ?? SeatState.empty, rowI: rowI, colI: colI, - seatSize: stateModel.seatSize, + seatSize: widget.stateModel.seatSize, ); - stateModel.allBoxes.add(model); - return model; } } diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index e3fe0e0d..16482f8a 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -5,6 +5,7 @@ import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/services/DialogHelper.dart'; import 'package:fstapp/services/ToastHelper.dart'; +import 'package:fstapp/styles/StylesConfig.dart'; import '../components/seatReservation/model/BoxGroupModel.dart'; import '../components/seatReservation/model/BoxModel.dart'; @@ -53,7 +54,7 @@ class _SeatReservationWidgetState extends State { body: SafeArea( child: Center( child: Container( - constraints: const BoxConstraints(maxWidth: 440), + constraints: const BoxConstraints(maxWidth: StylesConfig.appMaxWidth), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -136,49 +137,45 @@ class _SeatReservationWidgetState extends State { Visibility( visible: currentBoxes != null, child: Flexible( - child: SizedBox( - width: currentWidth * boxSize.toDouble(), - height: currentHeight * boxSize.toDouble(), - child: SeatLayoutWidget( - onSeatTap: (model) { - if (currentSelectionMode == selectionMode.addBlack) { - model.seatState = SeatState.black; - changedBoxes.add(model); - } else if (currentSelectionMode == selectionMode.addAvailable) { - model.seatState = SeatState.available; - model.boxModel = model.boxModel ?? BoxModel(x: model.colI, y: model.rowI); - model.boxModel!.boxGroupId = currentBoxGroup!.id; - model.boxModel!.name = currentBoxGroup!.getNextBoxName(); - currentBoxGroup!.boxes!.add(model.boxModel!); + child: SeatLayoutWidget( + onSeatTap: (model) { + if (currentSelectionMode == selectionMode.addBlack) { + model.seatState = SeatState.black; + changedBoxes.add(model); + } else if (currentSelectionMode == selectionMode.addAvailable) { + model.seatState = SeatState.available; + model.boxModel = model.boxModel ?? BoxModel(x: model.colI, y: model.rowI); + model.boxModel!.boxGroupId = currentBoxGroup!.id; + model.boxModel!.name = currentBoxGroup!.getNextBoxName(); + currentBoxGroup!.boxes!.add(model.boxModel!); - changedBoxes.add(model); - ToastHelper.Show(context, "Přidáno sedadlo ${model.boxModel!.name}."); - } else if (currentSelectionMode == selectionMode.normal) { - if (model.seatState == SeatState.selected) { - model.seatState = SeatState.available; - changedBoxes.remove(model); - return; - } else if (model.seatState != SeatState.available) { - return; - } + changedBoxes.add(model); + ToastHelper.Show(context, "Přidáno sedadlo ${model.boxModel!.name}."); + } else if (currentSelectionMode == selectionMode.normal) { + if (model.seatState == SeatState.selected) { + model.seatState = SeatState.available; + changedBoxes.remove(model); + return; + } else if (model.seatState != SeatState.available) { + return; + } - // available - var alreadySelected = allBoxes.where((b) => b.seatState == SeatState.selected); - if (alreadySelected.isNotEmpty) { - ToastHelper.Show(context, "Je možné vybrat pouze jedno místo!"); - return; - } - model.seatState = SeatState.selected; - changedBoxes.add(model); + // available + var alreadySelected = allBoxes.where((b) => b.seatState == SeatState.selected); + if (alreadySelected.isNotEmpty) { + ToastHelper.Show(context, "Je možné vybrat pouze jedno místo!"); + return; } - }, - stateModel: SeatLayoutStateModel( - rows: currentHeight, - cols: currentWidth, - seatSize: boxSize, - currentBoxes: currentBoxes ?? [], - allBoxes: allBoxes, - ), + model.seatState = SeatState.selected; + changedBoxes.add(model); + } + }, + stateModel: SeatLayoutStateModel( + rows: currentHeight, + cols: currentWidth, + seatSize: boxSize, + currentBoxes: currentBoxes ?? [], + allBoxes: allBoxes, ), ), ), From 802e9333d6de343cc9e7f8337504ed8b4052761b Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 2 Dec 2024 18:03:28 +0100 Subject: [PATCH 046/159] items to products --- lib/dataModelsEshop/ItemModel.dart | 60 ------------- lib/dataModelsEshop/ItemTypeModel.dart | 61 -------------- lib/dataModelsEshop/ProductModel.dart | 60 +++++++++++++ lib/dataModelsEshop/ProductTypeModel.dart | 61 ++++++++++++++ lib/dataModelsEshop/TbEshop.dart | 28 +++---- lib/dataServices/DbEshop.dart | 30 +++---- lib/pages/FormPage.dart | 12 +-- .../database/eshop/create_ticket_order.sql | 84 +++++++++---------- scripts/database/eshop/get_blueprint.sql | 5 +- scripts/database/eshop/validate_item_type.sql | 26 ------ .../database/eshop/validate_product_type.sql | 26 ++++++ 11 files changed, 227 insertions(+), 226 deletions(-) delete mode 100644 lib/dataModelsEshop/ItemModel.dart delete mode 100644 lib/dataModelsEshop/ItemTypeModel.dart create mode 100644 lib/dataModelsEshop/ProductModel.dart create mode 100644 lib/dataModelsEshop/ProductTypeModel.dart delete mode 100644 scripts/database/eshop/validate_item_type.sql create mode 100644 scripts/database/eshop/validate_product_type.sql diff --git a/lib/dataModelsEshop/ItemModel.dart b/lib/dataModelsEshop/ItemModel.dart deleted file mode 100644 index 63658afd..00000000 --- a/lib/dataModelsEshop/ItemModel.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:fstapp/dataModelsEshop/TbEshop.dart'; - -class ItemModel { - int? id; - DateTime? createdAt; - DateTime? updatedAt; - String? title; - bool? isHidden; - String? description; - double? price; - Map? data; - int? itemType; - int? occasion; - - static const String foodType = "food"; - static const String taxiType = "taxi"; - - factory ItemModel.fromJson(Map json) { - return ItemModel( - id: json[TbEshop.items.id], - createdAt: json[TbEshop.items.created_at] != null ? DateTime.parse(json[TbEshop.items.created_at]) : null, - updatedAt: json[TbEshop.items.updated_at] != null ? DateTime.parse(json[TbEshop.items.updated_at]) : null, - title: json[TbEshop.items.title], - isHidden: json[TbEshop.items.is_hidden], - description: json[TbEshop.items.description], - price: json[TbEshop.items.price] != null ? double.tryParse(json[TbEshop.items.price].toString()) : null, - data: json[TbEshop.items.data], - itemType: json[TbEshop.items.item_type], - occasion: json[TbEshop.items.occasion], - ); - } - - Map toJson() => { - TbEshop.items.id: id, - TbEshop.items.created_at: createdAt?.toIso8601String(), - TbEshop.items.updated_at: updatedAt?.toIso8601String(), - TbEshop.items.title: title, - TbEshop.items.is_hidden: isHidden, - TbEshop.items.description: description, - TbEshop.items.price: price, - TbEshop.items.data: data, - TbEshop.items.item_type: itemType, - TbEshop.items.occasion: occasion, - }; - - String toBasicString() => title ?? id.toString(); - - ItemModel({ - this.id, - this.createdAt, - this.updatedAt, - this.title, - this.isHidden, - this.description, - this.price, - this.data, - this.itemType, - this.occasion, - }); -} diff --git a/lib/dataModelsEshop/ItemTypeModel.dart b/lib/dataModelsEshop/ItemTypeModel.dart deleted file mode 100644 index bb5fa12a..00000000 --- a/lib/dataModelsEshop/ItemTypeModel.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:fstapp/dataModelsEshop/ItemModel.dart'; -import 'package:fstapp/dataModelsEshop/TbEshop.dart'; - -class ItemTypeModel { - int? id; - DateTime? createdAt; - DateTime? updatedAt; - String? title; - String? description; - String? type; - Map? data; - int? occasion; - List? items; // Embedded list of items - - factory ItemTypeModel.fromJson(Map json) { - return ItemTypeModel( - id: json[TbEshop.item_types.id], - createdAt: json[TbEshop.item_types.created_at] != null - ? DateTime.parse(json[TbEshop.item_types.created_at]) - : null, - updatedAt: json[TbEshop.item_types.updated_at] != null - ? DateTime.parse(json[TbEshop.item_types.updated_at]) - : null, - title: json[TbEshop.item_types.title], - description: json[TbEshop.item_types.description], - type: json[TbEshop.item_types.type], - data: json[TbEshop.item_types.data], - occasion: json[TbEshop.item_types.occasion], - items: json[TbEshop.items.table] != null - ? List.from( - json[TbEshop.items.table].map((item) => ItemModel.fromJson(item))) - : null, - ); - } - - Map toJson() => { - TbEshop.item_types.id: id, - TbEshop.item_types.created_at: createdAt?.toIso8601String(), - TbEshop.item_types.updated_at: updatedAt?.toIso8601String(), - TbEshop.item_types.title: title, - TbEshop.item_types.description: description, - TbEshop.item_types.type: type, - TbEshop.item_types.data: data, - TbEshop.item_types.occasion: occasion, - TbEshop.items.table: items?.map((item) => item.toJson()).toList(), - }; - - String toBasicString() => title ?? id.toString(); - - ItemTypeModel({ - this.id, - this.createdAt, - this.updatedAt, - this.title, - this.description, - this.type, - this.data, - this.occasion, - this.items, - }); -} diff --git a/lib/dataModelsEshop/ProductModel.dart b/lib/dataModelsEshop/ProductModel.dart new file mode 100644 index 00000000..0fec58ed --- /dev/null +++ b/lib/dataModelsEshop/ProductModel.dart @@ -0,0 +1,60 @@ +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; + +class ProductModel { + int? id; + DateTime? createdAt; + DateTime? updatedAt; + String? title; + bool? isHidden; + String? description; + double? price; + Map? data; + int? productType; + int? occasion; + + static const String foodType = "food"; + static const String taxiType = "taxi"; + + factory ProductModel.fromJson(Map json) { + return ProductModel( + id: json[TbEshop.products.id], + createdAt: json[TbEshop.products.created_at] != null ? DateTime.parse(json[TbEshop.products.created_at]) : null, + updatedAt: json[TbEshop.products.updated_at] != null ? DateTime.parse(json[TbEshop.products.updated_at]) : null, + title: json[TbEshop.products.title], + isHidden: json[TbEshop.products.is_hidden], + description: json[TbEshop.products.description], + price: json[TbEshop.products.price] != null ? double.tryParse(json[TbEshop.products.price].toString()) : null, + data: json[TbEshop.products.data], + productType: json[TbEshop.products.product_type], + occasion: json[TbEshop.products.occasion], + ); + } + + Map toJson() => { + TbEshop.products.id: id, + TbEshop.products.created_at: createdAt?.toIso8601String(), + TbEshop.products.updated_at: updatedAt?.toIso8601String(), + TbEshop.products.title: title, + TbEshop.products.is_hidden: isHidden, + TbEshop.products.description: description, + TbEshop.products.price: price, + TbEshop.products.data: data, + TbEshop.products.product_type: productType, + TbEshop.products.occasion: occasion, + }; + + String toBasicString() => title ?? id.toString(); + + ProductModel({ + this.id, + this.createdAt, + this.updatedAt, + this.title, + this.isHidden, + this.description, + this.price, + this.data, + this.productType, + this.occasion, + }); +} diff --git a/lib/dataModelsEshop/ProductTypeModel.dart b/lib/dataModelsEshop/ProductTypeModel.dart new file mode 100644 index 00000000..c9dd6f77 --- /dev/null +++ b/lib/dataModelsEshop/ProductTypeModel.dart @@ -0,0 +1,61 @@ +import 'package:fstapp/dataModelsEshop/ProductModel.dart'; +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; + +class ProductTypeModel { + int? id; + DateTime? createdAt; + DateTime? updatedAt; + String? title; + String? description; + String? type; + Map? data; + int? occasion; + List? products; + + factory ProductTypeModel.fromJson(Map json) { + return ProductTypeModel( + id: json[TbEshop.product_types.id], + createdAt: json[TbEshop.product_types.created_at] != null + ? DateTime.parse(json[TbEshop.product_types.created_at]) + : null, + updatedAt: json[TbEshop.product_types.updated_at] != null + ? DateTime.parse(json[TbEshop.product_types.updated_at]) + : null, + title: json[TbEshop.product_types.title], + description: json[TbEshop.product_types.description], + type: json[TbEshop.product_types.type], + data: json[TbEshop.product_types.data], + occasion: json[TbEshop.product_types.occasion], + products: json[TbEshop.products.table] != null + ? List.from( + json[TbEshop.products.table].map((p) => ProductModel.fromJson(p))) + : null, + ); + } + + Map toJson() => { + TbEshop.product_types.id: id, + TbEshop.product_types.created_at: createdAt?.toIso8601String(), + TbEshop.product_types.updated_at: updatedAt?.toIso8601String(), + TbEshop.product_types.title: title, + TbEshop.product_types.description: description, + TbEshop.product_types.type: type, + TbEshop.product_types.data: data, + TbEshop.product_types.occasion: occasion, + TbEshop.products.table: products?.map((p) => p.toJson()).toList(), + }; + + String toBasicString() => title ?? id.toString(); + + ProductTypeModel({ + this.id, + this.createdAt, + this.updatedAt, + this.title, + this.description, + this.type, + this.data, + this.occasion, + this.products, + }); +} diff --git a/lib/dataModelsEshop/TbEshop.dart b/lib/dataModelsEshop/TbEshop.dart index 659b3ad6..69f3540f 100644 --- a/lib/dataModelsEshop/TbEshop.dart +++ b/lib/dataModelsEshop/TbEshop.dart @@ -1,15 +1,15 @@ class TbEshop { - static ItemTypesTb item_types = const ItemTypesTb(); - static ItemsTb items = const ItemsTb(); - static OrderItemTicketTb order_item_ticket = const OrderItemTicketTb(); + static ProductTypesTb product_types = const ProductTypesTb(); + static ProductsTb products = const ProductsTb(); + static OrderProductTicketTb order_product_ticket = const OrderProductTicketTb(); static OrdersTb orders = const OrdersTb(); static TicketsTb tickets = const TicketsTb(); static BlueprintTb blueprints = const BlueprintTb(); } -class ItemTypesTb { - const ItemTypesTb(); - String get table => "item_types"; +class ProductTypesTb { + const ProductTypesTb(); + String get table => "product_types"; String get id => "id"; String get created_at => "created_at"; String get updated_at => "updated_at"; @@ -20,9 +20,9 @@ class ItemTypesTb { String get occasion => "occasion"; } -class ItemsTb { - const ItemsTb(); - String get table => "items"; +class ProductsTb { + const ProductsTb(); + String get table => "products"; String get id => "id"; String get created_at => "created_at"; String get updated_at => "updated_at"; @@ -31,17 +31,17 @@ class ItemsTb { String get description => "description"; String get price => "price"; String get data => "data"; - String get item_type => "item_type"; + String get product_type => "product_type"; String get occasion => "occasion"; } -class OrderItemTicketTb { - const OrderItemTicketTb(); - String get table => "order_item_ticket"; +class OrderProductTicketTb { + const OrderProductTicketTb(); + String get table => "order_product_ticket"; String get id => "id"; String get created_at => "created_at"; String get order => "order"; - String get item => "item"; + String get product => "product"; String get ticket => "ticket"; } diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index b8c6f1a7..bf6f2944 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -2,8 +2,8 @@ import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; -import 'package:fstapp/dataModelsEshop/ItemModel.dart'; -import 'package:fstapp/dataModelsEshop/ItemTypeModel.dart'; +import 'package:fstapp/dataModelsEshop/ProductModel.dart'; +import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; import 'package:fstapp/services/Utilities.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -12,24 +12,24 @@ class DbEshop { static final _supabaseEshop = Supabase.instance.client.schema("eshop"); static final _supabase = Supabase.instance.client; - static Future> getItems(BuildContext context, int currentOccasion) async { + static Future> getProducts(BuildContext context, int currentOccasion) async { var data = await _supabaseEshop - .from(TbEshop.item_types.table) + .from(TbEshop.product_types.table) .select( - "${TbEshop.item_types.id}," - "${TbEshop.item_types.type}," - "${TbEshop.item_types.title}," - "${TbEshop.items.table}(${TbEshop.items.id},${TbEshop.items.title},${TbEshop.items.price})" + "${TbEshop.product_types.id}," + "${TbEshop.product_types.type}," + "${TbEshop.product_types.title}," + "${TbEshop.products.table}(${TbEshop.products.id},${TbEshop.products.title},${TbEshop.products.price})" ) - .eq(TbEshop.item_types.occasion, currentOccasion) - .eq("${TbEshop.items.table}.${TbEshop.items.is_hidden}", false); + .eq(TbEshop.product_types.occasion, currentOccasion) + .eq("${TbEshop.products.table}.${TbEshop.products.is_hidden}", false); - var infoList = List.from( + var infoList = List.from( data.map((x) { - var toReturn = ItemTypeModel.fromJson(x); - toReturn.items = toReturn.items?.sortedBy((i) => i.title ?? ""); - toReturn.items = toReturn.items?.sortedBy((i) => i.price ?? 0); - for (ItemModel v in toReturn.items??[]){ + var toReturn = ProductTypeModel.fromJson(x); + toReturn.products = toReturn.products?.sortedBy((i) => i.title ?? ""); + toReturn.products = toReturn.products?.sortedBy((i) => i.price ?? 0); + for (ProductModel v in toReturn.products??[]){ v.title = v.price != null && v.price! > 0 ? "${v.title} (${Utilities.formatPrice(context, v.price!)})" : v.title; } return toReturn; diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index cce4e7a8..f4f06fcc 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -6,8 +6,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataModels/FormOptionModel.dart'; -import 'package:fstapp/dataModelsEshop/ItemModel.dart'; -import 'package:fstapp/dataModelsEshop/ItemTypeModel.dart'; +import 'package:fstapp/dataModelsEshop/ProductModel.dart'; +import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/services/FormHelper.dart'; import 'package:fstapp/services/Utilities.dart'; @@ -203,18 +203,18 @@ class _FormPageState extends State { // Function to generate options for a specific item type and add them to the ticket fields Map generateOptionsForItemType( - List allItems, + List allItems, String itemType ) { var itemTypeModel = allItems.firstWhereOrNull((item) => item.type == itemType); - if (itemTypeModel == null || itemTypeModel.items == null) { + if (itemTypeModel == null || itemTypeModel.products == null) { return {}; } List> options = []; - for (var item in itemTypeModel.items!) { + for (var item in itemTypeModel.products!) { options.add({ FormOptionModel.metaOptionsName: item.title.toString(), FormOptionModel.metaOptionsId: item.id.toString(), @@ -244,7 +244,7 @@ class _FormPageState extends State { return; } // Fetching items - var allItems = await DbEshop.getItems(context, form!.occasion!); + var allItems = await DbEshop.getProducts(context, form!.occasion!); // New fields to replace existing ones List updatedFields = []; diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index e2acc3d6..ce912251 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -11,7 +11,7 @@ DECLARE now TIMESTAMP WITH TIME ZONE := NOW(); calculated_price DOUBLE PRECISION := 0; spot_secret UUID; - item_id BIGINT; + product_id BIGINT; used_spots JSONB := '[]'::JSONB; occasion_id BIGINT; organization_id BIGINT; @@ -19,11 +19,11 @@ DECLARE account_number TEXT; account_number_human_readable TEXT; ticket_details JSONB := '[]'::JSONB; - item_data RECORD; + product_data RECORD; ticket_id BIGINT; - order_item_ticket_id BIGINT; + order_product_ticket_id BIGINT; ticket_symbol TEXT; - ticket_items JSONB := '[]'::JSONB; + ticket_products JSONB := '[]'::JSONB; payment_info_id BIGINT; generated_variable_symbol BIGINT; bank_account_id BIGINT; @@ -100,8 +100,8 @@ BEGIN RETURN JSONB_BUILD_OBJECT('code', 1007, 'message', 'Invalid or unrelated spot'); END IF; - -- Check if spot is already used (order_item_ticket is not NULL) - IF spot_data.order_item_ticket IS NOT NULL THEN + -- Check if spot is already used (order_product_ticket is not NULL) + IF spot_data.order_product_ticket IS NOT NULL THEN RETURN JSONB_BUILD_OBJECT('code', 1008, 'message', 'Spot is already reserved or in use'); END IF; @@ -122,71 +122,71 @@ BEGIN VALUES ('ordered', occasion_id, ticket_symbol, now, now) RETURNING id INTO ticket_id; - -- Initialize ticket items array - ticket_items := '[]'::JSONB; + -- Initialize ticket products array + ticket_products := '[]'::JSONB; - -- Process taxi, food, and spot items - FOREACH item_id IN ARRAY ARRAY[ + -- Process taxi, food, and spot products + FOREACH product_id IN ARRAY ARRAY[ (ticket_data->'taxi'->>'id')::BIGINT, (ticket_data->'food'->>'id')::BIGINT, - spot_data.item + spot_data.product ] LOOP - IF item_id IS NOT NULL THEN + IF product_id IS NOT NULL THEN SELECT i.*, it.type, i.price, i.is_hidden - INTO item_data - FROM eshop.items i - LEFT JOIN eshop.item_types it ON i.item_type = it.id - WHERE i.id = item_id AND i.occasion = occasion_id; + INTO product_data + FROM eshop.products i + LEFT JOIN eshop.product_types it ON i.product_type = it.id + WHERE i.id = product_id AND i.occasion = occasion_id; - IF item_data IS NULL THEN + IF product_data IS NULL THEN RETURN JSONB_BUILD_OBJECT( 'code', 1011, - 'message', 'Item not found or not part of occasion', - 'details', item_id + 'message', 'product not found or not part of occasion', + 'details', product_id ); END IF; - -- Check if item is hidden - IF item_data.is_hidden THEN + -- Check if product is hidden + IF product_data.is_hidden THEN RETURN JSONB_BUILD_OBJECT( 'code', 1012, - 'message', 'Selected item is hidden and cannot be ordered', - 'item_id', item_id + 'message', 'Selected product is hidden and cannot be ordered', + 'product_id', product_id ); END IF; - -- Add item details to ticket items - ticket_items := ticket_items || JSONB_BUILD_OBJECT( - 'item_id', item_id, - 'title', item_data.title, - 'type', item_data.type, - 'price', item_data.price, - 'spot_title', CASE WHEN item_id = spot_data.item THEN spot_data.title ELSE NULL END, - 'description', CASE WHEN item_id = spot_data.item THEN item_data.description ELSE NULL END + -- Add product details to ticket products + ticket_products := ticket_products || JSONB_BUILD_OBJECT( + 'product_id', product_id, + 'title', product_data.title, + 'type', product_data.type, + 'price', product_data.price, + 'spot_title', CASE WHEN product_id = spot_data.product THEN spot_data.title ELSE NULL END, + 'description', CASE WHEN product_id = spot_data.product THEN product_data.description ELSE NULL END ); - -- Add item price to calculated total - calculated_price := calculated_price + COALESCE(item_data.price, 0); + -- Add product price to calculated total + calculated_price := calculated_price + COALESCE(product_data.price, 0); - -- Link ticket and item to the order - INSERT INTO eshop.order_item_ticket ("order", item, ticket) - VALUES (order_id, item_id, ticket_id) - RETURNING id INTO order_item_ticket_id; + -- Link ticket and product to the order + INSERT INTO eshop.order_product_ticket ("order", product, ticket) + VALUES (order_id, product_id, ticket_id) + RETURNING id INTO order_product_ticket_id; - -- Assign the order_item_ticket ID to the spot - IF item_id = spot_data.item THEN + -- Assign the order_product_ticket ID to the spot + IF product_id = spot_data.product THEN UPDATE eshop.spots - SET order_item_ticket = order_item_ticket_id, updated_at = now + SET order_product_ticket = order_product_ticket_id, updated_at = now WHERE id = spot_data.id; END IF; END IF; END LOOP; - -- Add ticket with its items to ticket details + -- Add ticket with its products to ticket details ticket_details := ticket_details || JSONB_BUILD_OBJECT( 'ticket_id', ticket_id, 'ticket_symbol', ticket_symbol, - 'items', ticket_items + 'products', ticket_products ); END LOOP; diff --git a/scripts/database/eshop/get_blueprint.sql b/scripts/database/eshop/get_blueprint.sql index 7690f9f0..68b25181 100644 --- a/scripts/database/eshop/get_blueprint.sql +++ b/scripts/database/eshop/get_blueprint.sql @@ -39,7 +39,8 @@ BEGIN 'data', b.data, 'title', b.title, 'configuration', b.configuration, - 'objects', b.objects + 'objects', b.objects, + 'groups', b.groups ) INTO blueprintData FROM eshop.blueprints b @@ -59,7 +60,7 @@ BEGIN 'id', s.id, 'title', s.title, 'state', CASE - WHEN s.order_item_ticket IS NOT NULL THEN 'ordered' + WHEN s.order_product_ticket IS NOT NULL THEN 'ordered' WHEN s.secret IS NOT NULL AND s.secret_expiration_time > now() THEN CASE WHEN s.secret = my_secret THEN 'selected_by_me' diff --git a/scripts/database/eshop/validate_item_type.sql b/scripts/database/eshop/validate_item_type.sql deleted file mode 100644 index c6cda857..00000000 --- a/scripts/database/eshop/validate_item_type.sql +++ /dev/null @@ -1,26 +0,0 @@ -CREATE OR REPLACE FUNCTION validate_item_type(item_id BIGINT, required_type TEXT) -RETURNS BOOLEAN -LANGUAGE plpgsql -AS $$ -DECLARE - item_type RECORD; -BEGIN - -- Retrieve the item's type - SELECT it.* - INTO item_type - FROM eshop.items i - JOIN eshop.item_types it ON i.item_type = it.id - WHERE i.id = item_id; - - -- Check if the type matches - IF item_type IS NULL THEN - RAISE EXCEPTION 'Item with ID % not found or has no type.', item_id; - END IF; - - IF item_type.type = required_type THEN - RETURN TRUE; - ELSE - RAISE EXCEPTION 'Item with ID % is not of type %. Found type: %', item_id, required_type, item_type.type; - END IF; -END; -$$; diff --git a/scripts/database/eshop/validate_product_type.sql b/scripts/database/eshop/validate_product_type.sql new file mode 100644 index 00000000..0d0f0227 --- /dev/null +++ b/scripts/database/eshop/validate_product_type.sql @@ -0,0 +1,26 @@ +CREATE OR REPLACE FUNCTION validate_product_type(product_id BIGINT, required_type TEXT) +RETURNS BOOLEAN +LANGUAGE plpgsql +AS $$ +DECLARE + product_type RECORD; +BEGIN + -- Retrieve the product's type + SELECT it.* + INTO product_type + FROM eshop.products i + JOIN eshop.product_types it ON i.product_type = it.id + WHERE i.id = product_id; + + -- Check if the type matches + IF product_type IS NULL THEN + RAISE EXCEPTION 'product with ID % not found or has no type.', product_id; + END IF; + + IF product_type.type = required_type THEN + RETURN TRUE; + ELSE + RAISE EXCEPTION 'product with ID % is not of type %. Found type: %', product_id, required_type, product_type.type; + END IF; +END; +$$; From 2e31e8a21dd65298af28bc0b4ad28e2944f825cf Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:06:46 +0100 Subject: [PATCH 047/159] currency --- .../database/cron/apply_planned_changes.sql | 12 +++---- .../database/eshop/create_ticket_order.sql | 33 +++++++++++++++---- supabase/functions/send-ticket-order/index.ts | 11 ++++++- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/scripts/database/cron/apply_planned_changes.sql b/scripts/database/cron/apply_planned_changes.sql index c43e3e05..3a9d6ae0 100644 --- a/scripts/database/cron/apply_planned_changes.sql +++ b/scripts/database/cron/apply_planned_changes.sql @@ -9,15 +9,15 @@ BEGIN WHERE change_time <= NOW() AND applied = FALSE ORDER BY change_time ASC LOOP - IF change.change_type = 'items.price' THEN - -- Update item price - UPDATE eshop.items + IF change.change_type = 'products.price' THEN + -- Update product price + UPDATE eshop.products SET price = change.new_value::NUMERIC WHERE id = change.subject_id; - ELSIF change.change_type = 'items.is_hidden' THEN - -- Update item visibility - UPDATE eshop.items + ELSIF change.change_type = 'products.is_hidden' THEN + -- Update product visibility + UPDATE eshop.products SET is_hidden = change.new_value::BOOLEAN WHERE id = change.subject_id; diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index ce912251..79a6f116 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -9,7 +9,7 @@ DECLARE spot_data RECORD; spot_id BIGINT; now TIMESTAMP WITH TIME ZONE := NOW(); - calculated_price DOUBLE PRECISION := 0; + calculated_price NUMERIC(10,2) := 0; spot_secret UUID; product_id BIGINT; used_spots JSONB := '[]'::JSONB; @@ -30,6 +30,8 @@ DECLARE form_key UUID; deadline TIMESTAMPTZ; form_deadline_duration BIGINT; + currency_code TEXT; -- Variable for currency_code + first_currency_code TEXT := NULL; -- Variable to store the first product's currency BEGIN -- Validate input data and extract form key IF input_data IS NULL OR input_data->'form' IS NULL THEN @@ -132,7 +134,7 @@ BEGIN spot_data.product ] LOOP IF product_id IS NOT NULL THEN - SELECT i.*, it.type, i.price, i.is_hidden + SELECT i.*, it.type INTO product_data FROM eshop.products i LEFT JOIN eshop.product_types it ON i.product_type = it.id @@ -141,7 +143,7 @@ BEGIN IF product_data IS NULL THEN RETURN JSONB_BUILD_OBJECT( 'code', 1011, - 'message', 'product not found or not part of occasion', + 'message', 'Product not found or not part of occasion', 'details', product_id ); END IF; @@ -155,18 +157,34 @@ BEGIN ); END IF; + -- Check if product currency matches + IF first_currency_code IS NULL THEN + first_currency_code := product_data.currency_code; -- Set the currency for the first product + ELSE + IF product_data.currency_code IS DISTINCT FROM first_currency_code THEN + RETURN JSONB_BUILD_OBJECT( + 'code', 1014, + 'message', 'Products in the order must have the same currency', + 'product_id', product_id, + 'expected_currency', first_currency_code, + 'actual_currency', product_data.currency_code + ); + END IF; + END IF; + -- Add product details to ticket products ticket_products := ticket_products || JSONB_BUILD_OBJECT( 'product_id', product_id, 'title', product_data.title, 'type', product_data.type, 'price', product_data.price, + 'currency_code', product_data.currency_code, 'spot_title', CASE WHEN product_id = spot_data.product THEN spot_data.title ELSE NULL END, 'description', CASE WHEN product_id = spot_data.product THEN product_data.description ELSE NULL END ); -- Add product price to calculated total - calculated_price := calculated_price + COALESCE(product_data.price, 0); + calculated_price := calculated_price + COALESCE(product_data.price, 0)::NUMERIC(10,2); -- Link ticket and product to the order INSERT INTO eshop.order_product_ticket ("order", product, ticket) @@ -191,7 +209,7 @@ BEGIN END LOOP; -- Generate variable symbol for payment - generated_variable_symbol := generate_ticket_symbol(bank_account_id); + generated_variable_symbol := generate_variable_symbol(bank_account_id); -- Insert payment info after calculating price INSERT INTO eshop.payment_info (bank_account, variable_symbol, amount, created_at, deadline) @@ -207,7 +225,7 @@ BEGIN INSERT INTO eshop.orders_history (created_at, data, "order", state, price) VALUES ( now, - JSONB_BUILD_OBJECT('input_data', input_data, 'tickets', ticket_details, 'price', calculated_price), + JSONB_BUILD_OBJECT('input_data', input_data, 'tickets', ticket_details, 'price', calculated_price, 'currency_code', first_currency_code), order_id, 'ordered', calculated_price @@ -224,7 +242,8 @@ BEGIN 'amount', calculated_price, 'deadline', deadline, 'account_number', account_number, - 'account_number_human_readable', account_number_human_readable + 'account_number_human_readable', account_number_human_readable, + 'currency_code', first_currency_code ), 'occasion', JSONB_BUILD_OBJECT( 'id', occasion_id, diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index 1acc2750..94c39d30 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -18,7 +18,7 @@ const supabaseAdmin = createClient( async function generateQrCode(paymentInfo: any, orderDetails: any): Promise { const qrData = `SPD*1.0*ACC:${paymentInfo.account_number}*AM:${paymentInfo.amount.toFixed( 2 - )}*CC:CZK*MSG:${orderDetails.name} ${orderDetails.surname}*X-VS:${paymentInfo.variable_symbol}`; + )}*CC:${paymentInfo.currency_code}*MSG:${orderDetails.name} ${orderDetails.surname}*X-VS:${paymentInfo.variable_symbol}`; const base64QrCode = await qrcode(qrData, { size: 500 }); console.log("QR Code Output:", base64QrCode); @@ -41,6 +41,13 @@ function formatDatetime(datetime: string): string { }).format(date); } +function formatCurrency(amount, currencyCode) { + return new Intl.NumberFormat("cs-CZ", { + style: "currency", + currency: currencyCode, + }).format(amount); +} + function generateFullOrder(orderDetails: any, tickets: any[]): string { const { name, surname, email, note } = orderDetails; @@ -133,6 +140,8 @@ Deno.serve(async (req) => { const subs = { occasionTitle: occasion.occasion_title, price: paymentInfo.amount, + currencyCode: paymentInfo.currency_code, + amount: formatCurrency(paymentInfo.amount, paymentInfo.currency_code), accountNumber: paymentInfo.account_number_human_readable, variableSymbol: paymentInfo.variable_symbol, deadline: formattedDeadline, From cd10293feb7f18bdef217feb4b286e2dfa4e79e0 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 2 Dec 2024 22:26:09 +0100 Subject: [PATCH 048/159] refactor --- .../seatReservation/model/BoxGroupModel.dart | 67 ------ .../seatReservation/model/BoxModel.dart | 50 ----- .../model/SeatLayoutStateModel.dart | 6 +- .../seatReservation/model/SeatModel.dart | 7 +- .../widgets/SeatLayoutWidget.dart | 8 +- lib/dataModelsEshop/BlueprintGroup.dart | 47 +++++ lib/dataModelsEshop/BlueprintModel.dart | 60 +++--- lib/dataModelsEshop/BlueprintObject.dart | 50 ----- lib/dataModelsEshop/BlueprintObjectModel.dart | 80 +++++++ lib/dataServices/RightsService.dart | 1 + lib/widgets/SeatReservationWidget.dart | 197 +++++------------- 11 files changed, 220 insertions(+), 353 deletions(-) delete mode 100644 lib/components/seatReservation/model/BoxGroupModel.dart delete mode 100644 lib/components/seatReservation/model/BoxModel.dart create mode 100644 lib/dataModelsEshop/BlueprintGroup.dart delete mode 100644 lib/dataModelsEshop/BlueprintObject.dart create mode 100644 lib/dataModelsEshop/BlueprintObjectModel.dart diff --git a/lib/components/seatReservation/model/BoxGroupModel.dart b/lib/components/seatReservation/model/BoxGroupModel.dart deleted file mode 100644 index 701935a5..00000000 --- a/lib/components/seatReservation/model/BoxGroupModel.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:fstapp/components/seatReservation/model/BoxModel.dart'; - -class BoxGroupModel{ - static const String boxGroupsTable = "box_groups"; - static const String idColumn = "id"; - static const String occasionColumn = "occasion"; - static const String nameColumn = "name"; - - int? id; - int? blueprintId; - String? name; - List? boxes = []; - - BoxGroupModel({ - this.id, - this.blueprintId, - this.name, - }); - - static BoxGroupModel fromJson(Map json) { - return BoxGroupModel( - id: json[idColumn], - blueprintId: json[occasionColumn], - name: json[nameColumn], - ); - } - - Map toJson() { - var map = { - occasionColumn: blueprintId, - nameColumn: name, - }; - if(id != null) - { - map[idColumn] = id; - } - return map; - } - - @override - toString() - { - return "${name}"; - } - List alphabet = [ - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' - ]; - - String getNextBoxName() - { - return alphabet[boxes!.length]; - } - - @override - int get hashCode { - return id.hashCode; - } - - @override bool operator ==(Object other) { - if(other is BoxGroupModel) - { - return id == other.id; - } - return false; - } -} \ No newline at end of file diff --git a/lib/components/seatReservation/model/BoxModel.dart b/lib/components/seatReservation/model/BoxModel.dart deleted file mode 100644 index e2588395..00000000 --- a/lib/components/seatReservation/model/BoxModel.dart +++ /dev/null @@ -1,50 +0,0 @@ - -import '../utils/SeatState.dart'; -import 'BoxGroupModel.dart'; - -class BoxModel{ - static const String soldType = "sold"; - static const String selectedType = "selected"; - static const String blackType = "black"; - static const String availableType = "available"; - - static const String boxTable = "boxes"; - - static const SeatState stateColumn = SeatState.empty; - - int? id; - String? name; - SeatState? type; - int? boxGroupId; - BoxGroupModel? boxGroup; - int? x; - int? y; - - BoxModel({ - this.id, - this.name, - this.type, - this.boxGroupId, - this.boxGroup, - this.x, - this.y - }); - - static Map States = { - SeatState.black: blackType, - SeatState.available: availableType, - SeatState.selected: selectedType, - SeatState.ordered: soldType, - }; - - @override - toString() - { - return "stůl ${boxGroup}, sedadlo ${name}"; - } - - toShortString() - { - return "${boxGroup??""}${name??""}"; - } -} \ No newline at end of file diff --git a/lib/components/seatReservation/model/SeatLayoutStateModel.dart b/lib/components/seatReservation/model/SeatLayoutStateModel.dart index 28c98e3d..f72e9eb7 100644 --- a/lib/components/seatReservation/model/SeatLayoutStateModel.dart +++ b/lib/components/seatReservation/model/SeatLayoutStateModel.dart @@ -1,18 +1,18 @@ -import 'BoxModel.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'SeatModel.dart'; class SeatLayoutStateModel { final int rows; final int cols; - final List currentBoxes; + final List currentObjects; final List allBoxes; final int seatSize; const SeatLayoutStateModel({ required this.rows, required this.cols, - required this.currentBoxes, + required this.currentObjects, required this.allBoxes, this.seatSize = 50, }); diff --git a/lib/components/seatReservation/model/SeatModel.dart b/lib/components/seatReservation/model/SeatModel.dart index 9081fcde..ce8327de 100644 --- a/lib/components/seatReservation/model/SeatModel.dart +++ b/lib/components/seatReservation/model/SeatModel.dart @@ -1,5 +1,6 @@ +import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; + import '../utils/SeatState.dart'; -import 'BoxModel.dart'; class SeatModel { @@ -7,10 +8,10 @@ class SeatModel { final int rowI; final int colI; final int seatSize; - BoxModel? boxModel; + BlueprintObjectModel? objectModel; SeatModel({ - required this.boxModel, + required this.objectModel, required this.seatState, required this.rowI, required this.colI, diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index e713a997..555365dd 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -73,7 +73,7 @@ class _SeatLayoutWidgetState extends State { final seatModel = _createSeat(colI, rowI); return Tooltip( showDuration: const Duration(seconds: 0), - message: seatModel.boxModel?.toShortString() ?? "", + message: seatModel.objectModel?.title ?? "", child: SeatWidget( model: seatModel, onSeatTap: widget.onSeatTap, @@ -87,11 +87,11 @@ class _SeatLayoutWidgetState extends State { } SeatModel _createSeat(int colI, int rowI) { - var boxModel = widget.stateModel.currentBoxes + var boxModel = widget.stateModel.currentObjects .firstWhereOrNull((b) => b.x == colI && b.y == rowI); return SeatModel( - boxModel: boxModel, - seatState: boxModel?.type ?? SeatState.empty, + objectModel: boxModel, + seatState: boxModel?.stateEnum ?? SeatState.empty, rowI: rowI, colI: colI, seatSize: widget.stateModel.seatSize, diff --git a/lib/dataModelsEshop/BlueprintGroup.dart b/lib/dataModelsEshop/BlueprintGroup.dart new file mode 100644 index 00000000..d0bc5845 --- /dev/null +++ b/lib/dataModelsEshop/BlueprintGroup.dart @@ -0,0 +1,47 @@ +import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; + +class BlueprintGroupModel { + static const String metaTitle = "title"; + static const String metaItems = "items"; + + String? title; + List? objectIds; + List objects = []; + + BlueprintGroupModel({ + this.title, + this.objectIds, + }); + + factory BlueprintGroupModel.fromJson(Map json) { + return BlueprintGroupModel( + title: json[metaTitle], + objectIds: json[metaItems] != null + ? List.from(json[metaItems]) + : null, + ); + } + + Map toJson() => { + metaTitle: title, + metaItems: objectIds, + }; + + List alphabet = [ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' + ]; + + String getNextBoxName() { + String getAlphabetName(int index) { + String name = ""; + while (index >= 0) { + name = alphabet[index % 26] + name; + index = (index ~/ 26) - 1; + } + return name; + } + + return "$title${getAlphabetName(objects.length)}"; + } +} \ No newline at end of file diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index 3bd0cf13..1c390acb 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -1,11 +1,13 @@ -import 'package:fstapp/components/seatReservation/model/BoxModel.dart'; import 'package:fstapp/components/seatReservation/utils/SeatState.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; import 'BlueprintConfiguration.dart'; -import 'BlueprintObject.dart'; +import 'BlueprintObjectModel.dart'; class BlueprintModel { static const String metaSpots = "spots"; + static const String metaTableAreaType = "table"; + static const String metaSpotType = "spot"; int? id; DateTime? createdAt; @@ -13,8 +15,9 @@ class BlueprintModel { String? title; int? organization; BlueprintConfiguration? configuration; - List? objects; - List? spots; + List? objects; + List? spots; + List? groups; factory BlueprintModel.fromJson(Map json) { return BlueprintModel( @@ -29,12 +32,16 @@ class BlueprintModel { ? BlueprintConfiguration.fromJson(json[TbEshop.blueprints.configuration]) : null, objects: json[TbEshop.blueprints.objects] != null - ? List.from( - json[TbEshop.blueprints.objects].map((obj) => BlueprintObject.fromJson(obj))) + ? List.from( + json[TbEshop.blueprints.objects].map((obj) => BlueprintObjectModel.fromJson(obj))) : null, - spots: json[metaSpots] != null - ? List.from( - json[metaSpots].map((spot) => BlueprintObject.fromJson(spot))) + spots: json[metaSpotType] != null + ? List.from( + json[metaSpotType].map((spot) => BlueprintObjectModel.fromJson(spot))) + : null, + groups: json[TbEshop.blueprints.groups] != null + ? List.from( + json[TbEshop.blueprints.groups].map((g) => BlueprintGroupModel.fromJson(g))) : null, ); } @@ -46,46 +53,42 @@ class BlueprintModel { TbEshop.blueprints.title: title, TbEshop.blueprints.organization: organization, TbEshop.blueprints.configuration: configuration?.toJson(), - TbEshop.blueprints.objects: objects?.map((obj) => obj.toJson()).toList(), - metaSpots: spots?.map((spot) => spot.toJson()).toList(), + TbEshop.blueprints.objects: objects?.map((obj) => obj.toJson()), + TbEshop.blueprints.groups: groups?.map((g) => g.toJson()), }; String toBasicString() => title ?? id.toString(); - List toBoxModels() { + List toBlueprintObjects() { if (objects == null) return []; return objects!.map((object) { - if (object.type == "spot") { - // Find the matching spot in the spots list - final matchingSpot = spots?.firstWhere( - (spot) => spot.id == object.spot, - ); - return BoxModel( - id: object.spot, // Use object spot ID - name: matchingSpot?.title ?? object.title, // Spot title or object title - type: BoxModel.States.entries + if (object.type == metaSpotType) { + final matchingSpot = spots?.firstWhere((spot) => spot.id == object.spot); + return BlueprintObjectModel( + id: object.spot, + title: matchingSpot?.title ?? object.title, + stateEnum: BlueprintObjectModel.States.entries .firstWhere( (entry) => entry.value == matchingSpot?.state, - orElse: () => MapEntry(SeatState.available, BoxModel.availableType)).key, // Map state from spot + orElse: () => MapEntry(SeatState.available, BlueprintObjectModel.availableType)).key, x: object.x, y: object.y, ); - } else if (object.type == "table") { - return BoxModel( + } else if (object.type == metaTableAreaType) { + return BlueprintObjectModel( id: object.spot, // Use object spot ID or null - name: object.title, // Use object title - type: SeatState.black, // "table" type always results in black state + title: object.title, // Use object title + stateEnum: SeatState.black, // "table" type always results in black state x: object.x, y: object.y, ); } // If the object type is neither "spot" nor "table", skip it return null; - }).whereType().toList(); // Filter out null values + }).whereType().toList(); // Filter out null values } - BlueprintModel({ this.id, this.createdAt, @@ -95,5 +98,6 @@ class BlueprintModel { this.configuration, this.objects, this.spots, + this.groups, }); } diff --git a/lib/dataModelsEshop/BlueprintObject.dart b/lib/dataModelsEshop/BlueprintObject.dart deleted file mode 100644 index f47acb13..00000000 --- a/lib/dataModelsEshop/BlueprintObject.dart +++ /dev/null @@ -1,50 +0,0 @@ -class BlueprintObject { - static const String metaX = "x"; - static const String metaY = "y"; - static const String metaSpot = "spot"; - static const String metaType = "type"; - static const String metaState = "state"; - static const String metaTitle = "title"; - static const String metaId = "id"; - - - int? x; - int? y; - int? id; - int? spot; - String? type; - String? state; - String? title; - - factory BlueprintObject.fromJson(Map json) { - return BlueprintObject( - x: json[metaX], - y: json[metaY], - id: json[metaId], - spot: json[metaSpot], - type: json[metaType], - state: json[metaState], - title: json[metaTitle], - ); - } - - Map toJson() => { - metaX: x, - metaY: y, - metaId: id, - metaSpot: spot, - metaType: type, - metaState: state, - metaTitle: title, - }; - - BlueprintObject({ - this.x, - this.y, - this.id, - this.spot, - this.type, - this.state, - this.title, - }); -} diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart new file mode 100644 index 00000000..ecfdff0c --- /dev/null +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -0,0 +1,80 @@ +import 'package:fstapp/components/seatReservation/utils/SeatState.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; + +class BlueprintObjectModel { + static const String metaX = "x"; + static const String metaY = "y"; + static const String metaSpot = "spot"; + static const String metaType = "type"; + static const String metaState = "state"; + static const String metaTitle = "title"; + static const String metaId = "id"; + + static const String soldType = "sold"; + static const String selectedType = "selected"; + static const String selectedByMeType = "selected_by_me"; + static const String blackType = "black"; + static const String availableType = "available"; + + static Map States = { + SeatState.black: blackType, + SeatState.available: availableType, + SeatState.selected: selectedType, + SeatState.selected_by_me: selectedByMeType, + SeatState.ordered: soldType, + }; + + @override + toString() + { + return "stůl $group, sedadlo $title"; + } + + toShortString() + { + return "${group??""}${title??""}"; + } + + int? x; + int? y; + int? id; + int? spot; + String? type; + String? state; + String? title; + BlueprintGroupModel? group; + SeatState? stateEnum; + + factory BlueprintObjectModel.fromJson(Map json) { + return BlueprintObjectModel( + x: json[metaX], + y: json[metaY], + id: json[metaId], + spot: json[metaSpot], + type: json[metaType], + state: json[metaState], + title: json[metaTitle], + ); + } + + Map toJson() => { + metaX: x, + metaY: y, + metaId: id, + metaSpot: spot, + metaType: type, + metaState: state, + metaTitle: title, + }; + + BlueprintObjectModel({ + this.x, + this.y, + this.id, + this.spot, + this.type, + this.state, + this.stateEnum, + this.title, + }); +} diff --git a/lib/dataServices/RightsService.dart b/lib/dataServices/RightsService.dart index 5e0cba9a..1f5f6b1d 100644 --- a/lib/dataServices/RightsService.dart +++ b/lib/dataServices/RightsService.dart @@ -73,6 +73,7 @@ class RightsService{ } static bool isEditor() { + return true; return currentUserOccasion?.isEditor??false; } diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index 16482f8a..058c1bbb 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -1,14 +1,12 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fstapp/components/seatReservation/widgets/SeatWidget.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; -import 'package:fstapp/dataServices/RightsService.dart'; -import 'package:fstapp/services/DialogHelper.dart'; -import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/styles/StylesConfig.dart'; -import '../components/seatReservation/model/BoxGroupModel.dart'; -import '../components/seatReservation/model/BoxModel.dart'; import '../components/seatReservation/model/SeatLayoutStateModel.dart'; import '../components/seatReservation/model/SeatModel.dart'; import '../components/seatReservation/utils/SeatState.dart'; @@ -26,7 +24,11 @@ class SeatReservationWidget extends StatefulWidget { } class _SeatReservationWidgetState extends State { - Set selectedSeats = {}; + SeatModel? selectedSeat; + List allObjects = []; + + static int boxSize = 15; + BlueprintModel? blueprint; _SeatReservationWidgetState(); @@ -36,18 +38,12 @@ class _SeatReservationWidgetState extends State { loadData(); } - List? currentBoxes; - BoxGroupModel? currentBoxGroup; + List? currentObjects; + BlueprintGroupModel? currentBoxGroup; int currentWidth = 20; int currentHeight = 20; - List changedBoxes = []; - List allBoxes = []; - - static int boxSize = 15; - selectionMode currentSelectionMode = selectionMode.normal; - @override Widget build(BuildContext context) { return Scaffold( @@ -58,13 +54,9 @@ class _SeatReservationWidgetState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - const SizedBox( - height: 16, - ), - Text(blueprint?.title ?? "", style: const TextStyle(fontWeight: FontWeight.bold)), - const SizedBox( - height: 16, - ), + const SizedBox(height: 16), + Text(blueprint?.title ?? "", style: StylesConfig.textStyleBig), + const SizedBox(height: 16), Padding( padding: const EdgeInsets.all(16.0), child: Row( @@ -84,16 +76,9 @@ class _SeatReservationWidgetState extends State { Row( mainAxisSize: MainAxisSize.min, children: [ - GestureDetector( - onTap: () { - if (currentBoxGroup != null) { - currentSelectionMode = selectionMode.addAvailable; - } - }, - child: SeatWidget.buildSeat( - state: SeatState.available, - size: 15.0, - ), + SeatWidget.buildSeat( + state: SeatState.available, + size: boxSize.toDouble(), ), const SizedBox(width: 8), const Text("dostupné"), @@ -104,7 +89,7 @@ class _SeatReservationWidgetState extends State { children: [ SeatWidget.buildSeat( state: SeatState.selected, - size: 15.0, + size: boxSize.toDouble(), ), const SizedBox(width: 8), const Text("vybrané"), @@ -113,16 +98,9 @@ class _SeatReservationWidgetState extends State { Row( mainAxisSize: MainAxisSize.min, children: [ - GestureDetector( - onTap: () { - if (currentBoxGroup != null) { - currentSelectionMode = selectionMode.addBlack; - } - }, - child: SeatWidget.buildSeat( - state: SeatState.black, - size: 15.0, - ), + SeatWidget.buildSeat( + state: SeatState.black, + size: 15.0, ), const SizedBox(width: 8), const Text("stůl"), @@ -131,127 +109,50 @@ class _SeatReservationWidgetState extends State { ], ), ), - const SizedBox( - height: 12, - ), - Visibility( - visible: currentBoxes != null, - child: Flexible( - child: SeatLayoutWidget( - onSeatTap: (model) { - if (currentSelectionMode == selectionMode.addBlack) { - model.seatState = SeatState.black; - changedBoxes.add(model); - } else if (currentSelectionMode == selectionMode.addAvailable) { - model.seatState = SeatState.available; - model.boxModel = model.boxModel ?? BoxModel(x: model.colI, y: model.rowI); - model.boxModel!.boxGroupId = currentBoxGroup!.id; - model.boxModel!.name = currentBoxGroup!.getNextBoxName(); - currentBoxGroup!.boxes!.add(model.boxModel!); - - changedBoxes.add(model); - ToastHelper.Show(context, "Přidáno sedadlo ${model.boxModel!.name}."); - } else if (currentSelectionMode == selectionMode.normal) { - if (model.seatState == SeatState.selected) { - model.seatState = SeatState.available; - changedBoxes.remove(model); - return; - } else if (model.seatState != SeatState.available) { - return; - } - - // available - var alreadySelected = allBoxes.where((b) => b.seatState == SeatState.selected); - if (alreadySelected.isNotEmpty) { - ToastHelper.Show(context, "Je možné vybrat pouze jedno místo!"); - return; - } - model.seatState = SeatState.selected; - changedBoxes.add(model); - } - }, - stateModel: SeatLayoutStateModel( - rows: currentHeight, - cols: currentWidth, - seatSize: boxSize, - currentBoxes: currentBoxes ?? [], - allBoxes: allBoxes, - ), + const SizedBox(height: 12), + Flexible( + child: + blueprint == null + ? const Center(child: CircularProgressIndicator()) : + SeatLayoutWidget( + onSeatTap: (model) { + if (model.seatState == SeatState.selected) { + model.seatState = SeatState.available; + selectedSeat = null; + return; + } else if (model.seatState != SeatState.available) { + return; + } + + model.seatState = SeatState.selected; + selectedSeat = model; + }, + stateModel: SeatLayoutStateModel( + rows: currentHeight, + cols: currentWidth, + seatSize: boxSize, + currentObjects: currentObjects ?? [], + allBoxes: allObjects, ), ), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Visibility( - visible: RightsService.isEditor(), - child: ElevatedButton( - onPressed: () async { - Set tables = {}; - tables.addAll(currentBoxes!.where((element) => element.boxGroup != null).map((e) => e.boxGroup!)); - var newTableName = ((tables.length + 1)).toString(); - var result = await DialogHelper.showConfirmationDialogAsync(context, "Přidat stůl", "Chcete přidat stůl ${newTableName}?"); - if (!result) { - return; - } - var newBox = BoxGroupModel(name: newTableName, blueprintId: blueprint?.id); - // currentBoxGroup = await DataService.updateBoxGroup(newBox); - ToastHelper.Show(context, "Vytvořen stůl ${newTableName}."); - }, - child: const Text("přidat stůl"), - ), - ), - const SizedBox(width: 12), - Visibility( - visible: RightsService.isEditor(), - child: ElevatedButton( - onPressed: () async { - Set tables = {}; - tables.addAll(currentBoxes!.where((element) => element.boxGroup != null).map((e) => e.boxGroup!)); - // var selectedTable = await DialogHelper.chooseBoxGroup(context, tables.toList()); - var selectedTable = null; - - if (selectedTable == null) { - return; - } - var result = await DialogHelper.showConfirmationDialogAsync(context, "Přidání ke stolu", "Chcete přidat židle ke stolu ${selectedTable.name!}?"); - if (!result) { - return; - } - currentBoxGroup = selectedTable; - ToastHelper.Show(context, "Nyní přidáváte ke stolu ${selectedTable.name}."); - }, - child: const Text("přidat ke stolu"), - ), - ), - const SizedBox(width: 12), ElevatedButton( onPressed: () { Navigator.pop(context); }, - child: const Text("zpět"), + child: const Text("Storno").tr(), ), const SizedBox(width: 12), ElevatedButton( onPressed: () { - var changed = changedBoxes.map((e) { - var newBoxModel = e.boxModel ?? BoxModel(x: e.colI, y: e.rowI); - newBoxModel.type = e.seatState; - return newBoxModel; - }).toList(); - - // only non-selected - selectedSeats.clear(); - selectedSeats.addAll(changed.where((element) => element.type == BoxModel.selectedType)); - changed = changed.where((element) => element.type != BoxModel.selectedType).toList(); - - // DataService.updateBoxes(changed); + // Save changes logic Navigator.pop(context); }, - child: const Text("uložit"), + child: const Text("Save").tr(), ), ], ), @@ -271,7 +172,7 @@ class _SeatReservationWidgetState extends State { } setState(() { - currentBoxes = blueprint!.toBoxModels(); + currentObjects = blueprint!.toBlueprintObjects(); currentHeight = blueprint!.configuration!.height!; currentWidth = blueprint!.configuration!.width!; }); From f743ce6d26f59f845529754885b4b5887615513a Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 2 Dec 2024 23:48:49 +0100 Subject: [PATCH 049/159] fix var first = value.first.key; --- lib/dataModels/OccasionUserModel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dataModels/OccasionUserModel.dart b/lib/dataModels/OccasionUserModel.dart index a56488a8..b0dbd9e5 100644 --- a/lib/dataModels/OccasionUserModel.dart +++ b/lib/dataModels/OccasionUserModel.dart @@ -119,7 +119,7 @@ class OccasionUserModel extends IPlutoRowModel { if(value.isEmpty){ return emptyResult; } - var first = value.first.formKey; + var first = value.first.key; cell = PlutoCell(value: first); return { serviceType : cell }; } From b962520ed6dee7abcd40b5a94ba875d01fc4b0b6 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 00:30:43 +0100 Subject: [PATCH 050/159] blueprint editor --- lib/AppRouter.dart | 4 + lib/AppRouter.gr.dart | 494 ++++++++++++++----------- lib/dataModelsEshop/TbEshop.dart | 16 + lib/dataServices/DbEshop.dart | 14 + lib/dataServices/RightsService.dart | 2 - lib/pages/BlueprintEditorPage.dart | 271 ++++++++++++++ lib/services/DialogHelper.dart | 34 ++ lib/widgets/SeatReservationWidget.dart | 9 +- 8 files changed, 618 insertions(+), 226 deletions(-) create mode 100644 lib/pages/BlueprintEditorPage.dart diff --git a/lib/AppRouter.dart b/lib/AppRouter.dart index 387b0137..2f1f73e5 100644 --- a/lib/AppRouter.dart +++ b/lib/AppRouter.dart @@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/pages/AdminDashboardPage.dart'; +import 'package:fstapp/pages/BlueprintEditorPage.dart'; import 'package:fstapp/pages/CheckPage.dart'; import 'package:fstapp/pages/EventEditPage.dart'; import 'package:fstapp/pages/EventPage.dart'; @@ -56,6 +57,9 @@ class AppRouter extends RootStackRouter { AutoRoute(page: EventEditRoute.page, path: "/:{$LINK}/${EventEditPage.ROUTE}", children: [ AutoRoute(path: ':id', page: EventEditRoute.page,), ]), + AutoRoute(page: BlueprintEditorRoute.page, path: "/:{$LINK}/${BlueprintEditorPage.ROUTE}", children: [ + AutoRoute(path: ':id', page: BlueprintEditorRoute.page,), + ]), AutoRoute(page: OccasionHomeRoute.page, path: "/:{$LINK}", children: [ AutoRoute(page: UserRoute.page, path: "${UserPage.ROUTE}"), AutoRoute(page: ScheduleNavigationRoute.page, path: EventPage.ROUTE, children: [ diff --git a/lib/AppRouter.gr.dart b/lib/AppRouter.gr.dart index 4344ca8f..0be52e5d 100644 --- a/lib/AppRouter.gr.dart +++ b/lib/AppRouter.gr.dart @@ -8,41 +8,42 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i26; -import 'package:flutter/foundation.dart' as _i28; -import 'package:flutter/material.dart' as _i27; -import 'package:fstapp/dataModels/PlaceModel.dart' as _i29; +import 'package:auto_route/auto_route.dart' as _i27; +import 'package:flutter/foundation.dart' as _i29; +import 'package:flutter/material.dart' as _i28; +import 'package:fstapp/dataModels/PlaceModel.dart' as _i30; import 'package:fstapp/pages/AdminDashboardPage.dart' deferred as _i1; import 'package:fstapp/pages/AdministrationOccasion/AdminPage.dart' deferred as _i2; -import 'package:fstapp/pages/CheckPage.dart' deferred as _i3; -import 'package:fstapp/pages/EventEditPage.dart' deferred as _i4; -import 'package:fstapp/pages/EventPage.dart' deferred as _i5; -import 'package:fstapp/pages/ForgotPasswordPage.dart' deferred as _i6; -import 'package:fstapp/pages/FormPage.dart' deferred as _i7; -import 'package:fstapp/pages/GamePage.dart' deferred as _i8; -import 'package:fstapp/pages/HtmlEditorPage.dart' deferred as _i9; -import 'package:fstapp/pages/InfoPage.dart' deferred as _i10; -import 'package:fstapp/pages/InstallPage.dart' deferred as _i11; -import 'package:fstapp/pages/LoginPage.dart' deferred as _i12; -import 'package:fstapp/pages/MapPage.dart' deferred as _i13; -import 'package:fstapp/pages/MySchedulePage.dart' deferred as _i14; -import 'package:fstapp/pages/NewsFormPage.dart' deferred as _i15; -import 'package:fstapp/pages/NewsPage.dart' deferred as _i16; -import 'package:fstapp/pages/OccasionHomePage.dart' deferred as _i17; -import 'package:fstapp/pages/ScheduleNavigationScreen.dart' deferred as _i19; -import 'package:fstapp/pages/SchedulePage.dart' deferred as _i20; -import 'package:fstapp/pages/SettingsPage.dart' deferred as _i21; -import 'package:fstapp/pages/SignupPage.dart' deferred as _i22; -import 'package:fstapp/pages/SignupPasswordPage.dart' deferred as _i18; -import 'package:fstapp/pages/SongPage.dart' deferred as _i23; -import 'package:fstapp/pages/TimetablePage.dart' deferred as _i24; -import 'package:fstapp/pages/UserPage.dart' deferred as _i25; +import 'package:fstapp/pages/BlueprintEditorPage.dart' deferred as _i3; +import 'package:fstapp/pages/CheckPage.dart' deferred as _i4; +import 'package:fstapp/pages/EventEditPage.dart' deferred as _i5; +import 'package:fstapp/pages/EventPage.dart' deferred as _i6; +import 'package:fstapp/pages/ForgotPasswordPage.dart' deferred as _i7; +import 'package:fstapp/pages/FormPage.dart' deferred as _i8; +import 'package:fstapp/pages/GamePage.dart' deferred as _i9; +import 'package:fstapp/pages/HtmlEditorPage.dart' deferred as _i10; +import 'package:fstapp/pages/InfoPage.dart' deferred as _i11; +import 'package:fstapp/pages/InstallPage.dart' deferred as _i12; +import 'package:fstapp/pages/LoginPage.dart' deferred as _i13; +import 'package:fstapp/pages/MapPage.dart' deferred as _i14; +import 'package:fstapp/pages/MySchedulePage.dart' deferred as _i15; +import 'package:fstapp/pages/NewsFormPage.dart' deferred as _i16; +import 'package:fstapp/pages/NewsPage.dart' deferred as _i17; +import 'package:fstapp/pages/OccasionHomePage.dart' deferred as _i18; +import 'package:fstapp/pages/ScheduleNavigationScreen.dart' deferred as _i20; +import 'package:fstapp/pages/SchedulePage.dart' deferred as _i21; +import 'package:fstapp/pages/SettingsPage.dart' deferred as _i22; +import 'package:fstapp/pages/SignupPage.dart' deferred as _i23; +import 'package:fstapp/pages/SignupPasswordPage.dart' deferred as _i19; +import 'package:fstapp/pages/SongPage.dart' deferred as _i24; +import 'package:fstapp/pages/TimetablePage.dart' deferred as _i25; +import 'package:fstapp/pages/UserPage.dart' deferred as _i26; /// generated route for /// [_i1.AdminDashboardPage] -class AdminDashboardRoute extends _i26.PageRouteInfo { - const AdminDashboardRoute({List<_i26.PageRouteInfo>? children}) +class AdminDashboardRoute extends _i27.PageRouteInfo { + const AdminDashboardRoute({List<_i27.PageRouteInfo>? children}) : super( AdminDashboardRoute.name, initialChildren: children, @@ -50,10 +51,10 @@ class AdminDashboardRoute extends _i26.PageRouteInfo { static const String name = 'AdminDashboardRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( + return _i27.DeferredWidget( _i1.loadLibrary, () => _i1.AdminDashboardPage(), ); @@ -63,8 +64,8 @@ class AdminDashboardRoute extends _i26.PageRouteInfo { /// generated route for /// [_i2.AdminPage] -class AdminRoute extends _i26.PageRouteInfo { - const AdminRoute({List<_i26.PageRouteInfo>? children}) +class AdminRoute extends _i27.PageRouteInfo { + const AdminRoute({List<_i27.PageRouteInfo>? children}) : super( AdminRoute.name, initialChildren: children, @@ -72,10 +73,10 @@ class AdminRoute extends _i26.PageRouteInfo { static const String name = 'AdminRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( + return _i27.DeferredWidget( _i2.loadLibrary, () => _i2.AdminPage(), ); @@ -84,12 +85,65 @@ class AdminRoute extends _i26.PageRouteInfo { } /// generated route for -/// [_i3.CheckPage] -class CheckRoute extends _i26.PageRouteInfo { +/// [_i3.BlueprintEditorPage] +class BlueprintEditorRoute + extends _i27.PageRouteInfo { + BlueprintEditorRoute({ + _i28.Key? key, + int? id, + List<_i27.PageRouteInfo>? children, + }) : super( + BlueprintEditorRoute.name, + args: BlueprintEditorRouteArgs( + key: key, + id: id, + ), + rawPathParams: {'id': id}, + initialChildren: children, + ); + + static const String name = 'BlueprintEditorRoute'; + + static _i27.PageInfo page = _i27.PageInfo( + name, + builder: (data) { + final pathParams = data.inheritedPathParams; + final args = data.argsAs( + orElse: () => BlueprintEditorRouteArgs(id: pathParams.optInt('id'))); + return _i27.DeferredWidget( + _i3.loadLibrary, + () => _i3.BlueprintEditorPage( + key: args.key, + id: args.id, + ), + ); + }, + ); +} + +class BlueprintEditorRouteArgs { + const BlueprintEditorRouteArgs({ + this.key, + this.id, + }); + + final _i28.Key? key; + + final int? id; + + @override + String toString() { + return 'BlueprintEditorRouteArgs{key: $key, id: $id}'; + } +} + +/// generated route for +/// [_i4.CheckPage] +class CheckRoute extends _i27.PageRouteInfo { CheckRoute({ required int id, - _i27.Key? key, - List<_i26.PageRouteInfo>? children, + _i28.Key? key, + List<_i27.PageRouteInfo>? children, }) : super( CheckRoute.name, args: CheckRouteArgs( @@ -102,15 +156,15 @@ class CheckRoute extends _i26.PageRouteInfo { static const String name = 'CheckRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => CheckRouteArgs(id: pathParams.getInt('id'))); - return _i26.DeferredWidget( - _i3.loadLibrary, - () => _i3.CheckPage( + return _i27.DeferredWidget( + _i4.loadLibrary, + () => _i4.CheckPage( id: args.id, key: args.key, ), @@ -127,7 +181,7 @@ class CheckRouteArgs { final int id; - final _i27.Key? key; + final _i28.Key? key; @override String toString() { @@ -136,12 +190,12 @@ class CheckRouteArgs { } /// generated route for -/// [_i4.EventEditPage] -class EventEditRoute extends _i26.PageRouteInfo { +/// [_i5.EventEditPage] +class EventEditRoute extends _i27.PageRouteInfo { EventEditRoute({ - _i27.Key? key, + _i28.Key? key, int? id, - List<_i26.PageRouteInfo>? children, + List<_i27.PageRouteInfo>? children, }) : super( EventEditRoute.name, args: EventEditRouteArgs( @@ -154,15 +208,15 @@ class EventEditRoute extends _i26.PageRouteInfo { static const String name = 'EventEditRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => EventEditRouteArgs(id: pathParams.optInt('id'))); - return _i26.DeferredWidget( - _i4.loadLibrary, - () => _i4.EventEditPage( + return _i27.DeferredWidget( + _i5.loadLibrary, + () => _i5.EventEditPage( key: args.key, id: args.id, ), @@ -177,7 +231,7 @@ class EventEditRouteArgs { this.id, }); - final _i27.Key? key; + final _i28.Key? key; final int? id; @@ -188,12 +242,12 @@ class EventEditRouteArgs { } /// generated route for -/// [_i5.EventPage] -class EventRoute extends _i26.PageRouteInfo { +/// [_i6.EventPage] +class EventRoute extends _i27.PageRouteInfo { EventRoute({ int? id, - _i27.Key? key, - List<_i26.PageRouteInfo>? children, + _i28.Key? key, + List<_i27.PageRouteInfo>? children, }) : super( EventRoute.name, args: EventRouteArgs( @@ -206,15 +260,15 @@ class EventRoute extends _i26.PageRouteInfo { static const String name = 'EventRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => EventRouteArgs(id: pathParams.optInt('id'))); - return _i26.DeferredWidget( - _i5.loadLibrary, - () => _i5.EventPage( + return _i27.DeferredWidget( + _i6.loadLibrary, + () => _i6.EventPage( id: args.id, key: args.key, ), @@ -231,7 +285,7 @@ class EventRouteArgs { final int? id; - final _i27.Key? key; + final _i28.Key? key; @override String toString() { @@ -240,9 +294,9 @@ class EventRouteArgs { } /// generated route for -/// [_i6.ForgotPasswordPage] -class ForgotPasswordRoute extends _i26.PageRouteInfo { - const ForgotPasswordRoute({List<_i26.PageRouteInfo>? children}) +/// [_i7.ForgotPasswordPage] +class ForgotPasswordRoute extends _i27.PageRouteInfo { + const ForgotPasswordRoute({List<_i27.PageRouteInfo>? children}) : super( ForgotPasswordRoute.name, initialChildren: children, @@ -250,23 +304,23 @@ class ForgotPasswordRoute extends _i26.PageRouteInfo { static const String name = 'ForgotPasswordRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i6.loadLibrary, - () => _i6.ForgotPasswordPage(), + return _i27.DeferredWidget( + _i7.loadLibrary, + () => _i7.ForgotPasswordPage(), ); }, ); } /// generated route for -/// [_i7.FormPage] -class FormRoute extends _i26.PageRouteInfo { +/// [_i8.FormPage] +class FormRoute extends _i27.PageRouteInfo { FormRoute({ - _i27.Key? key, - List<_i26.PageRouteInfo>? children, + _i28.Key? key, + List<_i27.PageRouteInfo>? children, }) : super( FormRoute.name, args: FormRouteArgs(key: key), @@ -275,14 +329,14 @@ class FormRoute extends _i26.PageRouteInfo { static const String name = 'FormRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const FormRouteArgs()); - return _i26.DeferredWidget( - _i7.loadLibrary, - () => _i7.FormPage(key: args.key), + return _i27.DeferredWidget( + _i8.loadLibrary, + () => _i8.FormPage(key: args.key), ); }, ); @@ -291,7 +345,7 @@ class FormRoute extends _i26.PageRouteInfo { class FormRouteArgs { const FormRouteArgs({this.key}); - final _i27.Key? key; + final _i28.Key? key; @override String toString() { @@ -300,11 +354,11 @@ class FormRouteArgs { } /// generated route for -/// [_i8.GamePage] -class GameRoute extends _i26.PageRouteInfo { +/// [_i9.GamePage] +class GameRoute extends _i27.PageRouteInfo { GameRoute({ - _i27.Key? key, - List<_i26.PageRouteInfo>? children, + _i28.Key? key, + List<_i27.PageRouteInfo>? children, }) : super( GameRoute.name, args: GameRouteArgs(key: key), @@ -313,14 +367,14 @@ class GameRoute extends _i26.PageRouteInfo { static const String name = 'GameRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const GameRouteArgs()); - return _i26.DeferredWidget( - _i8.loadLibrary, - () => _i8.GamePage(key: args.key), + return _i27.DeferredWidget( + _i9.loadLibrary, + () => _i9.GamePage(key: args.key), ); }, ); @@ -329,7 +383,7 @@ class GameRoute extends _i26.PageRouteInfo { class GameRouteArgs { const GameRouteArgs({this.key}); - final _i27.Key? key; + final _i28.Key? key; @override String toString() { @@ -338,12 +392,12 @@ class GameRouteArgs { } /// generated route for -/// [_i9.HtmlEditorPage] -class HtmlEditorRoute extends _i26.PageRouteInfo { +/// [_i10.HtmlEditorPage] +class HtmlEditorRoute extends _i27.PageRouteInfo { HtmlEditorRoute({ Map? content, - _i27.Key? key, - List<_i26.PageRouteInfo>? children, + _i28.Key? key, + List<_i27.PageRouteInfo>? children, }) : super( HtmlEditorRoute.name, args: HtmlEditorRouteArgs( @@ -355,14 +409,14 @@ class HtmlEditorRoute extends _i26.PageRouteInfo { static const String name = 'HtmlEditorRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const HtmlEditorRouteArgs()); - return _i26.DeferredWidget( - _i9.loadLibrary, - () => _i9.HtmlEditorPage( + return _i27.DeferredWidget( + _i10.loadLibrary, + () => _i10.HtmlEditorPage( content: args.content, key: args.key, ), @@ -379,7 +433,7 @@ class HtmlEditorRouteArgs { final Map? content; - final _i27.Key? key; + final _i28.Key? key; @override String toString() { @@ -388,12 +442,12 @@ class HtmlEditorRouteArgs { } /// generated route for -/// [_i10.InfoPage] -class InfoRoute extends _i26.PageRouteInfo { +/// [_i11.InfoPage] +class InfoRoute extends _i27.PageRouteInfo { InfoRoute({ int? id, - _i28.Key? key, - List<_i26.PageRouteInfo>? children, + _i29.Key? key, + List<_i27.PageRouteInfo>? children, }) : super( InfoRoute.name, args: InfoRouteArgs( @@ -406,15 +460,15 @@ class InfoRoute extends _i26.PageRouteInfo { static const String name = 'InfoRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => InfoRouteArgs(id: pathParams.optInt('id'))); - return _i26.DeferredWidget( - _i10.loadLibrary, - () => _i10.InfoPage( + return _i27.DeferredWidget( + _i11.loadLibrary, + () => _i11.InfoPage( id: args.id, key: args.key, ), @@ -431,7 +485,7 @@ class InfoRouteArgs { final int? id; - final _i28.Key? key; + final _i29.Key? key; @override String toString() { @@ -440,9 +494,9 @@ class InfoRouteArgs { } /// generated route for -/// [_i11.InstallPage] -class InstallRoute extends _i26.PageRouteInfo { - const InstallRoute({List<_i26.PageRouteInfo>? children}) +/// [_i12.InstallPage] +class InstallRoute extends _i27.PageRouteInfo { + const InstallRoute({List<_i27.PageRouteInfo>? children}) : super( InstallRoute.name, initialChildren: children, @@ -450,21 +504,21 @@ class InstallRoute extends _i26.PageRouteInfo { static const String name = 'InstallRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i11.loadLibrary, - () => _i11.InstallPage(), + return _i27.DeferredWidget( + _i12.loadLibrary, + () => _i12.InstallPage(), ); }, ); } /// generated route for -/// [_i12.LoginPage] -class LoginRoute extends _i26.PageRouteInfo { - const LoginRoute({List<_i26.PageRouteInfo>? children}) +/// [_i13.LoginPage] +class LoginRoute extends _i27.PageRouteInfo { + const LoginRoute({List<_i27.PageRouteInfo>? children}) : super( LoginRoute.name, initialChildren: children, @@ -472,25 +526,25 @@ class LoginRoute extends _i26.PageRouteInfo { static const String name = 'LoginRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i12.loadLibrary, - () => _i12.LoginPage(), + return _i27.DeferredWidget( + _i13.loadLibrary, + () => _i13.LoginPage(), ); }, ); } /// generated route for -/// [_i13.MapPage] -class MapRoute extends _i26.PageRouteInfo { +/// [_i14.MapPage] +class MapRoute extends _i27.PageRouteInfo { MapRoute({ int? id, - _i29.PlaceModel? place, - _i27.Key? key, - List<_i26.PageRouteInfo>? children, + _i30.PlaceModel? place, + _i28.Key? key, + List<_i27.PageRouteInfo>? children, }) : super( MapRoute.name, args: MapRouteArgs( @@ -504,15 +558,15 @@ class MapRoute extends _i26.PageRouteInfo { static const String name = 'MapRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => MapRouteArgs(id: pathParams.optInt('id'))); - return _i26.DeferredWidget( - _i13.loadLibrary, - () => _i13.MapPage( + return _i27.DeferredWidget( + _i14.loadLibrary, + () => _i14.MapPage( id: args.id, place: args.place, key: args.key, @@ -531,9 +585,9 @@ class MapRouteArgs { final int? id; - final _i29.PlaceModel? place; + final _i30.PlaceModel? place; - final _i27.Key? key; + final _i28.Key? key; @override String toString() { @@ -542,9 +596,9 @@ class MapRouteArgs { } /// generated route for -/// [_i14.MySchedulePage] -class MyScheduleRoute extends _i26.PageRouteInfo { - const MyScheduleRoute({List<_i26.PageRouteInfo>? children}) +/// [_i15.MySchedulePage] +class MyScheduleRoute extends _i27.PageRouteInfo { + const MyScheduleRoute({List<_i27.PageRouteInfo>? children}) : super( MyScheduleRoute.name, initialChildren: children, @@ -552,21 +606,21 @@ class MyScheduleRoute extends _i26.PageRouteInfo { static const String name = 'MyScheduleRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i14.loadLibrary, - () => _i14.MySchedulePage(), + return _i27.DeferredWidget( + _i15.loadLibrary, + () => _i15.MySchedulePage(), ); }, ); } /// generated route for -/// [_i15.NewsFormPage] -class NewsFormRoute extends _i26.PageRouteInfo { - const NewsFormRoute({List<_i26.PageRouteInfo>? children}) +/// [_i16.NewsFormPage] +class NewsFormRoute extends _i27.PageRouteInfo { + const NewsFormRoute({List<_i27.PageRouteInfo>? children}) : super( NewsFormRoute.name, initialChildren: children, @@ -574,24 +628,24 @@ class NewsFormRoute extends _i26.PageRouteInfo { static const String name = 'NewsFormRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i15.loadLibrary, - () => _i15.NewsFormPage(), + return _i27.DeferredWidget( + _i16.loadLibrary, + () => _i16.NewsFormPage(), ); }, ); } /// generated route for -/// [_i16.NewsPage] -class NewsRoute extends _i26.PageRouteInfo { +/// [_i17.NewsPage] +class NewsRoute extends _i27.PageRouteInfo { NewsRoute({ - _i27.Key? key, - _i27.VoidCallback? onSetAsRead, - List<_i26.PageRouteInfo>? children, + _i28.Key? key, + _i28.VoidCallback? onSetAsRead, + List<_i27.PageRouteInfo>? children, }) : super( NewsRoute.name, args: NewsRouteArgs( @@ -603,14 +657,14 @@ class NewsRoute extends _i26.PageRouteInfo { static const String name = 'NewsRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const NewsRouteArgs()); - return _i26.DeferredWidget( - _i16.loadLibrary, - () => _i16.NewsPage( + return _i27.DeferredWidget( + _i17.loadLibrary, + () => _i17.NewsPage( key: args.key, onSetAsRead: args.onSetAsRead, ), @@ -625,9 +679,9 @@ class NewsRouteArgs { this.onSetAsRead, }); - final _i27.Key? key; + final _i28.Key? key; - final _i27.VoidCallback? onSetAsRead; + final _i28.VoidCallback? onSetAsRead; @override String toString() { @@ -636,9 +690,9 @@ class NewsRouteArgs { } /// generated route for -/// [_i17.OccasionHomePage] -class OccasionHomeRoute extends _i26.PageRouteInfo { - const OccasionHomeRoute({List<_i26.PageRouteInfo>? children}) +/// [_i18.OccasionHomePage] +class OccasionHomeRoute extends _i27.PageRouteInfo { + const OccasionHomeRoute({List<_i27.PageRouteInfo>? children}) : super( OccasionHomeRoute.name, initialChildren: children, @@ -646,21 +700,21 @@ class OccasionHomeRoute extends _i26.PageRouteInfo { static const String name = 'OccasionHomeRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i17.loadLibrary, - () => _i17.OccasionHomePage(), + return _i27.DeferredWidget( + _i18.loadLibrary, + () => _i18.OccasionHomePage(), ); }, ); } /// generated route for -/// [_i18.ResetPasswordPage] -class ResetPasswordRoute extends _i26.PageRouteInfo { - const ResetPasswordRoute({List<_i26.PageRouteInfo>? children}) +/// [_i19.ResetPasswordPage] +class ResetPasswordRoute extends _i27.PageRouteInfo { + const ResetPasswordRoute({List<_i27.PageRouteInfo>? children}) : super( ResetPasswordRoute.name, initialChildren: children, @@ -668,21 +722,21 @@ class ResetPasswordRoute extends _i26.PageRouteInfo { static const String name = 'ResetPasswordRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i18.loadLibrary, - () => _i18.ResetPasswordPage(), + return _i27.DeferredWidget( + _i19.loadLibrary, + () => _i19.ResetPasswordPage(), ); }, ); } /// generated route for -/// [_i19.ScheduleNavigationPage] -class ScheduleNavigationRoute extends _i26.PageRouteInfo { - const ScheduleNavigationRoute({List<_i26.PageRouteInfo>? children}) +/// [_i20.ScheduleNavigationPage] +class ScheduleNavigationRoute extends _i27.PageRouteInfo { + const ScheduleNavigationRoute({List<_i27.PageRouteInfo>? children}) : super( ScheduleNavigationRoute.name, initialChildren: children, @@ -690,21 +744,21 @@ class ScheduleNavigationRoute extends _i26.PageRouteInfo { static const String name = 'ScheduleNavigationRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i19.loadLibrary, - () => _i19.ScheduleNavigationPage(), + return _i27.DeferredWidget( + _i20.loadLibrary, + () => _i20.ScheduleNavigationPage(), ); }, ); } /// generated route for -/// [_i20.SchedulePage] -class ScheduleRoute extends _i26.PageRouteInfo { - const ScheduleRoute({List<_i26.PageRouteInfo>? children}) +/// [_i21.SchedulePage] +class ScheduleRoute extends _i27.PageRouteInfo { + const ScheduleRoute({List<_i27.PageRouteInfo>? children}) : super( ScheduleRoute.name, initialChildren: children, @@ -712,21 +766,21 @@ class ScheduleRoute extends _i26.PageRouteInfo { static const String name = 'ScheduleRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i20.loadLibrary, - () => _i20.SchedulePage(), + return _i27.DeferredWidget( + _i21.loadLibrary, + () => _i21.SchedulePage(), ); }, ); } /// generated route for -/// [_i21.SettingsPage] -class SettingsRoute extends _i26.PageRouteInfo { - const SettingsRoute({List<_i26.PageRouteInfo>? children}) +/// [_i22.SettingsPage] +class SettingsRoute extends _i27.PageRouteInfo { + const SettingsRoute({List<_i27.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -734,21 +788,21 @@ class SettingsRoute extends _i26.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i21.loadLibrary, - () => _i21.SettingsPage(), + return _i27.DeferredWidget( + _i22.loadLibrary, + () => _i22.SettingsPage(), ); }, ); } /// generated route for -/// [_i22.SignupPage] -class SignupRoute extends _i26.PageRouteInfo { - const SignupRoute({List<_i26.PageRouteInfo>? children}) +/// [_i23.SignupPage] +class SignupRoute extends _i27.PageRouteInfo { + const SignupRoute({List<_i27.PageRouteInfo>? children}) : super( SignupRoute.name, initialChildren: children, @@ -756,23 +810,23 @@ class SignupRoute extends _i26.PageRouteInfo { static const String name = 'SignupRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i22.loadLibrary, - () => _i22.SignupPage(), + return _i27.DeferredWidget( + _i23.loadLibrary, + () => _i23.SignupPage(), ); }, ); } /// generated route for -/// [_i23.SongbookPage] -class SongbookRoute extends _i26.PageRouteInfo { +/// [_i24.SongbookPage] +class SongbookRoute extends _i27.PageRouteInfo { SongbookRoute({ - _i27.Key? key, - List<_i26.PageRouteInfo>? children, + _i28.Key? key, + List<_i27.PageRouteInfo>? children, }) : super( SongbookRoute.name, args: SongbookRouteArgs(key: key), @@ -781,14 +835,14 @@ class SongbookRoute extends _i26.PageRouteInfo { static const String name = 'SongbookRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const SongbookRouteArgs()); - return _i26.DeferredWidget( - _i23.loadLibrary, - () => _i23.SongbookPage(key: args.key), + return _i27.DeferredWidget( + _i24.loadLibrary, + () => _i24.SongbookPage(key: args.key), ); }, ); @@ -797,7 +851,7 @@ class SongbookRoute extends _i26.PageRouteInfo { class SongbookRouteArgs { const SongbookRouteArgs({this.key}); - final _i27.Key? key; + final _i28.Key? key; @override String toString() { @@ -806,9 +860,9 @@ class SongbookRouteArgs { } /// generated route for -/// [_i24.TimetablePage] -class TimetableRoute extends _i26.PageRouteInfo { - const TimetableRoute({List<_i26.PageRouteInfo>? children}) +/// [_i25.TimetablePage] +class TimetableRoute extends _i27.PageRouteInfo { + const TimetableRoute({List<_i27.PageRouteInfo>? children}) : super( TimetableRoute.name, initialChildren: children, @@ -816,21 +870,21 @@ class TimetableRoute extends _i26.PageRouteInfo { static const String name = 'TimetableRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i24.loadLibrary, - () => _i24.TimetablePage(), + return _i27.DeferredWidget( + _i25.loadLibrary, + () => _i25.TimetablePage(), ); }, ); } /// generated route for -/// [_i25.UserPage] -class UserRoute extends _i26.PageRouteInfo { - const UserRoute({List<_i26.PageRouteInfo>? children}) +/// [_i26.UserPage] +class UserRoute extends _i27.PageRouteInfo { + const UserRoute({List<_i27.PageRouteInfo>? children}) : super( UserRoute.name, initialChildren: children, @@ -838,12 +892,12 @@ class UserRoute extends _i26.PageRouteInfo { static const String name = 'UserRoute'; - static _i26.PageInfo page = _i26.PageInfo( + static _i27.PageInfo page = _i27.PageInfo( name, builder: (data) { - return _i26.DeferredWidget( - _i25.loadLibrary, - () => _i25.UserPage(), + return _i27.DeferredWidget( + _i26.loadLibrary, + () => _i26.UserPage(), ); }, ); diff --git a/lib/dataModelsEshop/TbEshop.dart b/lib/dataModelsEshop/TbEshop.dart index 69f3540f..58ce4cb7 100644 --- a/lib/dataModelsEshop/TbEshop.dart +++ b/lib/dataModelsEshop/TbEshop.dart @@ -5,6 +5,7 @@ class TbEshop { static OrdersTb orders = const OrdersTb(); static TicketsTb tickets = const TicketsTb(); static BlueprintTb blueprints = const BlueprintTb(); + static SpotsTb spots = const SpotsTb(); } class ProductTypesTb { @@ -76,7 +77,22 @@ class BlueprintTb { String get data => "data"; String get title => "title"; String get organization => "organization"; + String get occasion => "occasion"; String get configuration => "configuration"; String get objects => "objects"; String get groups => "groups"; } + +class SpotsTb { + const SpotsTb(); + String get table => "spots"; + String get id => "id"; + String get created_at => "created_at"; + String get updated_at => "updated_at"; + String get occasion => "occasion"; + String get product => "product"; + String get secret => "secret"; + String get secret_expiration_time => "secret_expiration_time"; + String get title => "title"; + String get order_product_ticket => "order_product_ticket"; +} \ No newline at end of file diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index bf6f2944..0f2f6d60 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -5,6 +5,7 @@ import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; import 'package:fstapp/dataModelsEshop/ProductModel.dart'; import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/services/Utilities.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -69,4 +70,17 @@ class DbEshop { return BlueprintModel.fromJson(response["data"]); } + + static Future getBlueprintForEdit(int blueprintId) async { + + final response = await _supabaseEshop + .from(TbEshop.blueprints.table) + .select() + .eq(TbEshop.blueprints.id, blueprintId) + .single(); + var spots = await _supabaseEshop.from(TbEshop.spots.table).select().eq(TbEshop.spots.occasion, RightsService.currentOccasion!); + response[BlueprintModel.metaSpots] = spots; + + return BlueprintModel.fromJson(response); + } } diff --git a/lib/dataServices/RightsService.dart b/lib/dataServices/RightsService.dart index 1f5f6b1d..741c1902 100644 --- a/lib/dataServices/RightsService.dart +++ b/lib/dataServices/RightsService.dart @@ -1,6 +1,5 @@ import 'package:fstapp/AppRouter.dart'; import 'package:fstapp/RouterService.dart'; -import 'package:fstapp/appConfig.dart'; import 'package:fstapp/dataServices/OfflineDataService.dart'; import 'package:fstapp/dataModels/OccasionUserModel.dart'; import 'package:fstapp/dataServices/SynchroService.dart'; @@ -73,7 +72,6 @@ class RightsService{ } static bool isEditor() { - return true; return currentUserOccasion?.isEditor??false; } diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart new file mode 100644 index 00000000..0d6c649d --- /dev/null +++ b/lib/pages/BlueprintEditorPage.dart @@ -0,0 +1,271 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:fstapp/components/seatReservation/widgets/SeatWidget.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; +import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/services/DialogHelper.dart'; +import 'package:fstapp/services/ToastHelper.dart'; +import 'package:fstapp/styles/StylesConfig.dart'; +import 'package:fstapp/widgets/SeatReservationWidget.dart'; + +import '../components/seatReservation/model/SeatLayoutStateModel.dart'; +import '../components/seatReservation/model/SeatModel.dart'; +import '../components/seatReservation/utils/SeatState.dart'; +import '../components/seatReservation/widgets/SeatLayoutWidget.dart'; + +@RoutePage() +class BlueprintEditorPage extends StatefulWidget { + static const ROUTE = "blueprintEdit"; + + int? id; + + BlueprintEditorPage({super.key, @pathParam this.id}); + + @override + State createState() => _BlueprintEditorPageState(); +} + +class _BlueprintEditorPageState extends State { + BlueprintModel? blueprint; + List? currentBoxes; + BlueprintGroupModel? currentGroup; + + int currentWidth = 20; + int currentHeight = 20; + + List changedBoxes = []; + List allBoxes = []; + + selectionMode currentSelectionMode = selectionMode.none; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (widget.id == null && context.routeData.hasPendingChildren) { + widget.id = + context.routeData.pendingChildren[0].pathParams.getInt("id"); + } + loadData(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Editor Blueprintu:"), + Flexible( + child: Text( + blueprint?.title ?? "", + overflow: TextOverflow.ellipsis, + ), + ), + IconButton( + icon: const Icon(Icons.edit), + onPressed: editBlueprintTitle, + ), + ], + ), + ), + body: SafeArea( + child: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: StylesConfig.appMaxWidth), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 16), + buildDimensionControls(), // Dimension controls above layout + const SizedBox(height: 12), + Flexible( + child: blueprint == null + ? const Center(child: CircularProgressIndicator()) + : SeatLayoutWidget( + onSeatTap: handleSeatTap, + stateModel: SeatLayoutStateModel( + rows: currentHeight, + cols: currentWidth, + seatSize: SeatReservationWidget.boxSize, + currentObjects: currentBoxes ?? [], + allBoxes: allBoxes, + ), + ), + ), + const SizedBox(height: 12), + buildLegend(), // Legend moved below layout + const SizedBox(height: 12), + buildEditorActions(), // Save and cancel buttons + const SizedBox(height: 12), + ], + ), + ), + ), + ), + ); + } + + Widget buildDimensionControls() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildDimensionEditor( + "Šířka", + currentWidth, + (value) => setState(() { + currentWidth = value; + }), + ), + const SizedBox(width: 16), + buildDimensionEditor( + "Výška", + currentHeight, + (value) => setState(() { + currentHeight = value; + }), + ), + ], + ); + } + + Widget buildLegend() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + buildLegendItem("Obsazené", SeatState.ordered), + buildLegendItem("Dostupné", SeatState.available, selectable: true, onTap: () { + currentSelectionMode = selectionMode.addAvailable; + }), + buildLegendItem("Vybrané", SeatState.selected), + buildLegendItem("Stůl", SeatState.black, selectable: true, onTap: () { + currentSelectionMode = selectionMode.addBlack; + }), + ], + ), + ); + } + + Widget buildDimensionEditor(String label, int currentValue, ValueChanged onChanged) { + return Column( + children: [ + Text(label), + const SizedBox(height: 8), + Row( + children: [ + IconButton( + icon: const Icon(Icons.remove), + onPressed: () { + if (currentValue > 1) { + onChanged(currentValue - 1); + } + }, + ), + Text( + "$currentValue" + ), + IconButton( + icon: const Icon(Icons.add), + onPressed: () { + onChanged(currentValue + 1); + }, + ), + ], + ), + ], + ); + } + + Widget buildLegendItem(String label, SeatState state, {bool selectable = false, VoidCallback? onTap}) { + return GestureDetector( + onTap: selectable ? onTap : null, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SeatWidget.buildSeat(state: state, size: SeatReservationWidget.boxSize.toDouble()), + const SizedBox(width: 8), + Text(label), + ], + ), + ); + } + + Widget buildEditorActions() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () => Navigator.pop(context), + child: const Text("Storno").tr(), + ), + const SizedBox(width: 12), + ElevatedButton( + onPressed: saveChanges, + child: const Text("Save").tr(), + ), + ], + ); + } + + void editBlueprintTitle() async { + final newTitle = await DialogHelper.showInputDialog( + context: context, + dialogTitle: "Změnit název", + labelText: "Název blueprintu", + initialValue: blueprint?.title ?? "", + ); + + if (newTitle != null && newTitle.isNotEmpty) { + setState(() { + blueprint!.title = newTitle; + }); + } + } + + void handleSeatTap(SeatModel model) { + if (currentSelectionMode == selectionMode.addBlack) { + model.seatState = SeatState.black; + changedBoxes.add(model); + } else if (currentSelectionMode == selectionMode.addAvailable) { + model.seatState = SeatState.available; + model.objectModel = model.objectModel ?? BlueprintObjectModel(x: model.colI, y: model.rowI); + model.objectModel!.group = currentGroup!; + model.objectModel!.title = currentGroup!.getNextBoxName(); + currentGroup!.objects.add(model.objectModel!); + changedBoxes.add(model); + ToastHelper.Show(context, "Přidáno sedadlo ${model.objectModel!.title}."); + } + setState(() {}); + } + + void saveChanges() async { + var updatedObjects = changedBoxes.map((e) { + var newObject = e.objectModel ?? BlueprintObjectModel(x: e.colI, y: e.rowI); + newObject.stateEnum = e.seatState; + return newObject; + }).toList(); + + // Save changes to the backend + // DataService.updateBlueprintObjects(updatedObjects); + + ToastHelper.Show(context, "Změny byly uloženy."); + Navigator.pop(context); + } + + void loadData() async { + blueprint = await DbEshop.getBlueprintForEdit(widget.id!); + + setState(() { + currentBoxes = blueprint!.toBlueprintObjects(); + currentHeight = blueprint!.configuration!.height!; + currentWidth = blueprint!.configuration!.width!; + }); + } +} + +enum selectionMode { none, addBlack, addAvailable } diff --git a/lib/services/DialogHelper.dart b/lib/services/DialogHelper.dart index ff943933..c4cacf77 100644 --- a/lib/services/DialogHelper.dart +++ b/lib/services/DialogHelper.dart @@ -466,4 +466,38 @@ class DialogHelper{ return completer.future; } + static Future showInputDialog({ + required BuildContext context, + required String initialValue, + required String dialogTitle, + required String labelText, + }) async { + final TextEditingController controller = TextEditingController(text: initialValue); + + return await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(dialogTitle), + content: TextField( + controller: controller, + decoration: InputDecoration(labelText: labelText), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), // Cancel button + child: const Text("Storno").tr(), + ), + TextButton( + onPressed: () { + Navigator.pop(context, controller.text.trim()); // Return the input + }, + child: const Text("Save").tr(), + ), + ], + ); + }, + ); + } + } \ No newline at end of file diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index 058c1bbb..df5502b0 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -13,6 +13,8 @@ import '../components/seatReservation/utils/SeatState.dart'; import '../components/seatReservation/widgets/SeatLayoutWidget.dart'; class SeatReservationWidget extends StatefulWidget { + static const int boxSize = 15; + final int blueprintId; final String secret; final String formKey; @@ -27,7 +29,6 @@ class _SeatReservationWidgetState extends State { SeatModel? selectedSeat; List allObjects = []; - static int boxSize = 15; BlueprintModel? blueprint; _SeatReservationWidgetState(); @@ -78,7 +79,7 @@ class _SeatReservationWidgetState extends State { children: [ SeatWidget.buildSeat( state: SeatState.available, - size: boxSize.toDouble(), + size: SeatReservationWidget.boxSize.toDouble(), ), const SizedBox(width: 8), const Text("dostupné"), @@ -89,7 +90,7 @@ class _SeatReservationWidgetState extends State { children: [ SeatWidget.buildSeat( state: SeatState.selected, - size: boxSize.toDouble(), + size: SeatReservationWidget.boxSize.toDouble(), ), const SizedBox(width: 8), const Text("vybrané"), @@ -130,7 +131,7 @@ class _SeatReservationWidgetState extends State { stateModel: SeatLayoutStateModel( rows: currentHeight, cols: currentWidth, - seatSize: boxSize, + seatSize: SeatReservationWidget.boxSize, currentObjects: currentObjects ?? [], allBoxes: allObjects, ), From a6b0d31a1b214cca4a8ad1d926654055a0961c72 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 00:35:47 +0100 Subject: [PATCH 051/159] bottom appbar --- lib/pages/BlueprintEditorPage.dart | 31 +++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 0d6c649d..c822f05d 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -9,6 +9,7 @@ import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/services/DialogHelper.dart'; import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/styles/StylesConfig.dart'; +import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/SeatReservationWidget.dart'; import '../components/seatReservation/model/SeatLayoutStateModel.dart'; @@ -80,7 +81,7 @@ class _BlueprintEditorPageState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(height: 16), - buildDimensionControls(), // Dimension controls above layout + buildDimensionControls(), const SizedBox(height: 12), Flexible( child: blueprint == null @@ -97,18 +98,38 @@ class _BlueprintEditorPageState extends State { ), ), const SizedBox(height: 12), - buildLegend(), // Legend moved below layout - const SizedBox(height: 12), - buildEditorActions(), // Save and cancel buttons - const SizedBox(height: 12), + buildLegend(), + const SizedBox(height: 24), ], ), ), ), ), + bottomNavigationBar: Container( + color: ThemeConfig.appBarColor(), + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + style: TextButton.styleFrom( + foregroundColor: Colors.white, + ), + child: Text("Storno").tr(), + ), + const SizedBox(width: 16), + ElevatedButton( + onPressed: saveChanges, + child: Text("Save").tr(), + ), + ], + ), + ), ); } + Widget buildDimensionControls() { return Row( mainAxisAlignment: MainAxisAlignment.center, From a2e72d59cd4763cff7e1383b6903d2e58e9e598b Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 07:42:16 +0100 Subject: [PATCH 052/159] autofit --- .../widgets/SeatLayoutWidget.dart | 19 ++++++++++++++++ lib/pages/BlueprintEditorPage.dart | 22 ++++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index 555365dd..253f7c52 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -8,11 +8,13 @@ import 'SeatWidget.dart'; class SeatLayoutWidget extends StatefulWidget { final SeatLayoutStateModel stateModel; + final SeatLayoutWidgetController? controller; final void Function(SeatModel model)? onSeatTap; const SeatLayoutWidget({ Key? key, required this.stateModel, + this.controller, this.onSeatTap, }) : super(key: key); @@ -26,6 +28,9 @@ class _SeatLayoutWidgetState extends State { @override void initState() { super.initState(); + + widget.controller?.attachFitLayout(_fitLayout); + WidgetsBinding.instance.addPostFrameCallback((_) { _fitLayout(); }); @@ -98,3 +103,17 @@ class _SeatLayoutWidgetState extends State { ); } } + +class SeatLayoutWidgetController { + Function()? _fitLayout; + + /// Attach a fit layout method + void attachFitLayout(Function()? fitLayout) { + _fitLayout = fitLayout; + } + + /// Call the fit layout method + void fitLayout() { + _fitLayout?.call(); + } +} diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index c822f05d..1bcbb499 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -42,6 +42,9 @@ class _BlueprintEditorPageState extends State { selectionMode currentSelectionMode = selectionMode.none; + final SeatLayoutWidgetController seatLayoutController = + SeatLayoutWidgetController(); + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -87,6 +90,7 @@ class _BlueprintEditorPageState extends State { child: blueprint == null ? const Center(child: CircularProgressIndicator()) : SeatLayoutWidget( + controller: seatLayoutController, onSeatTap: handleSeatTap, stateModel: SeatLayoutStateModel( rows: currentHeight, @@ -137,17 +141,23 @@ class _BlueprintEditorPageState extends State { buildDimensionEditor( "Šířka", currentWidth, - (value) => setState(() { - currentWidth = value; - }), + (value) { + setState(() { + currentWidth = value; + }); + seatLayoutController.fitLayout(); // Fit layout after width change + }, ), const SizedBox(width: 16), buildDimensionEditor( "Výška", currentHeight, - (value) => setState(() { - currentHeight = value; - }), + (value) { + setState(() { + currentHeight = value; + }); + seatLayoutController.fitLayout(); // Fit layout after height change + }, ), ], ); From 9029763112f9fbb75bcbe34c8a0b4e00f63102eb Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 07:43:12 +0100 Subject: [PATCH 053/159] adjust --- lib/pages/BlueprintEditorPage.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 1bcbb499..5415c007 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -101,9 +101,9 @@ class _BlueprintEditorPageState extends State { ), ), ), - const SizedBox(height: 12), + const SizedBox(height: 16), buildLegend(), - const SizedBox(height: 24), + const SizedBox(height: 16), ], ), ), From 8862e535f48d8c99a2b188f0063df1c6918da069 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 07:52:21 +0100 Subject: [PATCH 054/159] interactive legend --- lib/pages/BlueprintEditorPage.dart | 59 +++++++++++++++++++----------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 5415c007..ff04230d 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -167,21 +167,52 @@ class _BlueprintEditorPageState extends State { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.center, // Center legend items children: [ - buildLegendItem("Obsazené", SeatState.ordered), - buildLegendItem("Dostupné", SeatState.available, selectable: true, onTap: () { - currentSelectionMode = selectionMode.addAvailable; + buildLegendItem("Obsazené", SeatState.ordered, isActive: currentSelectionMode == selectionMode.none), + const SizedBox(width: 8), // Reduce spacing + buildLegendItem("Dostupné", SeatState.available, isActive: currentSelectionMode == selectionMode.addAvailable, onTap: () { + setState(() { + currentSelectionMode = selectionMode.addAvailable; + }); }), - buildLegendItem("Vybrané", SeatState.selected), - buildLegendItem("Stůl", SeatState.black, selectable: true, onTap: () { - currentSelectionMode = selectionMode.addBlack; + const SizedBox(width: 8), + buildLegendItem("Vybrané", SeatState.selected, isActive: currentSelectionMode == selectionMode.none), + const SizedBox(width: 8), + buildLegendItem("Stůl", SeatState.black, isActive: currentSelectionMode == selectionMode.addBlack, onTap: () { + setState(() { + currentSelectionMode = selectionMode.addBlack; + }); }), ], ), ); } + Widget buildLegendItem(String label, SeatState state, {bool isActive = false, VoidCallback? onTap}) { + return GestureDetector( + onTap: onTap, + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: isActive ? Colors.blue : Colors.transparent, // Highlight active state + width: 2, + ), + borderRadius: BorderRadius.circular(4), + ), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SeatWidget.buildSeat(state: state, size: SeatReservationWidget.boxSize.toDouble()), + const SizedBox(width: 4), + Text(label), + ], + ), + ), + ); + } + Widget buildDimensionEditor(String label, int currentValue, ValueChanged onChanged) { return Column( children: [ @@ -212,20 +243,6 @@ class _BlueprintEditorPageState extends State { ); } - Widget buildLegendItem(String label, SeatState state, {bool selectable = false, VoidCallback? onTap}) { - return GestureDetector( - onTap: selectable ? onTap : null, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SeatWidget.buildSeat(state: state, size: SeatReservationWidget.boxSize.toDouble()), - const SizedBox(width: 8), - Text(label), - ], - ), - ); - } - Widget buildEditorActions() { return Row( mainAxisAlignment: MainAxisAlignment.center, From 7fa13ab52ae523c3d0128fae26523fefab084e2a Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 07:57:57 +0100 Subject: [PATCH 055/159] hint --- lib/pages/BlueprintEditorPage.dart | 71 +++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index ff04230d..d541f740 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -39,6 +39,7 @@ class _BlueprintEditorPageState extends State { List changedBoxes = []; List allBoxes = []; + bool showHint = true; selectionMode currentSelectionMode = selectionMode.none; @@ -85,7 +86,7 @@ class _BlueprintEditorPageState extends State { children: [ const SizedBox(height: 16), buildDimensionControls(), - const SizedBox(height: 12), + const SizedBox(height: 16), Flexible( child: blueprint == null ? const Center(child: CircularProgressIndicator()) @@ -102,6 +103,15 @@ class _BlueprintEditorPageState extends State { ), ), const SizedBox(height: 16), + if (showHint) + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text( + "Pro přidávání sedadel a stolů klikněte na čtvereček v legendě (Dostupné nebo Stůl).", + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + ), buildLegend(), const SizedBox(height: 16), ], @@ -169,33 +179,56 @@ class _BlueprintEditorPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, // Center legend items children: [ - buildLegendItem("Obsazené", SeatState.ordered, isActive: currentSelectionMode == selectionMode.none), - const SizedBox(width: 8), // Reduce spacing - buildLegendItem("Dostupné", SeatState.available, isActive: currentSelectionMode == selectionMode.addAvailable, onTap: () { - setState(() { - currentSelectionMode = selectionMode.addAvailable; - }); - }), + buildLegendItem( + "Obsazené", + SeatState.ordered, + isActive: false, // None state is not active + ), + const SizedBox(width: 8), + buildLegendItem( + "Dostupné", + SeatState.available, + isActive: currentSelectionMode == selectionMode.addAvailable, + onTap: () { + setState(() { + currentSelectionMode = selectionMode.addAvailable; + showHint = false; // Hide hint on click + }); + }, + ), const SizedBox(width: 8), - buildLegendItem("Vybrané", SeatState.selected, isActive: currentSelectionMode == selectionMode.none), + buildLegendItem( + "Vybrané", + SeatState.selected, + isActive: false, // None state is not active + ), const SizedBox(width: 8), - buildLegendItem("Stůl", SeatState.black, isActive: currentSelectionMode == selectionMode.addBlack, onTap: () { - setState(() { - currentSelectionMode = selectionMode.addBlack; - }); - }), + buildLegendItem( + "Stůl", + SeatState.black, + isActive: currentSelectionMode == selectionMode.addBlack, + onTap: () { + setState(() { + currentSelectionMode = selectionMode.addBlack; + showHint = false; // Hide hint on click + }); + }, + ), ], ), ); } - Widget buildLegendItem(String label, SeatState state, {bool isActive = false, VoidCallback? onTap}) { + Widget buildLegendItem(String label, SeatState state, + {bool isActive = false, VoidCallback? onTap}) { return GestureDetector( onTap: onTap, child: Container( decoration: BoxDecoration( border: Border.all( - color: isActive ? Colors.blue : Colors.transparent, // Highlight active state + color: isActive + ? Theme.of(context).colorScheme.primary + : Colors.transparent, // Use primary color for active state width: 2, ), borderRadius: BorderRadius.circular(4), @@ -204,7 +237,10 @@ class _BlueprintEditorPageState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - SeatWidget.buildSeat(state: state, size: SeatReservationWidget.boxSize.toDouble()), + SeatWidget.buildSeat( + state: state, + size: SeatReservationWidget.boxSize.toDouble(), + ), const SizedBox(width: 4), Text(label), ], @@ -316,4 +352,5 @@ class _BlueprintEditorPageState extends State { } } + enum selectionMode { none, addBlack, addAvailable } From a7f548d9aa733543d7d82cf18fcc0cd0a3e9b634 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:43:44 +0100 Subject: [PATCH 056/159] layout --- lib/dataModelsEshop/BlueprintModel.dart | 2 +- lib/pages/BlueprintEditorPage.dart | 402 +++++++++++++++++------- lib/services/DialogHelper.dart | 2 +- 3 files changed, 287 insertions(+), 119 deletions(-) diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index 1c390acb..f908e8a7 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -42,7 +42,7 @@ class BlueprintModel { groups: json[TbEshop.blueprints.groups] != null ? List.from( json[TbEshop.blueprints.groups].map((g) => BlueprintGroupModel.fromJson(g))) - : null, + : [], ); } diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index d541f740..9f6a04cb 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -8,6 +8,7 @@ import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/services/DialogHelper.dart'; import 'package:fstapp/services/ToastHelper.dart'; +import 'package:fstapp/services/Utilities.dart'; import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/SeatReservationWidget.dart'; @@ -63,8 +64,7 @@ class _BlueprintEditorPageState extends State { title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("Editor Blueprintu:"), - Flexible( + Center( child: Text( blueprint?.title ?? "", overflow: TextOverflow.ellipsis, @@ -79,43 +79,64 @@ class _BlueprintEditorPageState extends State { ), body: SafeArea( child: Center( - child: Container( - constraints: const BoxConstraints(maxWidth: StylesConfig.appMaxWidth), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 16), - buildDimensionControls(), - const SizedBox(height: 16), - Flexible( - child: blueprint == null - ? const Center(child: CircularProgressIndicator()) - : SeatLayoutWidget( - controller: seatLayoutController, - onSeatTap: handleSeatTap, - stateModel: SeatLayoutStateModel( - rows: currentHeight, - cols: currentWidth, - seatSize: SeatReservationWidget.boxSize, - currentObjects: currentBoxes ?? [], - allBoxes: allBoxes, - ), - ), + child: Row( + children: [ + // Left panel (Legend) + Container( + width: 250, + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (showHint) + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Text( + "Pro přidávání sedadel a stolů klikněte na čtvereček v legendě (Dostupné nebo Stůl).", + style: Theme.of(context).textTheme.bodySmall, + textAlign: TextAlign.left, + ), + ), + buildLegend(), + ], ), - const SizedBox(height: 16), - if (showHint) - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Text( - "Pro přidávání sedadel a stolů klikněte na čtvereček v legendě (Dostupné nebo Stůl).", - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, + ), + const SizedBox(width: 16), // Padding between legend and main content + // Main content section + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 16), + buildDimensionControls(), + const SizedBox(height: 16), + Flexible( + child: blueprint == null + ? const Center(child: CircularProgressIndicator()) + : SeatLayoutWidget( + controller: seatLayoutController, + onSeatTap: handleSeatTap, + stateModel: SeatLayoutStateModel( + rows: currentHeight, + cols: currentWidth, + seatSize: SeatReservationWidget.boxSize, + currentObjects: currentBoxes ?? [], + allBoxes: allBoxes, + ), + ), ), - ), - buildLegend(), - const SizedBox(height: 16), - ], - ), + const SizedBox(height: 16), + ], + ), + ), + const SizedBox(width: 16), // Padding between main content and groups + // Right panel (Groups) + Container( + width: 250, + padding: const EdgeInsets.all(16.0), + child: buildGroupsSection(), + ), + ], ), ), ), @@ -144,6 +165,215 @@ class _BlueprintEditorPageState extends State { } + Widget buildGroupsSection() { + return blueprint == null + ? const Center(child: CircularProgressIndicator()) : + Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Stoly (Skupiny):", + style: Theme.of(context).textTheme.titleMedium, + ), + Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + tooltip: "Přidat nový stůl", + onPressed: addGroup, + ), + IconButton( + icon: const Icon(Icons.delete), + tooltip: "Smazat vybraný stůl", + onPressed: deleteGroup, + ), + IconButton( + icon: const Icon(Icons.edit), + tooltip: "Přejmenovat vybraný stůl", + onPressed: renameGroup, + ), + ], + ), + ], + ), + const SizedBox(height: 8), + Container( + height: 120, // Limit height for the group list + child: ListView.builder( + itemCount: blueprint!.groups!.length, + itemBuilder: (context, index) { + final group = blueprint!.groups![index]; + return GestureDetector( + onTap: () { + setState(() { + currentGroup = group; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + decoration: BoxDecoration( + color: currentGroup == group + ? Theme.of(context).colorScheme.primary.withOpacity(0.1) + : Colors.transparent, + border: Border.all( + color: currentGroup == group + ? Theme.of(context).colorScheme.primary + : Colors.grey, + ), + borderRadius: BorderRadius.circular(4), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + group.title!, + style: Theme.of(context).textTheme.bodySmall, + ), + Text( + "${group.objects.length} objektů", + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ), + ); + }, + ), + ), + ], + ); + } + + void addGroup() async { + final newTitle = await DialogHelper.showInputDialog( + context: context, + dialogTitle: "Přidat nový stůl", + labelText: "Název stolu", + ); + + if (newTitle != null && newTitle.isNotEmpty) { + setState(() { + blueprint!.groups!.add(BlueprintGroupModel(title: newTitle)); + blueprint!.groups!.sort((a,b) => Utilities.naturalCompare(a.title!, b.title!)); + }); + } + } + + void deleteGroup() { + if (currentGroup == null) return; + + setState(() { + blueprint!.groups!.remove(currentGroup); + currentGroup = null; + }); + } + + void renameGroup() async { + if (currentGroup == null) return; + + final newTitle = await DialogHelper.showInputDialog( + context: context, + dialogTitle: "Přejmenovat stůl", + labelText: "Nový název", + initialValue: currentGroup!.title, + ); + + if (newTitle != null && newTitle.isNotEmpty) { + setState(() { + currentGroup!.title = newTitle; + blueprint!.groups!.sort((a,b) => Utilities.naturalCompare(a.title!, b.title!)); + }); + } + } + + Widget buildLegend() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildLegendItem( + "Stůl", + SeatState.black, + isActive: currentSelectionMode == selectionMode.addBlack, + onTap: () { + setState(() { + currentSelectionMode = selectionMode.addBlack; + showHint = false; // Hide hint on click + }); + }, + ), + const SizedBox(height: 8), + buildLegendItem( + "Dostupné", + SeatState.available, + isActive: currentSelectionMode == selectionMode.addAvailable, + onTap: () { + setState(() { + currentSelectionMode = selectionMode.addAvailable; + showHint = false; // Hide hint on click + }); + }, + ), + const SizedBox(height: 8), + buildLegendItem( + "Obsazené", + SeatState.ordered, + isActive: false, // Not clickable + grayedOut: true, + ), + const SizedBox(height: 8), + buildLegendItem( + "Vybrané", + SeatState.selected, + isActive: false, // Not clickable + grayedOut: true, + ), + ], + ); + } + + Widget buildLegendItem(String label, SeatState state, + {bool isActive = false, VoidCallback? onTap, bool grayedOut = false}) { + return MouseRegion( + cursor: grayedOut ? SystemMouseCursors.forbidden : SystemMouseCursors.click, + child: GestureDetector( + onTap: grayedOut ? null : onTap, + child: Opacity( + opacity: grayedOut ? 0.4 : 1.0, // Reduce opacity for grayed-out items + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: isActive + ? Theme.of(context).colorScheme.primary + : Colors.transparent, // Highlight active items + width: 2, + ), + borderRadius: BorderRadius.circular(4), + ), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SeatWidget.buildSeat( + state: state, + size: SeatReservationWidget.boxSize.toDouble(), + ), + const SizedBox(width: 4), + Text( + label, + style: TextStyle( + color: grayedOut ? Colors.grey : null, + ), + ), + ], + ), + ), + ), + ), + ); + } + Widget buildDimensionControls() { return Row( mainAxisAlignment: MainAxisAlignment.center, @@ -158,7 +388,7 @@ class _BlueprintEditorPageState extends State { seatLayoutController.fitLayout(); // Fit layout after width change }, ), - const SizedBox(width: 16), + const SizedBox(width: 12), // Reduced spacing between editors buildDimensionEditor( "Výška", currentHeight, @@ -173,91 +403,23 @@ class _BlueprintEditorPageState extends State { ); } - Widget buildLegend() { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, // Center legend items - children: [ - buildLegendItem( - "Obsazené", - SeatState.ordered, - isActive: false, // None state is not active - ), - const SizedBox(width: 8), - buildLegendItem( - "Dostupné", - SeatState.available, - isActive: currentSelectionMode == selectionMode.addAvailable, - onTap: () { - setState(() { - currentSelectionMode = selectionMode.addAvailable; - showHint = false; // Hide hint on click - }); - }, - ), - const SizedBox(width: 8), - buildLegendItem( - "Vybrané", - SeatState.selected, - isActive: false, // None state is not active - ), - const SizedBox(width: 8), - buildLegendItem( - "Stůl", - SeatState.black, - isActive: currentSelectionMode == selectionMode.addBlack, - onTap: () { - setState(() { - currentSelectionMode = selectionMode.addBlack; - showHint = false; // Hide hint on click - }); - }, - ), - ], - ), - ); - } - - Widget buildLegendItem(String label, SeatState state, - {bool isActive = false, VoidCallback? onTap}) { - return GestureDetector( - onTap: onTap, - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: isActive - ? Theme.of(context).colorScheme.primary - : Colors.transparent, // Use primary color for active state - width: 2, - ), - borderRadius: BorderRadius.circular(4), - ), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SeatWidget.buildSeat( - state: state, - size: SeatReservationWidget.boxSize.toDouble(), - ), - const SizedBox(width: 4), - Text(label), - ], - ), - ), - ); - } - Widget buildDimensionEditor(String label, int currentValue, ValueChanged onChanged) { return Column( children: [ - Text(label), - const SizedBox(height: 8), + Text( + label, + style: TextStyle( + fontSize: 12, + ), + ), + const SizedBox(height: 4), Row( children: [ IconButton( icon: const Icon(Icons.remove), + iconSize: 16, + padding: const EdgeInsets.all(4), + constraints: const BoxConstraints(), onPressed: () { if (currentValue > 1) { onChanged(currentValue - 1); @@ -265,10 +427,16 @@ class _BlueprintEditorPageState extends State { }, ), Text( - "$currentValue" + "$currentValue", + style: TextStyle( + fontSize: 14, + ), ), IconButton( icon: const Icon(Icons.add), + iconSize: 16, + padding: const EdgeInsets.all(4), + constraints: const BoxConstraints(), onPressed: () { onChanged(currentValue + 1); }, diff --git a/lib/services/DialogHelper.dart b/lib/services/DialogHelper.dart index c4cacf77..deb5fc0f 100644 --- a/lib/services/DialogHelper.dart +++ b/lib/services/DialogHelper.dart @@ -468,7 +468,7 @@ class DialogHelper{ static Future showInputDialog({ required BuildContext context, - required String initialValue, + String? initialValue, required String dialogTitle, required String labelText, }) async { From 6bd002c5ea75cd7dd60feaaa787474453d3b23e2 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:49:52 +0100 Subject: [PATCH 057/159] auto naming --- lib/pages/BlueprintEditorPage.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 9f6a04cb..12776616 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -247,16 +247,19 @@ class _BlueprintEditorPageState extends State { } void addGroup() async { + final defaultName = "Stůl ${blueprint!.groups!.length + 1}"; + final newTitle = await DialogHelper.showInputDialog( context: context, dialogTitle: "Přidat nový stůl", labelText: "Název stolu", + initialValue: defaultName, // Pre-fill input with default name ); if (newTitle != null && newTitle.isNotEmpty) { setState(() { blueprint!.groups!.add(BlueprintGroupModel(title: newTitle)); - blueprint!.groups!.sort((a,b) => Utilities.naturalCompare(a.title!, b.title!)); + blueprint!.groups!.sort((a, b) => Utilities.naturalCompare(a.title!, b.title!)); }); } } From bcc95f608ffd2698307c8d9aa3d58398c0756734 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:58:04 +0100 Subject: [PATCH 058/159] center --- lib/pages/BlueprintEditorPage.dart | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 12776616..7e670d48 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -61,21 +61,14 @@ class _BlueprintEditorPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Center( - child: Text( - blueprint?.title ?? "", - overflow: TextOverflow.ellipsis, - ), - ), - IconButton( - icon: const Icon(Icons.edit), - onPressed: editBlueprintTitle, - ), - ], - ), + title: Text(blueprint?.title??"Edit".tr()), + actions: [ + IconButton( + icon: const Icon(Icons.edit), + tooltip: "Změnit název".tr(), + onPressed: editBlueprintTitle, + ), + ], ), body: SafeArea( child: Center( From 2c72cdd5aa236760aad3fa7eeca85fa21613fc12 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 09:02:01 +0100 Subject: [PATCH 059/159] adjust --- lib/pages/BlueprintEditorPage.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 7e670d48..5df199f6 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -9,7 +9,6 @@ import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/services/DialogHelper.dart'; import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/services/Utilities.dart'; -import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/SeatReservationWidget.dart'; From fb6e563547bd1148b90d687e36f2ebe9969aeb0b Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:16:25 +0100 Subject: [PATCH 060/159] saving groups blueprint --- lib/dataModelsEshop/BlueprintGroup.dart | 5 +++ lib/dataModelsEshop/BlueprintModel.dart | 25 ++++++++------ lib/dataModelsEshop/BlueprintObjectModel.dart | 15 ++++----- lib/dataServices/DbEshop.dart | 9 +++++ lib/pages/BlueprintEditorPage.dart | 33 +++++++------------ 5 files changed, 47 insertions(+), 40 deletions(-) diff --git a/lib/dataModelsEshop/BlueprintGroup.dart b/lib/dataModelsEshop/BlueprintGroup.dart index d0bc5845..a37edba0 100644 --- a/lib/dataModelsEshop/BlueprintGroup.dart +++ b/lib/dataModelsEshop/BlueprintGroup.dart @@ -1,20 +1,24 @@ import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; class BlueprintGroupModel { + static const String metaId = "id"; static const String metaTitle = "title"; static const String metaItems = "items"; + int? id; String? title; List? objectIds; List objects = []; BlueprintGroupModel({ + this.id, this.title, this.objectIds, }); factory BlueprintGroupModel.fromJson(Map json) { return BlueprintGroupModel( + id: json[metaId], title: json[metaTitle], objectIds: json[metaItems] != null ? List.from(json[metaItems]) @@ -23,6 +27,7 @@ class BlueprintGroupModel { } Map toJson() => { + metaId: id, metaTitle: title, metaItems: objectIds, }; diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index f908e8a7..2f565df6 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -3,6 +3,7 @@ import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; import 'BlueprintConfiguration.dart'; import 'BlueprintObjectModel.dart'; +import 'package:collection/collection.dart'; class BlueprintModel { static const String metaSpots = "spots"; @@ -14,6 +15,7 @@ class BlueprintModel { Map? data; String? title; int? organization; + int? occasion; BlueprintConfiguration? configuration; List? objects; List? spots; @@ -28,6 +30,7 @@ class BlueprintModel { data: json[TbEshop.blueprints.data], title: json[TbEshop.blueprints.title], organization: json[TbEshop.blueprints.organization], + occasion: json[TbEshop.blueprints.occasion], configuration: json[TbEshop.blueprints.configuration] != null ? BlueprintConfiguration.fromJson(json[TbEshop.blueprints.configuration]) : null, @@ -35,9 +38,9 @@ class BlueprintModel { ? List.from( json[TbEshop.blueprints.objects].map((obj) => BlueprintObjectModel.fromJson(obj))) : null, - spots: json[metaSpotType] != null + spots: json[metaSpots] != null ? List.from( - json[metaSpotType].map((spot) => BlueprintObjectModel.fromJson(spot))) + json[metaSpots].map((spot) => BlueprintObjectModel.fromJson(spot))) : null, groups: json[TbEshop.blueprints.groups] != null ? List.from( @@ -51,10 +54,11 @@ class BlueprintModel { TbEshop.blueprints.created_at: createdAt?.toIso8601String(), TbEshop.blueprints.data: data, TbEshop.blueprints.title: title, + TbEshop.blueprints.occasion: occasion, TbEshop.blueprints.organization: organization, - TbEshop.blueprints.configuration: configuration?.toJson(), - TbEshop.blueprints.objects: objects?.map((obj) => obj.toJson()), - TbEshop.blueprints.groups: groups?.map((g) => g.toJson()), + TbEshop.blueprints.configuration: configuration, + TbEshop.blueprints.objects: objects, + TbEshop.blueprints.groups: groups, }; String toBasicString() => title ?? id.toString(); @@ -64,9 +68,9 @@ class BlueprintModel { return objects!.map((object) { if (object.type == metaSpotType) { - final matchingSpot = spots?.firstWhere((spot) => spot.id == object.spot); + final matchingSpot = spots?.firstWhereOrNull((spot) => spot.id == object.id); return BlueprintObjectModel( - id: object.spot, + id: object.id, title: matchingSpot?.title ?? object.title, stateEnum: BlueprintObjectModel.States.entries .firstWhere( @@ -77,9 +81,9 @@ class BlueprintModel { ); } else if (object.type == metaTableAreaType) { return BlueprintObjectModel( - id: object.spot, // Use object spot ID or null - title: object.title, // Use object title - stateEnum: SeatState.black, // "table" type always results in black state + id: object.id, + title: object.title, + stateEnum: SeatState.black, x: object.x, y: object.y, ); @@ -95,6 +99,7 @@ class BlueprintModel { this.data, this.title, this.organization, + this.occasion, this.configuration, this.objects, this.spots, diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index ecfdff0c..46ff7d70 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -9,6 +9,7 @@ class BlueprintObjectModel { static const String metaState = "state"; static const String metaTitle = "title"; static const String metaId = "id"; + static const String metaGroupId = "group_id"; static const String soldType = "sold"; static const String selectedType = "selected"; @@ -27,18 +28,18 @@ class BlueprintObjectModel { @override toString() { - return "stůl $group, sedadlo $title"; + return "stůl ${group?.title}, sedadlo $title"; } toShortString() { - return "${group??""}${title??""}"; + return "${group?.title??""}${title??""}"; } int? x; int? y; int? id; - int? spot; + int? groupId; String? type; String? state; String? title; @@ -50,7 +51,7 @@ class BlueprintObjectModel { x: json[metaX], y: json[metaY], id: json[metaId], - spot: json[metaSpot], + groupId: json[metaGroupId], type: json[metaType], state: json[metaState], title: json[metaTitle], @@ -61,17 +62,15 @@ class BlueprintObjectModel { metaX: x, metaY: y, metaId: id, - metaSpot: spot, + metaGroupId: group?.id, metaType: type, - metaState: state, - metaTitle: title, }; BlueprintObjectModel({ this.x, this.y, this.id, - this.spot, + this.groupId, this.type, this.state, this.stateEnum, diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 0f2f6d60..68591df9 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -83,4 +83,13 @@ class DbEshop { return BlueprintModel.fromJson(response); } + + static Future updateBlueprint(BlueprintModel blueprint) async { + var json = blueprint.toJson(); + if(blueprint.id==null) { + await _supabaseEshop.from(TbEshop.blueprints.table).insert(json); + } else { + await _supabaseEshop.from(TbEshop.blueprints.table).update(json).eq(TbEshop.blueprints.id, blueprint.id!); + } + } } diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 5df199f6..a3c15c4d 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -1,6 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:fstapp/RouterService.dart'; import 'package:fstapp/components/seatReservation/widgets/SeatWidget.dart'; import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; @@ -39,7 +40,6 @@ class _BlueprintEditorPageState extends State { List changedBoxes = []; List allBoxes = []; - bool showHint = true; selectionMode currentSelectionMode = selectionMode.none; @@ -80,15 +80,14 @@ class _BlueprintEditorPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (showHint) - Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: Text( - "Pro přidávání sedadel a stolů klikněte na čtvereček v legendě (Dostupné nebo Stůl).", - style: Theme.of(context).textTheme.bodySmall, - textAlign: TextAlign.left, - ), + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Text( + "Pro přidávání sedadel a stolů klikněte na čtvereček v legendě (Dostupné nebo Stůl).", + style: Theme.of(context).textTheme.bodySmall, + textAlign: TextAlign.left, ), + ), buildLegend(), ], ), @@ -294,7 +293,6 @@ class _BlueprintEditorPageState extends State { onTap: () { setState(() { currentSelectionMode = selectionMode.addBlack; - showHint = false; // Hide hint on click }); }, ), @@ -306,7 +304,6 @@ class _BlueprintEditorPageState extends State { onTap: () { setState(() { currentSelectionMode = selectionMode.addAvailable; - showHint = false; // Hide hint on click }); }, ), @@ -491,20 +488,12 @@ class _BlueprintEditorPageState extends State { } void saveChanges() async { - var updatedObjects = changedBoxes.map((e) { - var newObject = e.objectModel ?? BlueprintObjectModel(x: e.colI, y: e.rowI); - newObject.stateEnum = e.seatState; - return newObject; - }).toList(); - - // Save changes to the backend - // DataService.updateBlueprintObjects(updatedObjects); - + await DbEshop.updateBlueprint(blueprint!); ToastHelper.Show(context, "Změny byly uloženy."); - Navigator.pop(context); + await loadData(); } - void loadData() async { + Future loadData() async { blueprint = await DbEshop.getBlueprintForEdit(widget.id!); setState(() { From 2a85a98117f0acedf7d81f4075bcc9cd8a18d30d Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:07:40 +0100 Subject: [PATCH 061/159] fixes --- lib/dataModelsEshop/BlueprintModel.dart | 5 +- lib/pages/BlueprintEditorPage.dart | 75 +++++++++++++++++-------- 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index 2f565df6..94cffba3 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -73,9 +73,8 @@ class BlueprintModel { id: object.id, title: matchingSpot?.title ?? object.title, stateEnum: BlueprintObjectModel.States.entries - .firstWhere( - (entry) => entry.value == matchingSpot?.state, - orElse: () => MapEntry(SeatState.available, BlueprintObjectModel.availableType)).key, + .firstWhereOrNull( + (entry) => entry.value == matchingSpot?.state)?.key??SeatState.available, x: object.x, y: object.y, ); diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index a3c15c4d..a8d5fa52 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -35,10 +35,7 @@ class _BlueprintEditorPageState extends State { List? currentBoxes; BlueprintGroupModel? currentGroup; - int currentWidth = 20; - int currentHeight = 20; - List changedBoxes = []; List allBoxes = []; selectionMode currentSelectionMode = selectionMode.none; @@ -83,7 +80,7 @@ class _BlueprintEditorPageState extends State { Padding( padding: const EdgeInsets.only(bottom: 16.0), child: Text( - "Pro přidávání sedadel a stolů klikněte na čtvereček v legendě (Dostupné nebo Stůl).", + "Pro přidávání sedadel a stolů klikněte na čtvereček v legendě.", style: Theme.of(context).textTheme.bodySmall, textAlign: TextAlign.left, ), @@ -99,6 +96,8 @@ class _BlueprintEditorPageState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(height: 16), + blueprint == null + ? const SizedBox.shrink() : buildDimensionControls(), const SizedBox(height: 16), Flexible( @@ -108,8 +107,8 @@ class _BlueprintEditorPageState extends State { controller: seatLayoutController, onSeatTap: handleSeatTap, stateModel: SeatLayoutStateModel( - rows: currentHeight, - cols: currentWidth, + rows: blueprint!.configuration!.height!, + cols: blueprint!.configuration!.width!, seatSize: SeatReservationWidget.boxSize, currentObjects: currentBoxes ?? [], allBoxes: allBoxes, @@ -223,7 +222,7 @@ class _BlueprintEditorPageState extends State { style: Theme.of(context).textTheme.bodySmall, ), Text( - "${group.objects.length} objektů", + "${group.objects.length}", style: Theme.of(context).textTheme.bodySmall, ), ], @@ -238,13 +237,13 @@ class _BlueprintEditorPageState extends State { } void addGroup() async { - final defaultName = "Stůl ${blueprint!.groups!.length + 1}"; + final defaultName = "${blueprint!.groups!.length + 1}"; final newTitle = await DialogHelper.showInputDialog( context: context, dialogTitle: "Přidat nový stůl", - labelText: "Název stolu", - initialValue: defaultName, // Pre-fill input with default name + labelText: "Číslo stolu", + initialValue: defaultName, ); if (newTitle != null && newTitle.isNotEmpty) { @@ -308,6 +307,17 @@ class _BlueprintEditorPageState extends State { }, ), const SizedBox(height: 8), + buildLegendItem( + "Prázdné", + SeatState.empty, + isActive: currentSelectionMode == selectionMode.emptyArea, + onTap: () { + setState(() { + currentSelectionMode = selectionMode.emptyArea; + }); + }, + ), + const SizedBox(height: 8), buildLegendItem( "Obsazené", SeatState.ordered, @@ -372,10 +382,10 @@ class _BlueprintEditorPageState extends State { children: [ buildDimensionEditor( "Šířka", - currentWidth, + blueprint!.configuration!.width!, (value) { setState(() { - currentWidth = value; + blueprint!.configuration!.width = value; }); seatLayoutController.fitLayout(); // Fit layout after width change }, @@ -383,10 +393,10 @@ class _BlueprintEditorPageState extends State { const SizedBox(width: 12), // Reduced spacing between editors buildDimensionEditor( "Výška", - currentHeight, + blueprint!.configuration!.height!, (value) { setState(() { - currentHeight = value; + blueprint!.configuration!.height = value; }); seatLayoutController.fitLayout(); // Fit layout after height change }, @@ -474,15 +484,38 @@ class _BlueprintEditorPageState extends State { void handleSeatTap(SeatModel model) { if (currentSelectionMode == selectionMode.addBlack) { model.seatState = SeatState.black; - changedBoxes.add(model); + if(model.objectModel != null && model.objectModel!.type != BlueprintModel.metaTableAreaType) { + blueprint!.objects!.remove(model.objectModel!); + } + model.objectModel = model.objectModel ?? BlueprintObjectModel(x: model.colI, y: model.rowI); + model.objectModel!.type = BlueprintModel.metaTableAreaType; + blueprint!.objects!.add(model.objectModel!); } else if (currentSelectionMode == selectionMode.addAvailable) { model.seatState = SeatState.available; + if(model.objectModel != null && model.objectModel!.type != BlueprintModel.metaSpotType) { + blueprint!.objects!.remove(model.objectModel!); + //here go through all groups and remove model.objectModel! + } model.objectModel = model.objectModel ?? BlueprintObjectModel(x: model.colI, y: model.rowI); - model.objectModel!.group = currentGroup!; - model.objectModel!.title = currentGroup!.getNextBoxName(); - currentGroup!.objects.add(model.objectModel!); - changedBoxes.add(model); + model.objectModel!.type = BlueprintModel.metaSpotType; + model.objectModel!.group = currentGroup; + model.objectModel!.title = currentGroup?.getNextBoxName(); + currentGroup?.objects.add(model.objectModel!); + blueprint!.objects!.add(model.objectModel!); ToastHelper.Show(context, "Přidáno sedadlo ${model.objectModel!.title}."); + } else if (currentSelectionMode == selectionMode.emptyArea) { + if (model.objectModel != null) { + if(model.seatState == SeatState.black){ + ToastHelper.Show(context, "Odstraněna plocha."); + } else { + ToastHelper.Show(context, "Odstraněno místo."); + } + blueprint!.objects!.remove(model.objectModel); + //here go through all groups and remove model.objectModel! + + model.objectModel = null; + model.seatState = SeatState.empty; + } } setState(() {}); } @@ -498,11 +531,9 @@ class _BlueprintEditorPageState extends State { setState(() { currentBoxes = blueprint!.toBlueprintObjects(); - currentHeight = blueprint!.configuration!.height!; - currentWidth = blueprint!.configuration!.width!; }); } } -enum selectionMode { none, addBlack, addAvailable } +enum selectionMode { none, emptyArea, addBlack, addAvailable } From 10bd27fa94e44326090da695c37ac3274f123102 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:16:48 +0100 Subject: [PATCH 062/159] check --- lib/pages/BlueprintEditorPage.dart | 112 ++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 32 deletions(-) diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index a8d5fa52..2e46b625 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -482,42 +482,90 @@ class _BlueprintEditorPageState extends State { } void handleSeatTap(SeatModel model) { - if (currentSelectionMode == selectionMode.addBlack) { - model.seatState = SeatState.black; - if(model.objectModel != null && model.objectModel!.type != BlueprintModel.metaTableAreaType) { - blueprint!.objects!.remove(model.objectModel!); + switch (currentSelectionMode) { + case selectionMode.addBlack: + _handleAddBlack(model); + break; + case selectionMode.addAvailable: + _handleAddAvailable(model); + break; + case selectionMode.emptyArea: + _handleEmptyArea(model); + break; + default: + // Do nothing for other cases + break; + } + setState(() {}); + } + + /// Handle adding a black (table area) seat. + void _handleAddBlack(SeatModel model) { + model.seatState = SeatState.black; + + // Remove existing object if it's not a table area + if (model.objectModel != null && model.objectModel!.type != BlueprintModel.metaTableAreaType) { + blueprint!.objects!.remove(model.objectModel!); + } + + // Create or update the object model + model.objectModel = model.objectModel ?? BlueprintObjectModel(x: model.colI, y: model.rowI); + model.objectModel!.type = BlueprintModel.metaTableAreaType; + blueprint!.objects!.add(model.objectModel!); + } + + /// Handle adding an available (spot) seat. + void _handleAddAvailable(SeatModel model) { + if(currentGroup == null){ + ToastHelper.Show(context, "Nejdřív vyberte/vytvořte skupinu pro přidání místa (vpravo)."); + return; + } + model.seatState = SeatState.available; + + // Remove existing object if it's not a spot + if (model.objectModel != null && model.objectModel!.type != BlueprintModel.metaSpotType) { + blueprint!.objects!.remove(model.objectModel!); + + // Remove the object from all groups + for (var group in blueprint!.groups!) { + group.objects.remove(model.objectModel); } - model.objectModel = model.objectModel ?? BlueprintObjectModel(x: model.colI, y: model.rowI); - model.objectModel!.type = BlueprintModel.metaTableAreaType; - blueprint!.objects!.add(model.objectModel!); - } else if (currentSelectionMode == selectionMode.addAvailable) { - model.seatState = SeatState.available; - if(model.objectModel != null && model.objectModel!.type != BlueprintModel.metaSpotType) { - blueprint!.objects!.remove(model.objectModel!); - //here go through all groups and remove model.objectModel! + } + + // Create or update the object model + model.objectModel = model.objectModel ?? BlueprintObjectModel(x: model.colI, y: model.rowI); + model.objectModel!.type = BlueprintModel.metaSpotType; + model.objectModel!.group = currentGroup; + model.objectModel!.title = currentGroup?.getNextBoxName(); + + // Add the object to the current group and blueprint + currentGroup?.objects.add(model.objectModel!); + blueprint!.objects!.add(model.objectModel!); + + // Notify the user + ToastHelper.Show(context, "Přidáno sedadlo ${model.objectModel!.title}."); + } + + /// Handle clearing an area (emptying a seat). + void _handleEmptyArea(SeatModel model) { + if (model.objectModel != null) { + // Determine the type of area being removed and notify the user + if (model.seatState == SeatState.black) { + ToastHelper.Show(context, "Odstraněna plocha."); + } else { + ToastHelper.Show(context, "Odstraněno místo."); } - model.objectModel = model.objectModel ?? BlueprintObjectModel(x: model.colI, y: model.rowI); - model.objectModel!.type = BlueprintModel.metaSpotType; - model.objectModel!.group = currentGroup; - model.objectModel!.title = currentGroup?.getNextBoxName(); - currentGroup?.objects.add(model.objectModel!); - blueprint!.objects!.add(model.objectModel!); - ToastHelper.Show(context, "Přidáno sedadlo ${model.objectModel!.title}."); - } else if (currentSelectionMode == selectionMode.emptyArea) { - if (model.objectModel != null) { - if(model.seatState == SeatState.black){ - ToastHelper.Show(context, "Odstraněna plocha."); - } else { - ToastHelper.Show(context, "Odstraněno místo."); - } - blueprint!.objects!.remove(model.objectModel); - //here go through all groups and remove model.objectModel! - - model.objectModel = null; - model.seatState = SeatState.empty; + + // Remove the object from blueprint and all groups + blueprint!.objects!.remove(model.objectModel); + for (var group in blueprint!.groups!) { + group.objects.remove(model.objectModel); } + + // Clear the object model and reset the seat state + model.objectModel = null; + model.seatState = SeatState.empty; } - setState(() {}); } void saveChanges() async { From e4d57fbe4be4758ffbd2213669569054db0e843b Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:51:01 +0100 Subject: [PATCH 063/159] adjust --- lib/pages/BlueprintEditorPage.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 2e46b625..561ba083 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -482,6 +482,11 @@ class _BlueprintEditorPageState extends State { } void handleSeatTap(SeatModel model) { + if (model.seatState == SeatState.ordered) { + ToastHelper.Show(context, "Místa, která byla objednána není možné měnit.", severity: ToastSeverity.NotOk); + return; + } + switch (currentSelectionMode) { case selectionMode.addBlack: _handleAddBlack(model); @@ -517,7 +522,7 @@ class _BlueprintEditorPageState extends State { /// Handle adding an available (spot) seat. void _handleAddAvailable(SeatModel model) { if(currentGroup == null){ - ToastHelper.Show(context, "Nejdřív vyberte/vytvořte skupinu pro přidání místa (vpravo)."); + ToastHelper.Show(context, "Nejdřív vyberte/vytvořte skupinu pro přidání místa (vpravo).", severity: ToastSeverity.NotOk); return; } model.seatState = SeatState.available; From 2475759c8d8649b1055da4bc558becd66d88150e Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:41:45 +0100 Subject: [PATCH 064/159] saving blueprint --- lib/dataServices/DbEshop.dart | 21 ++- lib/pages/BlueprintEditorPage.dart | 8 +- scripts/database/eshop/update_blueprint.sql | 163 ++++++++++++++++++++ 3 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 scripts/database/eshop/update_blueprint.sql diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 68591df9..a42e659d 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -6,6 +6,7 @@ import 'package:fstapp/dataModelsEshop/ProductModel.dart'; import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; import 'package:fstapp/dataServices/RightsService.dart'; +import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/services/Utilities.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -84,12 +85,20 @@ class DbEshop { return BlueprintModel.fromJson(response); } - static Future updateBlueprint(BlueprintModel blueprint) async { - var json = blueprint.toJson(); - if(blueprint.id==null) { - await _supabaseEshop.from(TbEshop.blueprints.table).insert(json); - } else { - await _supabaseEshop.from(TbEshop.blueprints.table).update(json).eq(TbEshop.blueprints.id, blueprint.id!); + static Future updateBlueprint(context, BlueprintModel blueprint) async { + final response = await _supabase.rpc( + 'update_blueprint', + params: { + 'input_data': blueprint.toJson(), + }, + ); + + var code = response["code"]; + if (code != 200) { + ToastHelper.Show(context, code.toString(), severity: ToastSeverity.NotOk); + return false; } + + return true; } } diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 561ba083..84198011 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -574,9 +574,11 @@ class _BlueprintEditorPageState extends State { } void saveChanges() async { - await DbEshop.updateBlueprint(blueprint!); - ToastHelper.Show(context, "Změny byly uloženy."); - await loadData(); + var success = await DbEshop.updateBlueprint(context, blueprint!); + if(success){ + ToastHelper.Show(context, "Změny byly uloženy."); + await loadData(); + } } Future loadData() async { diff --git a/scripts/database/eshop/update_blueprint.sql b/scripts/database/eshop/update_blueprint.sql new file mode 100644 index 00000000..df018105 --- /dev/null +++ b/scripts/database/eshop/update_blueprint.sql @@ -0,0 +1,163 @@ +CREATE OR REPLACE FUNCTION update_blueprint(input_data JSONB) +RETURNS JSONB +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + blueprint_id BIGINT; + updated_objects JSONB := '[]'::JSONB; + object_data JSONB; + spot_id BIGINT; + now TIMESTAMP WITH TIME ZONE := NOW(); + spot RECORD; + spot_ids_in_objects BIGINT[] := ARRAY[]::BIGINT[]; + existing_spot_ids BIGINT[]; + occasion_id BIGINT; + organization_id BIGINT; +BEGIN + -- Validate input data + IF input_data IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 400, 'message', 'Input data is missing'); + END IF; + + -- Get occasion ID from input data + IF input_data->>'occasion' IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 400, 'message', 'Missing occasion ID in input data'); + END IF; + + occasion_id := (input_data->>'occasion')::BIGINT; + + -- Get organization ID from input data + IF input_data->>'organization' IS NULL THEN + RETURN JSONB_BUILD_OBJECT('code', 400, 'message', 'Missing organization ID in input data'); + END IF; + + organization_id := (input_data->>'organization')::BIGINT; + + -- Get blueprint ID + blueprint_id := NULLIF(input_data->>'id', '')::BIGINT; + + IF blueprint_id IS NULL THEN + -- Create a new blueprint + INSERT INTO eshop.blueprints ( + created_at, + data, + title, + organization, + configuration, + objects, + groups, + occasion + ) + VALUES ( + now, + input_data->'data', + input_data->>'title', + organization_id, + input_data->'configuration', + '[]'::JSONB, + input_data->'groups', + occasion_id + ) + RETURNING id INTO blueprint_id; + END IF; + + -- Process objects in input_data->'objects' + IF input_data->'objects' IS NOT NULL THEN + FOR object_data IN SELECT * FROM JSONB_ARRAY_ELEMENTS(input_data->'objects') LOOP + -- Handle "spot" type objects + IF object_data->>'type' = 'spot' THEN + -- Collect spot IDs from objects + IF object_data ? 'id' AND object_data->>'id' IS NOT NULL THEN + spot_ids_in_objects := spot_ids_in_objects || (object_data->>'id')::BIGINT; + ELSE + -- Create a new spot if ID is null + INSERT INTO eshop.spots ( + created_at, + updated_at, + occasion, + blueprint, + title + ) + VALUES ( + now, + now, + occasion_id, + blueprint_id, + object_data->>'title' + ) + RETURNING id INTO spot_id; + + -- Update the object with the new spot ID + object_data := jsonb_set(object_data, '{id}', TO_JSONB(spot_id)); + spot_ids_in_objects := spot_ids_in_objects || spot_id; + END IF; + END IF; + + -- Add updated object to the updated_objects array + updated_objects := updated_objects || object_data; + END LOOP; + END IF; + + -- Get all spot IDs currently associated with the blueprint + SELECT ARRAY_AGG(id) INTO existing_spot_ids + FROM eshop.spots + WHERE blueprint = blueprint_id; + + IF existing_spot_ids IS NULL THEN + existing_spot_ids := ARRAY[]::BIGINT[]; + END IF; + + -- Identify and validate spots to delete + FOR spot_id IN SELECT unnest(existing_spot_ids) LOOP + IF NOT spot_id = ANY(spot_ids_in_objects) THEN + -- Fetch the spot + SELECT * INTO spot FROM eshop.spots WHERE id = spot_id; + + -- Check conditions for deletion + IF (spot.secret_expiration_time IS NOT NULL AND spot.secret_expiration_time < now AND spot.secret IS NOT NULL) + AND spot.order_product_ticket IS NOT NULL THEN + -- Delete the spot + DELETE FROM eshop.spots WHERE id = spot.id; + ELSE + -- Return error if the spot cannot be deleted + RETURN JSONB_BUILD_OBJECT( + 'code', 403, + 'message', 'Cannot delete spot due to invalid state', + 'spot_id', spot.id, + 'details', JSONB_BUILD_OBJECT( + 'secret', spot.secret, + 'secret_expiration_time', spot.secret_expiration_time, + 'order_product_ticket', spot.order_product_ticket + ) + ); + END IF; + END IF; + END LOOP; + + -- Update blueprint's objects field + UPDATE eshop.blueprints + SET + objects = updated_objects, + updated_at = now, + data = input_data->'data', + title = input_data->>'title', + configuration = input_data->'configuration', + groups = input_data->'groups', + occasion = occasion_id, + organization = organization_id + WHERE id = blueprint_id; + + -- Return success response + RETURN JSONB_BUILD_OBJECT( + 'code', 200, + 'message', 'Blueprint updated successfully', + 'blueprint_id', blueprint_id, + 'updated_objects', updated_objects + ); + +EXCEPTION + WHEN OTHERS THEN + RETURN JSONB_BUILD_OBJECT('code', 500, 'message', SQLERRM); +END; +$$; From b1dc24ff872ab466737d25c36fda3ed46ae30937 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:12:53 +0100 Subject: [PATCH 065/159] group adjust --- lib/dataModelsEshop/BlueprintObjectModel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index 46ff7d70..73b0c792 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -9,7 +9,7 @@ class BlueprintObjectModel { static const String metaState = "state"; static const String metaTitle = "title"; static const String metaId = "id"; - static const String metaGroupId = "group_id"; + static const String metaGroupId = "group"; static const String soldType = "sold"; static const String selectedType = "selected"; From 1a7c4895bec71b1a114f385931716da50f860b6a Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:30:45 +0100 Subject: [PATCH 066/159] quick save --- lib/dataModelsEshop/BlueprintModel.dart | 50 ++++++++++++++++++++----- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index 94cffba3..171e4d25 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -7,6 +7,7 @@ import 'package:collection/collection.dart'; class BlueprintModel { static const String metaSpots = "spots"; + static const String metaDefaultProduct = "default_product"; static const String metaTableAreaType = "table"; static const String metaSpotType = "spot"; @@ -16,12 +17,35 @@ class BlueprintModel { String? title; int? organization; int? occasion; + int? defaultProduct; BlueprintConfiguration? configuration; List? objects; List? spots; List? groups; factory BlueprintModel.fromJson(Map json) { + List? groups = json[TbEshop.blueprints.groups] != null + ? List.from( + json[TbEshop.blueprints.groups].map((g) => BlueprintGroupModel.fromJson(g))) + : []; + + List? objects = json[TbEshop.blueprints.objects] != null + ? List.from( + json[TbEshop.blueprints.objects].map((obj) => BlueprintObjectModel.fromJson(obj))) + : null; + + // Assign objects to groups + if (objects != null) { + for (var obj in objects) { + if (obj.groupId != null) { + final group = groups.firstWhereOrNull((g) => g.id == obj.groupId); + if (group != null) { + group.objects.add(obj); + } + } + } + } + return BlueprintModel( id: json[TbEshop.blueprints.id], createdAt: json[TbEshop.blueprints.created_at] != null @@ -31,24 +55,20 @@ class BlueprintModel { title: json[TbEshop.blueprints.title], organization: json[TbEshop.blueprints.organization], occasion: json[TbEshop.blueprints.occasion], + defaultProduct: json[BlueprintModel.metaDefaultProduct], configuration: json[TbEshop.blueprints.configuration] != null ? BlueprintConfiguration.fromJson(json[TbEshop.blueprints.configuration]) : null, - objects: json[TbEshop.blueprints.objects] != null + objects: objects, + spots: json[BlueprintModel.metaSpots] != null ? List.from( - json[TbEshop.blueprints.objects].map((obj) => BlueprintObjectModel.fromJson(obj))) + json[BlueprintModel.metaSpots].map((spot) => BlueprintObjectModel.fromJson(spot))) : null, - spots: json[metaSpots] != null - ? List.from( - json[metaSpots].map((spot) => BlueprintObjectModel.fromJson(spot))) - : null, - groups: json[TbEshop.blueprints.groups] != null - ? List.from( - json[TbEshop.blueprints.groups].map((g) => BlueprintGroupModel.fromJson(g))) - : [], + groups: groups, ); } + Map toJson() => { TbEshop.blueprints.id: id, TbEshop.blueprints.created_at: createdAt?.toIso8601String(), @@ -71,6 +91,7 @@ class BlueprintModel { final matchingSpot = spots?.firstWhereOrNull((spot) => spot.id == object.id); return BlueprintObjectModel( id: object.id, + product: matchingSpot?.product, title: matchingSpot?.title ?? object.title, stateEnum: BlueprintObjectModel.States.entries .firstWhereOrNull( @@ -99,9 +120,18 @@ class BlueprintModel { this.title, this.organization, this.occasion, + this.defaultProduct, this.configuration, this.objects, this.spots, this.groups, }); + + int getFirstAvailableGroupId() { + int id = 1; + while (groups?.any((group) => group.id == id)??false) { + id++; + } + return id; + } } From 4a3c5ec6ec38f9bd027a2cc2e6018fa21967b3fc Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:12:17 +0100 Subject: [PATCH 067/159] seat refactor, updating spot --- .../widgets/SeatLayoutWidget.dart | 66 ++++++++--- .../seatReservation/widgets/SeatWidget.dart | 46 +------- lib/dataModelsEshop/BlueprintModel.dart | 108 +++++++++++------- lib/dataModelsEshop/BlueprintObjectModel.dart | 17 +-- lib/dataModelsEshop/ProductModel.dart | 1 + lib/dataServices/DbEshop.dart | 22 +++- lib/pages/BlueprintEditorPage.dart | 28 +++-- lib/widgets/SeatReservationWidget.dart | 10 +- scripts/database/eshop/update_blueprint.sql | 52 +++++++-- 9 files changed, 216 insertions(+), 134 deletions(-) diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index 253f7c52..5cfa989a 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -24,6 +24,7 @@ class SeatLayoutWidget extends StatefulWidget { class _SeatLayoutWidgetState extends State { final TransformationController _controller = TransformationController(); + late List _seats; @override void initState() { @@ -31,11 +32,33 @@ class _SeatLayoutWidgetState extends State { widget.controller?.attachFitLayout(_fitLayout); + _seats = _generateSeatModels(); + WidgetsBinding.instance.addPostFrameCallback((_) { _fitLayout(); }); } + List _generateSeatModels() { + final List seats = []; + for (int row = 0; row < widget.stateModel.rows; row++) { + for (int col = 0; col < widget.stateModel.cols; col++) { + var boxModel = widget.stateModel.currentObjects + .firstWhereOrNull((b) => b.x == col && b.y == row); + seats.add( + SeatModel( + objectModel: boxModel, + seatState: boxModel?.stateEnum ?? SeatState.empty, + rowI: row, + colI: col, + seatSize: widget.stateModel.seatSize, + ), + ); + } + } + return seats; + } + void _fitLayout() { if (!mounted) return; @@ -75,13 +98,21 @@ class _SeatLayoutWidgetState extends State { return Row( mainAxisSize: MainAxisSize.min, children: List.generate(widget.stateModel.cols, (colI) { - final seatModel = _createSeat(colI, rowI); + final seatModel = + _seats.firstWhere((seat) => seat.rowI == rowI && seat.colI == colI); return Tooltip( showDuration: const Duration(seconds: 0), message: seatModel.objectModel?.title ?? "", - child: SeatWidget( - model: seatModel, - onSeatTap: widget.onSeatTap, + child: GestureDetector( + onTap: () { + if (widget.onSeatTap != null) { + widget.onSeatTap!(seatModel); + } + }, + child: SeatWidgetHelper.buildSeat( + state: seatModel.seatState, + size: seatModel.seatSize.toDouble(), + ), ), ); }), @@ -91,16 +122,23 @@ class _SeatLayoutWidgetState extends State { ); } - SeatModel _createSeat(int colI, int rowI) { - var boxModel = widget.stateModel.currentObjects - .firstWhereOrNull((b) => b.x == colI && b.y == rowI); - return SeatModel( - objectModel: boxModel, - seatState: boxModel?.stateEnum ?? SeatState.empty, - rowI: rowI, - colI: colI, - seatSize: widget.stateModel.seatSize, - ); + @override + void didUpdateWidget(covariant SeatLayoutWidget oldWidget) { + super.didUpdateWidget(oldWidget); + + // Check if rows or columns have changed + if (widget.stateModel.rows != oldWidget.stateModel.rows || + widget.stateModel.cols != oldWidget.stateModel.cols || + !const DeepCollectionEquality().equals( + widget.stateModel.currentObjects, oldWidget.stateModel.currentObjects)) { + setState(() { + _seats = _generateSeatModels(); + }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + _fitLayout(); + }); + } } } diff --git a/lib/components/seatReservation/widgets/SeatWidget.dart b/lib/components/seatReservation/widgets/SeatWidget.dart index 3228ccd9..cc485ab9 100644 --- a/lib/components/seatReservation/widgets/SeatWidget.dart +++ b/lib/components/seatReservation/widgets/SeatWidget.dart @@ -1,29 +1,16 @@ import 'package:flutter/material.dart'; -import '../model/SeatModel.dart'; import '../utils/SeatState.dart'; -class SeatWidget extends StatefulWidget { - final SeatModel model; - final void Function(SeatModel model)? onSeatTap; +class SeatWidgetHelper { + static const double padding = 2.0; - const SeatWidget({ - Key? key, - required this.model, - this.onSeatTap, - }) : super(key: key); - - @override - State createState() => _SeatWidgetState(); - - static double padding = 2.0; /// Static method to create a seat widget with a given seat state and size. /// This allows external calls to render a seat without relying on the `SeatModel`. static Widget buildSeat({ required SeatState state, - double size = 40.0 + double size = 40.0, }) { - // Adjust padding for sold, selected, and available states final bool hasPadding = state == SeatState.ordered || state == SeatState.selected || state == SeatState.available; @@ -61,30 +48,3 @@ class SeatWidget extends StatefulWidget { } } -class _SeatWidgetState extends State { - late SeatState seatState; - - @override - void initState() { - super.initState(); - seatState = widget.model.seatState; - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - if (widget.onSeatTap != null) { - widget.onSeatTap!(widget.model); - setState(() { - seatState = widget.model.seatState; - }); - } - }, - child: SeatWidget.buildSeat( - state: seatState, - size: widget.model.seatSize.toDouble(), - ), - ); - } -} diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index 171e4d25..6674628c 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -24,27 +24,13 @@ class BlueprintModel { List? groups; factory BlueprintModel.fromJson(Map json) { - List? groups = json[TbEshop.blueprints.groups] != null - ? List.from( - json[TbEshop.blueprints.groups].map((g) => BlueprintGroupModel.fromJson(g))) - : []; + final List groups = _parseGroups(json); + final List? rawObjects = _parseObjects(json); + final List? spots = _parseSpots(json); - List? objects = json[TbEshop.blueprints.objects] != null - ? List.from( - json[TbEshop.blueprints.objects].map((obj) => BlueprintObjectModel.fromJson(obj))) - : null; + final List? enrichedObjects = _enrichObjects(rawObjects, spots); - // Assign objects to groups - if (objects != null) { - for (var obj in objects) { - if (obj.groupId != null) { - final group = groups.firstWhereOrNull((g) => g.id == obj.groupId); - if (group != null) { - group.objects.add(obj); - } - } - } - } + _assignObjectsToGroups(enrichedObjects, groups); return BlueprintModel( id: json[TbEshop.blueprints.id], @@ -59,60 +45,98 @@ class BlueprintModel { configuration: json[TbEshop.blueprints.configuration] != null ? BlueprintConfiguration.fromJson(json[TbEshop.blueprints.configuration]) : null, - objects: objects, - spots: json[BlueprintModel.metaSpots] != null - ? List.from( - json[BlueprintModel.metaSpots].map((spot) => BlueprintObjectModel.fromJson(spot))) - : null, + objects: enrichedObjects, groups: groups, ); } + // Helper Methods + static List _parseGroups(Map json) { + return json[TbEshop.blueprints.groups] != null + ? List.from( + json[TbEshop.blueprints.groups].map((g) => BlueprintGroupModel.fromJson(g))) + : []; + } - Map toJson() => { - TbEshop.blueprints.id: id, - TbEshop.blueprints.created_at: createdAt?.toIso8601String(), - TbEshop.blueprints.data: data, - TbEshop.blueprints.title: title, - TbEshop.blueprints.occasion: occasion, - TbEshop.blueprints.organization: organization, - TbEshop.blueprints.configuration: configuration, - TbEshop.blueprints.objects: objects, - TbEshop.blueprints.groups: groups, - }; + static List? _parseObjects(Map json) { + return json[TbEshop.blueprints.objects] != null + ? List.from( + json[TbEshop.blueprints.objects].map((obj) => BlueprintObjectModel.fromJson(obj))) + : null; + } - String toBasicString() => title ?? id.toString(); + static List? _parseSpots(Map json) { + return json[BlueprintModel.metaSpots] != null + ? List.from( + json[BlueprintModel.metaSpots].map((spot) => BlueprintObjectModel.fromJson(spot))) + : null; + } - List toBlueprintObjects() { - if (objects == null) return []; + static List? _enrichObjects( + List? rawObjects, List? spots) { + if (rawObjects == null) return null; - return objects!.map((object) { + return rawObjects.map((object) { if (object.type == metaSpotType) { final matchingSpot = spots?.firstWhereOrNull((spot) => spot.id == object.id); return BlueprintObjectModel( id: object.id, + type: object.type, product: matchingSpot?.product, + groupId: object.groupId, title: matchingSpot?.title ?? object.title, stateEnum: BlueprintObjectModel.States.entries .firstWhereOrNull( - (entry) => entry.value == matchingSpot?.state)?.key??SeatState.available, + (entry) => entry.value == matchingSpot?.state)?.key ?? + SeatState.available, x: object.x, y: object.y, ); } else if (object.type == metaTableAreaType) { return BlueprintObjectModel( id: object.id, + type: object.type, title: object.title, stateEnum: SeatState.black, x: object.x, y: object.y, ); } - // If the object type is neither "spot" nor "table", skip it - return null; + return null; // Skip unrecognized object types }).whereType().toList(); // Filter out null values } + static void _assignObjectsToGroups( + List? objects, List? groups) { + // Assign objects to groups + if (objects != null) { + for (var obj in objects) { + if (obj.groupId != null) { + final group = groups?.firstWhereOrNull((g) => g.id == obj.groupId); + if (group != null) { + group.objects.add(obj); + } + } + } + } + } + + + Map toJson() => { + TbEshop.blueprints.id: id, + TbEshop.blueprints.created_at: createdAt?.toIso8601String(), + TbEshop.blueprints.data: data, + TbEshop.blueprints.title: title, + TbEshop.blueprints.occasion: occasion, + TbEshop.blueprints.organization: organization, + TbEshop.blueprints.configuration: configuration, + TbEshop.blueprints.objects: objects, + TbEshop.blueprints.groups: groups, + }; + + String toBasicString() => title ?? id.toString(); + + BlueprintModel({ this.id, this.createdAt, diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index 73b0c792..bf524662 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -10,6 +10,7 @@ class BlueprintObjectModel { static const String metaTitle = "title"; static const String metaId = "id"; static const String metaGroupId = "group"; + static const String metaProduct = "product"; static const String soldType = "sold"; static const String selectedType = "selected"; @@ -26,14 +27,12 @@ class BlueprintObjectModel { }; @override - toString() - { + toString() { return "stůl ${group?.title}, sedadlo $title"; } - toShortString() - { - return "${group?.title??""}${title??""}"; + toShortString() { + return "${group?.title ?? ""}${title ?? ""}"; } int? x; @@ -43,6 +42,7 @@ class BlueprintObjectModel { String? type; String? state; String? title; + int? product; BlueprintGroupModel? group; SeatState? stateEnum; @@ -55,15 +55,17 @@ class BlueprintObjectModel { type: json[metaType], state: json[metaState], title: json[metaTitle], + product: json[metaProduct], ); } Map toJson() => { metaX: x, metaY: y, - metaId: id, - metaGroupId: group?.id, metaType: type, + if (id != null) metaId: id, + if (group?.id != null) metaGroupId: group?.id, + if (product != null) metaProduct: product, }; BlueprintObjectModel({ @@ -75,5 +77,6 @@ class BlueprintObjectModel { this.state, this.stateEnum, this.title, + this.product, }); } diff --git a/lib/dataModelsEshop/ProductModel.dart b/lib/dataModelsEshop/ProductModel.dart index 0fec58ed..3c4c3b83 100644 --- a/lib/dataModelsEshop/ProductModel.dart +++ b/lib/dataModelsEshop/ProductModel.dart @@ -14,6 +14,7 @@ class ProductModel { static const String foodType = "food"; static const String taxiType = "taxi"; + static const String spotType = "spot"; factory ProductModel.fromJson(Map json) { return ProductModel( diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index a42e659d..699c7e2f 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -5,7 +5,6 @@ import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; import 'package:fstapp/dataModelsEshop/ProductModel.dart'; import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; -import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/services/Utilities.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -72,24 +71,39 @@ class DbEshop { return BlueprintModel.fromJson(response["data"]); } - static Future getBlueprintForEdit(int blueprintId) async { + static Future getBlueprintForEdit(int blueprintId, int occasion) async { final response = await _supabaseEshop .from(TbEshop.blueprints.table) .select() .eq(TbEshop.blueprints.id, blueprintId) .single(); - var spots = await _supabaseEshop.from(TbEshop.spots.table).select().eq(TbEshop.spots.occasion, RightsService.currentOccasion!); + + var spots = await _supabaseEshop.from(TbEshop.spots.table).select().eq(TbEshop.spots.occasion, occasion); response[BlueprintModel.metaSpots] = spots; + var defaultProducts = await _supabaseEshop + .from(TbEshop.product_types.table) + .select( + "${TbEshop.product_types.id}," + "${TbEshop.product_types.type}," + "${TbEshop.product_types.title}," + "${TbEshop.products.table}(${TbEshop.products.id},${TbEshop.products.title},${TbEshop.products.price})" + ) + .eq(TbEshop.product_types.occasion, occasion) + .eq(TbEshop.product_types.type, ProductModel.spotType); + var x = defaultProducts.map((x) => ProductTypeModel.fromJson(x)).first.products!.first; + + response[BlueprintModel.metaDefaultProduct] = x.id; return BlueprintModel.fromJson(response); } static Future updateBlueprint(context, BlueprintModel blueprint) async { + var json = blueprint.toJson(); final response = await _supabase.rpc( 'update_blueprint', params: { - 'input_data': blueprint.toJson(), + 'input_data': json, }, ); diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 84198011..f508852b 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -7,6 +7,7 @@ import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/services/DialogHelper.dart'; import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/services/Utilities.dart'; @@ -110,7 +111,7 @@ class _BlueprintEditorPageState extends State { rows: blueprint!.configuration!.height!, cols: blueprint!.configuration!.width!, seatSize: SeatReservationWidget.boxSize, - currentObjects: currentBoxes ?? [], + currentObjects: blueprint!.objects!, allBoxes: allBoxes, ), ), @@ -189,7 +190,7 @@ class _BlueprintEditorPageState extends State { ], ), const SizedBox(height: 8), - Container( + SizedBox( height: 120, // Limit height for the group list child: ListView.builder( itemCount: blueprint!.groups!.length, @@ -248,7 +249,7 @@ class _BlueprintEditorPageState extends State { if (newTitle != null && newTitle.isNotEmpty) { setState(() { - blueprint!.groups!.add(BlueprintGroupModel(title: newTitle)); + blueprint!.groups!.add(BlueprintGroupModel(title: newTitle, id: blueprint!.getFirstAvailableGroupId())); blueprint!.groups!.sort((a, b) => Utilities.naturalCompare(a.title!, b.title!)); }); } @@ -357,7 +358,7 @@ class _BlueprintEditorPageState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - SeatWidget.buildSeat( + SeatWidgetHelper.buildSeat( state: state, size: SeatReservationWidget.boxSize.toDouble(), ), @@ -508,6 +509,9 @@ class _BlueprintEditorPageState extends State { void _handleAddBlack(SeatModel model) { model.seatState = SeatState.black; + if (model.objectModel != null && model.objectModel!.type == BlueprintModel.metaTableAreaType) { + return; + } // Remove existing object if it's not a table area if (model.objectModel != null && model.objectModel!.type != BlueprintModel.metaTableAreaType) { blueprint!.objects!.remove(model.objectModel!); @@ -525,21 +529,21 @@ class _BlueprintEditorPageState extends State { ToastHelper.Show(context, "Nejdřív vyberte/vytvořte skupinu pro přidání místa (vpravo).", severity: ToastSeverity.NotOk); return; } + model.seatState = SeatState.available; - // Remove existing object if it's not a spot + if (model.objectModel != null && model.objectModel!.type == BlueprintModel.metaSpotType) { + return; + } + if (model.objectModel != null && model.objectModel!.type != BlueprintModel.metaSpotType) { blueprint!.objects!.remove(model.objectModel!); - - // Remove the object from all groups - for (var group in blueprint!.groups!) { - group.objects.remove(model.objectModel); - } } // Create or update the object model model.objectModel = model.objectModel ?? BlueprintObjectModel(x: model.colI, y: model.rowI); model.objectModel!.type = BlueprintModel.metaSpotType; + model.objectModel!.product = blueprint!.defaultProduct; model.objectModel!.group = currentGroup; model.objectModel!.title = currentGroup?.getNextBoxName(); @@ -582,10 +586,10 @@ class _BlueprintEditorPageState extends State { } Future loadData() async { - blueprint = await DbEshop.getBlueprintForEdit(widget.id!); + blueprint = await DbEshop.getBlueprintForEdit(widget.id!, RightsService.currentOccasion!); setState(() { - currentBoxes = blueprint!.toBlueprintObjects(); + currentBoxes = blueprint!.objects; }); } } diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index df5502b0..5accb61b 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -66,7 +66,7 @@ class _SeatReservationWidgetState extends State { Row( mainAxisSize: MainAxisSize.min, children: [ - SeatWidget.buildSeat( + SeatWidgetHelper.buildSeat( state: SeatState.ordered, size: 15.0, ), @@ -77,7 +77,7 @@ class _SeatReservationWidgetState extends State { Row( mainAxisSize: MainAxisSize.min, children: [ - SeatWidget.buildSeat( + SeatWidgetHelper.buildSeat( state: SeatState.available, size: SeatReservationWidget.boxSize.toDouble(), ), @@ -88,7 +88,7 @@ class _SeatReservationWidgetState extends State { Row( mainAxisSize: MainAxisSize.min, children: [ - SeatWidget.buildSeat( + SeatWidgetHelper.buildSeat( state: SeatState.selected, size: SeatReservationWidget.boxSize.toDouble(), ), @@ -99,7 +99,7 @@ class _SeatReservationWidgetState extends State { Row( mainAxisSize: MainAxisSize.min, children: [ - SeatWidget.buildSeat( + SeatWidgetHelper.buildSeat( state: SeatState.black, size: 15.0, ), @@ -173,7 +173,7 @@ class _SeatReservationWidgetState extends State { } setState(() { - currentObjects = blueprint!.toBlueprintObjects(); + currentObjects = blueprint!.objects; currentHeight = blueprint!.configuration!.height!; currentWidth = blueprint!.configuration!.width!; }); diff --git a/scripts/database/eshop/update_blueprint.sql b/scripts/database/eshop/update_blueprint.sql index df018105..be74db05 100644 --- a/scripts/database/eshop/update_blueprint.sql +++ b/scripts/database/eshop/update_blueprint.sql @@ -14,6 +14,7 @@ DECLARE existing_spot_ids BIGINT[]; occasion_id BIGINT; organization_id BIGINT; + product_id BIGINT; BEGIN -- Validate input data IF input_data IS NULL THEN @@ -67,9 +68,41 @@ BEGIN FOR object_data IN SELECT * FROM JSONB_ARRAY_ELEMENTS(input_data->'objects') LOOP -- Handle "spot" type objects IF object_data->>'type' = 'spot' THEN + -- Validate product_id + product_id := NULL; + IF object_data ? 'product' AND object_data->>'product' IS NOT NULL THEN + product_id := (object_data->>'product')::BIGINT; + + -- Ensure the product exists in eshop.products + IF NOT EXISTS ( + SELECT 1 + FROM eshop.products + WHERE id = product_id AND occasion = occasion_id + ) THEN + product_id := NULL; + END IF; + END IF; + + -- Return error for invalid or missing product_id + IF product_id IS NULL THEN + RETURN JSONB_BUILD_OBJECT( + 'code', 400, + 'message', 'Invalid or missing product. Ensure the product exists and is part of the occasion.', + 'details', JSONB_BUILD_OBJECT( + 'product_id', NULL, + 'object', object_data + ) + ); + END IF; + -- Collect spot IDs from objects IF object_data ? 'id' AND object_data->>'id' IS NOT NULL THEN spot_ids_in_objects := spot_ids_in_objects || (object_data->>'id')::BIGINT; + + -- Update the spot's product if applicable + UPDATE eshop.spots + SET product = product_id, updated_at = now + WHERE id = (object_data->>'id')::BIGINT; ELSE -- Create a new spot if ID is null INSERT INTO eshop.spots ( @@ -77,20 +110,24 @@ BEGIN updated_at, occasion, blueprint, - title + title, + product ) VALUES ( now, now, occasion_id, blueprint_id, - object_data->>'title' + object_data->>'title', + product_id ) RETURNING id INTO spot_id; + -- Append the new spot ID to the list + spot_ids_in_objects := spot_ids_in_objects || spot_id; + -- Update the object with the new spot ID object_data := jsonb_set(object_data, '{id}', TO_JSONB(spot_id)); - spot_ids_in_objects := spot_ids_in_objects || spot_id; END IF; END IF; @@ -115,17 +152,18 @@ BEGIN SELECT * INTO spot FROM eshop.spots WHERE id = spot_id; -- Check conditions for deletion - IF (spot.secret_expiration_time IS NOT NULL AND spot.secret_expiration_time < now AND spot.secret IS NOT NULL) - AND spot.order_product_ticket IS NOT NULL THEN - -- Delete the spot + IF (spot.secret_expiration_time IS NULL OR spot.secret_expiration_time < now OR spot.secret IS NULL) + AND spot.order_product_ticket IS NULL THEN + -- Safe to delete DELETE FROM eshop.spots WHERE id = spot.id; ELSE - -- Return error if the spot cannot be deleted + -- Spot cannot be deleted, return error RETURN JSONB_BUILD_OBJECT( 'code', 403, 'message', 'Cannot delete spot due to invalid state', 'spot_id', spot.id, 'details', JSONB_BUILD_OBJECT( + 'x', spot_ids_in_objects 'secret', spot.secret, 'secret_expiration_time', spot.secret_expiration_time, 'order_product_ticket', spot.order_product_ticket From 276664955ca26583ff0dbb594c7b2bc44d1058ed Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:42:52 +0100 Subject: [PATCH 068/159] spot_title --- lib/dataModelsEshop/BlueprintObjectModel.dart | 1 + scripts/database/eshop/update_blueprint.sql | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index bf524662..ccd9aebb 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -63,6 +63,7 @@ class BlueprintObjectModel { metaX: x, metaY: y, metaType: type, + metaTitle: title, if (id != null) metaId: id, if (group?.id != null) metaGroupId: group?.id, if (product != null) metaProduct: product, diff --git a/scripts/database/eshop/update_blueprint.sql b/scripts/database/eshop/update_blueprint.sql index be74db05..8069aa86 100644 --- a/scripts/database/eshop/update_blueprint.sql +++ b/scripts/database/eshop/update_blueprint.sql @@ -15,6 +15,7 @@ DECLARE occasion_id BIGINT; organization_id BIGINT; product_id BIGINT; + spot_title TEXT; -- Updated variable name for title BEGIN -- Validate input data IF input_data IS NULL THEN @@ -68,6 +69,9 @@ BEGIN FOR object_data IN SELECT * FROM JSONB_ARRAY_ELEMENTS(input_data->'objects') LOOP -- Handle "spot" type objects IF object_data->>'type' = 'spot' THEN + -- Extract spot title + spot_title := object_data->>'title'; + -- Validate product_id product_id := NULL; IF object_data ? 'product' AND object_data->>'product' IS NOT NULL THEN @@ -99,9 +103,12 @@ BEGIN IF object_data ? 'id' AND object_data->>'id' IS NOT NULL THEN spot_ids_in_objects := spot_ids_in_objects || (object_data->>'id')::BIGINT; - -- Update the spot's product if applicable + -- Update the spot's product and title if applicable UPDATE eshop.spots - SET product = product_id, updated_at = now + SET + product = product_id, + title = spot_title, -- Save the extracted spot title + updated_at = now WHERE id = (object_data->>'id')::BIGINT; ELSE -- Create a new spot if ID is null @@ -110,7 +117,7 @@ BEGIN updated_at, occasion, blueprint, - title, + title, -- Save the extracted spot title product ) VALUES ( @@ -118,7 +125,7 @@ BEGIN now, occasion_id, blueprint_id, - object_data->>'title', + spot_title, product_id ) RETURNING id INTO spot_id; @@ -129,6 +136,9 @@ BEGIN -- Update the object with the new spot ID object_data := jsonb_set(object_data, '{id}', TO_JSONB(spot_id)); END IF; + + -- Remove title and product from the object before adding to updated_objects + object_data := object_data - 'title' - 'product'; END IF; -- Add updated object to the updated_objects array @@ -163,7 +173,6 @@ BEGIN 'message', 'Cannot delete spot due to invalid state', 'spot_id', spot.id, 'details', JSONB_BUILD_OBJECT( - 'x', spot_ids_in_objects 'secret', spot.secret, 'secret_expiration_time', spot.secret_expiration_time, 'order_product_ticket', spot.order_product_ticket From aac7125ac51c2d95a44f9e6445734b1d6fbad3ae Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:48:58 +0100 Subject: [PATCH 069/159] design adjust --- lib/pages/BlueprintEditorPage.dart | 50 +++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index f508852b..857655e9 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -158,14 +158,14 @@ class _BlueprintEditorPageState extends State { Widget buildGroupsSection() { return blueprint == null - ? const Center(child: CircularProgressIndicator()) : - Column( + ? const Center(child: CircularProgressIndicator()) + : Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "Stoly (Skupiny):", + "Skupiny (Stoly):", style: Theme.of(context).textTheme.titleMedium, ), Row( @@ -190,41 +190,61 @@ class _BlueprintEditorPageState extends State { ], ), const SizedBox(height: 8), - SizedBox( - height: 120, // Limit height for the group list + Expanded( child: ListView.builder( itemCount: blueprint!.groups!.length, itemBuilder: (context, index) { final group = blueprint!.groups![index]; + final isSelected = currentGroup == group; return GestureDetector( onTap: () { setState(() { currentGroup = group; }); }, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + margin: const EdgeInsets.symmetric(vertical: 4), + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 12), decoration: BoxDecoration( - color: currentGroup == group - ? Theme.of(context).colorScheme.primary.withOpacity(0.1) + color: isSelected + ? Theme.of(context) + .colorScheme + .primary + .withOpacity(0.2) : Colors.transparent, border: Border.all( - color: currentGroup == group + color: isSelected ? Theme.of(context).colorScheme.primary : Colors.grey, + width: isSelected ? 2 : 1, ), - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( group.title!, - style: Theme.of(context).textTheme.bodySmall, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: isSelected + ? Theme.of(context).colorScheme.primary + : null, + fontWeight: + isSelected ? FontWeight.bold : null, + ), ), - Text( - "${group.objects.length}", - style: Theme.of(context).textTheme.bodySmall, + Row( + children: [ + Text( + "${group.objects.length}", + style: Theme.of(context).textTheme.bodySmall, + ), + ], ), ], ), From d71ec3e3f9dbe13f8e4716b69ca9bcd9cf983467 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:16:33 +0100 Subject: [PATCH 070/159] selection done right --- lib/widgets/SeatReservationWidget.dart | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index 5accb61b..e0744d2a 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -39,12 +39,8 @@ class _SeatReservationWidgetState extends State { loadData(); } - List? currentObjects; BlueprintGroupModel? currentBoxGroup; - int currentWidth = 20; - int currentHeight = 20; - @override Widget build(BuildContext context) { return Scaffold( @@ -125,14 +121,18 @@ class _SeatReservationWidgetState extends State { return; } + if(selectedSeat != null) { + selectedSeat?.seatState = SeatState.available; + } model.seatState = SeatState.selected; selectedSeat = model; + setState(() {}); }, stateModel: SeatLayoutStateModel( - rows: currentHeight, - cols: currentWidth, + rows: blueprint!.configuration!.height!, + cols: blueprint!.configuration!.width!, seatSize: SeatReservationWidget.boxSize, - currentObjects: currentObjects ?? [], + currentObjects: blueprint!.objects!, allBoxes: allObjects, ), ), @@ -172,11 +172,7 @@ class _SeatReservationWidgetState extends State { return; } - setState(() { - currentObjects = blueprint!.objects; - currentHeight = blueprint!.configuration!.height!; - currentWidth = blueprint!.configuration!.width!; - }); + setState(() {}); } } From 1b546dc93205c1fbba142401b30e07853a9acafa Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:55:49 +0100 Subject: [PATCH 071/159] adjustments --- lib/components/seatReservation/widgets/SeatWidget.dart | 3 +++ lib/dataModelsEshop/BlueprintGroup.dart | 3 +-- lib/dataModelsEshop/BlueprintObjectModel.dart | 4 ++-- lib/services/FormHelper.dart | 5 +++-- lib/widgets/SeatReservationWidget.dart | 8 +------- scripts/database/eshop/get_blueprint.sql | 4 ++-- 6 files changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/components/seatReservation/widgets/SeatWidget.dart b/lib/components/seatReservation/widgets/SeatWidget.dart index cc485ab9..5af94eca 100644 --- a/lib/components/seatReservation/widgets/SeatWidget.dart +++ b/lib/components/seatReservation/widgets/SeatWidget.dart @@ -13,6 +13,7 @@ class SeatWidgetHelper { }) { final bool hasPadding = state == SeatState.ordered || state == SeatState.selected || + state == SeatState.selected_by_me || state == SeatState.available; return Container( @@ -34,6 +35,8 @@ class SeatWidgetHelper { switch (state) { case SeatState.available: return Colors.green; + case SeatState.selected_by_me: + return Colors.blue; case SeatState.selected: return Colors.blue; case SeatState.black: diff --git a/lib/dataModelsEshop/BlueprintGroup.dart b/lib/dataModelsEshop/BlueprintGroup.dart index a37edba0..b2bf2bcc 100644 --- a/lib/dataModelsEshop/BlueprintGroup.dart +++ b/lib/dataModelsEshop/BlueprintGroup.dart @@ -28,8 +28,7 @@ class BlueprintGroupModel { Map toJson() => { metaId: id, - metaTitle: title, - metaItems: objectIds, + metaTitle: title }; List alphabet = [ diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index ccd9aebb..a6d4ce29 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -12,7 +12,7 @@ class BlueprintObjectModel { static const String metaGroupId = "group"; static const String metaProduct = "product"; - static const String soldType = "sold"; + static const String orderedType = "ordered"; static const String selectedType = "selected"; static const String selectedByMeType = "selected_by_me"; static const String blackType = "black"; @@ -23,7 +23,7 @@ class BlueprintObjectModel { SeatState.available: availableType, SeatState.selected: selectedType, SeatState.selected_by_me: selectedByMeType, - SeatState.ordered: soldType, + SeatState.ordered: orderedType, }; @override diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index cdd9e12e..495568c2 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -1,4 +1,5 @@ import 'package:easy_localization/easy_localization.dart'; +import 'package:fstapp/components/seatReservation/model/SeatModel.dart'; import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataModels/FormOptionModel.dart'; import 'package:fstapp/dataModels/UserInfoModel.dart'; @@ -264,7 +265,7 @@ class FormHelper { FormBuilderValidators.required(), ]), onTap: () async { - await showGeneralDialog( + SeatModel? selectedSeat = await showGeneralDialog( context: context, barrierColor: Colors.black12.withOpacity(0.6), // Background color barrierDismissible: false, @@ -274,7 +275,7 @@ class FormHelper { return SeatReservationWidget(secret: secret, formKey: form.formKey!, blueprintId: form.blueprint!); }, ); - //_formKey.currentState?.fields[TicketModel.boxColumn]!.didChange(selectedSeats.firstOrNull?.toString()); + (FormBuilder.of(context)?.fields[name])?.didChange(selectedSeat?.objectModel?.title??"---"); }, ); } diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index e0744d2a..6779520c 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -151,7 +151,7 @@ class _SeatReservationWidgetState extends State { ElevatedButton( onPressed: () { // Save changes logic - Navigator.pop(context); + Navigator.pop(context, selectedSeat); }, child: const Text("Save").tr(), ), @@ -175,9 +175,3 @@ class _SeatReservationWidgetState extends State { setState(() {}); } } - -enum selectionMode { - normal, - addBlack, - addAvailable, -} diff --git a/scripts/database/eshop/get_blueprint.sql b/scripts/database/eshop/get_blueprint.sql index 68b25181..bae95607 100644 --- a/scripts/database/eshop/get_blueprint.sql +++ b/scripts/database/eshop/get_blueprint.sql @@ -48,12 +48,12 @@ BEGIN -- Extract valid spot IDs from blueprint.objects WITH valid_spots AS ( - SELECT (obj->>'spot')::BIGINT AS spot_id + SELECT (obj->>'id')::BIGINT AS spot_id FROM jsonb_array_elements( (SELECT b.objects FROM eshop.blueprints b WHERE b.id = blueprint_id) ) obj WHERE obj->>'type' = 'spot' - AND obj->>'spot' IS NOT NULL + AND obj->>'id' IS NOT NULL ) -- Fetch enriched spots data SELECT jsonb_agg(jsonb_build_object( From 09cd0c2d59486cf4c0865e1322135061eef6cc80 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:23:20 +0100 Subject: [PATCH 072/159] adjustments --- .../seatReservation/widgets/SeatWidget.dart | 2 +- lib/widgets/SeatReservationWidget.dart | 15 ++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/components/seatReservation/widgets/SeatWidget.dart b/lib/components/seatReservation/widgets/SeatWidget.dart index 5af94eca..de67c463 100644 --- a/lib/components/seatReservation/widgets/SeatWidget.dart +++ b/lib/components/seatReservation/widgets/SeatWidget.dart @@ -42,7 +42,7 @@ class SeatWidgetHelper { case SeatState.black: return Colors.black; case SeatState.ordered: - return Colors.red; + return Colors.black54; case SeatState.empty: return Colors.grey.shade300; default: diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index 6779520c..67b39a87 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -116,16 +116,13 @@ class _SeatReservationWidgetState extends State { if (model.seatState == SeatState.selected) { model.seatState = SeatState.available; selectedSeat = null; - return; - } else if (model.seatState != SeatState.available) { - return; + } else if (model.seatState == SeatState.available) { + if (selectedSeat != null) { + selectedSeat!.seatState = SeatState.available; + } + model.seatState = SeatState.selected; + selectedSeat = model; } - - if(selectedSeat != null) { - selectedSeat?.seatState = SeatState.available; - } - model.seatState = SeatState.selected; - selectedSeat = model; setState(() {}); }, stateModel: SeatLayoutStateModel( From 3814e43d3666200695868f2dc7fa85741f89e6e0 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:33:25 +0100 Subject: [PATCH 073/159] design adjustments --- lib/components/seatReservation/widgets/SeatWidget.dart | 9 +++++---- lib/pages/BlueprintEditorPage.dart | 2 +- lib/widgets/SeatReservationWidget.dart | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/components/seatReservation/widgets/SeatWidget.dart b/lib/components/seatReservation/widgets/SeatWidget.dart index de67c463..d2db7965 100644 --- a/lib/components/seatReservation/widgets/SeatWidget.dart +++ b/lib/components/seatReservation/widgets/SeatWidget.dart @@ -4,6 +4,7 @@ import '../utils/SeatState.dart'; class SeatWidgetHelper { static const double padding = 2.0; + static const double selectedByMePadding = 1.0; /// Static method to create a seat widget with a given seat state and size. /// This allows external calls to render a seat without relying on the `SeatModel`. @@ -21,7 +22,7 @@ class SeatWidgetHelper { height: size, width: size, child: Container( - margin: hasPadding ? EdgeInsets.all(padding) : EdgeInsets.zero, + margin: hasPadding ? EdgeInsets.all(state == SeatState.selected_by_me ? selectedByMePadding : padding) : EdgeInsets.zero, decoration: BoxDecoration( color: _getSeatColor(state), borderRadius: BorderRadius.circular(hasPadding ? padding : 0.0), @@ -34,11 +35,11 @@ class SeatWidgetHelper { static Color _getSeatColor(SeatState state) { switch (state) { case SeatState.available: - return Colors.green; + return Colors.blueAccent; case SeatState.selected_by_me: - return Colors.blue; + return Colors.purple; case SeatState.selected: - return Colors.blue; + return Colors.black54; case SeatState.black: return Colors.black; case SeatState.ordered: diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 857655e9..89cfa06b 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -348,7 +348,7 @@ class _BlueprintEditorPageState extends State { const SizedBox(height: 8), buildLegendItem( "Vybrané", - SeatState.selected, + SeatState.selected_by_me, isActive: false, // Not clickable grayedOut: true, ), diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index 67b39a87..d96f4581 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -85,7 +85,7 @@ class _SeatReservationWidgetState extends State { mainAxisSize: MainAxisSize.min, children: [ SeatWidgetHelper.buildSeat( - state: SeatState.selected, + state: SeatState.selected_by_me, size: SeatReservationWidget.boxSize.toDouble(), ), const SizedBox(width: 8), @@ -113,14 +113,14 @@ class _SeatReservationWidgetState extends State { ? const Center(child: CircularProgressIndicator()) : SeatLayoutWidget( onSeatTap: (model) { - if (model.seatState == SeatState.selected) { + if (model.seatState == SeatState.selected_by_me) { model.seatState = SeatState.available; selectedSeat = null; } else if (model.seatState == SeatState.available) { if (selectedSeat != null) { selectedSeat!.seatState = SeatState.available; } - model.seatState = SeatState.selected; + model.seatState = SeatState.selected_by_me; selectedSeat = model; } setState(() {}); From ce3d9a113ddc39d202d0d5b9480002364ecb1d96 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:56:43 +0100 Subject: [PATCH 074/159] updating text on spot in form --- lib/pages/FormPage.dart | 8 ++++---- lib/pages/SignupPage.dart | 2 +- lib/services/FormHelper.dart | 25 +++++++++++++------------ lib/widgets/SeatReservationWidget.dart | 7 +++---- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index f4f06fcc..fac435e3 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -34,7 +34,7 @@ class _FormPageState extends State { bool _isLoading = false; bool _isSendSuccess = false; double _totalPrice = 0.0; // Total price - Map? formData; + Map? formResult; FormModel? form; final _formKey = GlobalKey(); @@ -112,7 +112,7 @@ class _FormPageState extends State { color: ThemeConfig.blackColor(context), ), text: "Your order was successfully sent to your email {email}." - .tr(namedArgs: {"email": formData?[FormHelper.fieldTypeEmail] ?? ""}), + .tr(namedArgs: {"email": formResult?[FormHelper.fieldTypeEmail] ?? ""}), ), const WidgetSpan( child: Padding( @@ -133,7 +133,7 @@ class _FormPageState extends State { child: AutofillGroup( child: Column( children: [ - ...FormHelper.getAllFormFields(context, form!, _updateTotalPrice), + ...FormHelper.getAllFormFields(context, _formKey, form!, _updateTotalPrice), const SizedBox(height: 16), if (_totalPrice > 0) Text( @@ -161,7 +161,7 @@ class _FormPageState extends State { data["secret"] = "0fb80818-4c8d-4eb7-8205-859b1d786fb3"; data["form"] = "7f4e3892-a544-4385-b933-61117e9755c3"; - formData = data; + formResult = data; var response = await DbEshop.sendTicketOrder(data); diff --git a/lib/pages/SignupPage.dart b/lib/pages/SignupPage.dart index e9e9bfd7..bac23cd8 100644 --- a/lib/pages/SignupPage.dart +++ b/lib/pages/SignupPage.dart @@ -84,7 +84,7 @@ class _SignupPageState extends State { child: AutofillGroup( child: Column( children: [ - ...FormHelper.getAllFormFields(context, form.data![FormHelper.metaFields]), + ...FormHelper.getAllFormFields(context, _formKey, form.data![FormHelper.metaFields]), const SizedBox( height: 16, ), diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 495568c2..7f815eb2 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -58,12 +58,12 @@ class FormHelper { static String secret = "0fb80818-4c8d-4eb7-8205-859b1d786fb3"; - static List getAllFormFields(BuildContext context, FormModel form, [void Function()? updateTotalPrice]) { - return form.data?[FormHelper.metaFields].map((field) => createFormField(context, form, field, updateTotalPrice)).toList(); + static List getAllFormFields(BuildContext context, GlobalKey formKey, FormModel formData, [void Function()? updateTotalPrice]) { + return formData.data?[FormHelper.metaFields].map((field) => createFormField(context, formKey, formData, field, updateTotalPrice)).toList(); } - static List getFormFields(BuildContext context, FormModel form, dynamic fields, [void Function()? updateTotalPrice]) { - return fields.map((field) => createFormField(context, form, field, updateTotalPrice)).toList(); + static List getFormFields(BuildContext context, GlobalKey formKey, FormModel formData, dynamic fields, [void Function()? updateTotalPrice]) { + return fields.map((field) => createFormField(context, formKey, formData, field, updateTotalPrice)).toList(); } // Retrieve form data by iterating over defined fields @@ -118,7 +118,7 @@ class FormHelper { // Create individual form field widget based on configuration - static Widget createFormField(BuildContext context, FormModel form, Map field, [void Function()? updateTotalPrice]) { + static Widget createFormField(BuildContext context, GlobalKey formKey, FormModel form, Map field, [void Function()? updateTotalPrice]) { final bool isRequiredField = field[IS_REQUIRED] ?? false; switch (field[metaType]) { case fieldTypeNote: @@ -130,7 +130,7 @@ class FormHelper { case fieldTypeCity: return buildTextField(fieldTypeCity, cityLabel(), isRequiredField, [AutofillHints.addressCity]); case fieldTypeSpot: - return buildSpotField(context, form, fieldTypeSpot, spotLabel()); + return buildSpotField(context, formKey, form, fieldTypeSpot, spotLabel()); case fieldTypeEmail: return buildEmailField(isRequiredField); case fieldTypeSex: @@ -140,14 +140,15 @@ class FormHelper { case fieldTypeBirthYear: return buildBirthYearField(fieldTypeBirthYear, birthYearLabel(), isRequiredField); case fieldTypeTicket: - return buildTicketField(form, field, ticketValues, ticketKeys, updateTotalPrice); + return buildTicketField(formKey, form, field, ticketValues, ticketKeys, updateTotalPrice); default: return const SizedBox.shrink(); } } static Widget buildTicketField( - FormModel form, + GlobalKey formKey, + FormModel formData, Map field, List> ticketValues, List> ticketKeys, @@ -230,7 +231,7 @@ class FormHelper { key: ticketKeys[i], // Assign the corresponding key onChanged: updateTotalPrice, // Trigger price update on change child: Column( - children: getFormFields(context, form, ticketValues[i][FormHelper.metaFields]), + children: getFormFields(context, ticketKeys[i], formData, ticketValues[i][FormHelper.metaFields]), ), ), ], @@ -255,7 +256,7 @@ class FormHelper { // Build a simple text field with optional validation - static FormBuilderTextField buildSpotField(BuildContext context, FormModel form, String name, String label) { + static FormBuilderTextField buildSpotField(BuildContext context, GlobalKey formKey, FormModel form, String name, String label) { return FormBuilderTextField( name: name, enableInteractiveSelection: false, @@ -272,10 +273,10 @@ class FormHelper { barrierLabel: 'Dialog', transitionDuration: const Duration(milliseconds: 300), pageBuilder: (context, __, ___) { - return SeatReservationWidget(secret: secret, formKey: form.formKey!, blueprintId: form.blueprint!); + return SeatReservationWidget(secret: secret, formDataKey: form.formKey!, blueprintId: form.blueprint!); }, ); - (FormBuilder.of(context)?.fields[name])?.didChange(selectedSeat?.objectModel?.title??"---"); + formKey.currentState?.fields[name]?.didChange(selectedSeat?.objectModel?.title??"---"); }, ); } diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index d96f4581..618f1e35 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:fstapp/components/seatReservation/widgets/SeatWidget.dart'; import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; -import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/styles/StylesConfig.dart'; @@ -17,9 +16,9 @@ class SeatReservationWidget extends StatefulWidget { final int blueprintId; final String secret; - final String formKey; + final String formDataKey; - SeatReservationWidget({Key? key, required this.blueprintId, required this.secret, required this.formKey}) : super(key: key); + SeatReservationWidget({Key? key, required this.blueprintId, required this.secret, required this.formDataKey}) : super(key: key); @override State createState() => _SeatReservationWidgetState(); @@ -164,7 +163,7 @@ class _SeatReservationWidgetState extends State { } void loadData() async { - blueprint = await DbEshop.getBlueprint(widget.secret, widget.formKey, widget.blueprintId); + blueprint = await DbEshop.getBlueprint(widget.secret, widget.formDataKey, widget.blueprintId); if (blueprint == null) { return; } From 618c3fc53ad323a5d4a60bf7597b2d505bcbcf69 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 3 Dec 2024 22:51:51 +0100 Subject: [PATCH 075/159] seat focused --- .../seatReservation/utils/SeatState.dart | 3 + .../seatReservation/widgets/SeatWidget.dart | 13 ++-- lib/services/FormHelper.dart | 72 ++++++++++++++----- lib/widgets/SeatReservationWidget.dart | 28 +++++--- 4 files changed, 84 insertions(+), 32 deletions(-) diff --git a/lib/components/seatReservation/utils/SeatState.dart b/lib/components/seatReservation/utils/SeatState.dart index b5315c63..d73290b8 100644 --- a/lib/components/seatReservation/utils/SeatState.dart +++ b/lib/components/seatReservation/utils/SeatState.dart @@ -3,6 +3,9 @@ enum SeatState { /// some user selected this seat selected, + /// current user selected this seat + selected_by_me_focused, + /// current user selected this seat selected_by_me, diff --git a/lib/components/seatReservation/widgets/SeatWidget.dart b/lib/components/seatReservation/widgets/SeatWidget.dart index d2db7965..242cac94 100644 --- a/lib/components/seatReservation/widgets/SeatWidget.dart +++ b/lib/components/seatReservation/widgets/SeatWidget.dart @@ -4,7 +4,7 @@ import '../utils/SeatState.dart'; class SeatWidgetHelper { static const double padding = 2.0; - static const double selectedByMePadding = 1.0; + static const double focusedPadding = 1.0; /// Static method to create a seat widget with a given seat state and size. /// This allows external calls to render a seat without relying on the `SeatModel`. @@ -15,6 +15,7 @@ class SeatWidgetHelper { final bool hasPadding = state == SeatState.ordered || state == SeatState.selected || state == SeatState.selected_by_me || + state == SeatState.selected_by_me_focused || state == SeatState.available; return Container( @@ -22,7 +23,7 @@ class SeatWidgetHelper { height: size, width: size, child: Container( - margin: hasPadding ? EdgeInsets.all(state == SeatState.selected_by_me ? selectedByMePadding : padding) : EdgeInsets.zero, + margin: hasPadding ? EdgeInsets.all(state == SeatState.selected_by_me_focused ? focusedPadding : padding) : EdgeInsets.zero, decoration: BoxDecoration( color: _getSeatColor(state), borderRadius: BorderRadius.circular(hasPadding ? padding : 0.0), @@ -38,12 +39,14 @@ class SeatWidgetHelper { return Colors.blueAccent; case SeatState.selected_by_me: return Colors.purple; + case SeatState.selected_by_me_focused: + return Colors.purple; case SeatState.selected: - return Colors.black54; + return Colors.black38; case SeatState.black: - return Colors.black; + return Colors.black87; case SeatState.ordered: - return Colors.black54; + return Colors.black38; case SeatState.empty: return Colors.grey.shade300; default: diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 7f815eb2..b4d2b952 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -87,7 +87,10 @@ class FormHelper { } else if (fieldType == fieldTypeBirthYear) { return (fieldValue != null && fieldValue.isNotEmpty) ? int.tryParse(fieldValue) : null; } else if (fieldType == fieldTypeSpot) { - return (fieldValue != null && fieldValue.isNotEmpty) ? int.tryParse(fieldValue) : null; + if (fieldValue is SeatModel) { + return fieldValue.objectModel?.id; + } + return null; } else if (fieldType == fieldTypeTicket) { // Collect ticket data from multiple ticket forms List> tickets = []; @@ -107,7 +110,7 @@ class FormHelper { } } - return tickets; // Return ticket data with all subfields and prices + return tickets; } if(fieldValue is String){ @@ -256,27 +259,62 @@ class FormHelper { // Build a simple text field with optional validation - static FormBuilderTextField buildSpotField(BuildContext context, GlobalKey formKey, FormModel form, String name, String label) { - return FormBuilderTextField( + static Widget buildSpotField( + BuildContext context, GlobalKey formKey, FormModel form, String name, String label) { + // Create a TextEditingController to control the displayed text + TextEditingController textController = TextEditingController(); + + return FormBuilderField( name: name, - enableInteractiveSelection: false, - readOnly: true, - decoration: InputDecoration(labelText: label, suffixIcon: Icon(Icons.event_seat), labelStyle: StylesConfig.textStyleBig), validator: FormBuilderValidators.compose([ FormBuilderValidators.required(), + (value) { + if (value == null) { + return "Please select a seat.".tr(); + } + return null; + }, ]), - onTap: () async { - SeatModel? selectedSeat = await showGeneralDialog( - context: context, - barrierColor: Colors.black12.withOpacity(0.6), // Background color - barrierDismissible: false, - barrierLabel: 'Dialog', - transitionDuration: const Duration(milliseconds: 300), - pageBuilder: (context, __, ___) { - return SeatReservationWidget(secret: secret, formDataKey: form.formKey!, blueprintId: form.blueprint!); + builder: (FormFieldState field) { + SeatModel? seat = field.value; + textController.text = seat?.objectModel?.title ?? "---"; + + return TextField( + controller: textController, + readOnly: true, + canRequestFocus: false, + decoration: InputDecoration( + labelText: label, + suffixIcon: const Icon(Icons.event_seat), + labelStyle: StylesConfig.textStyleBig, + errorText: field.errorText, // Display validation error + ), + onTap: () async { + // Show the seat reservation dialog and await the result + SeatModel? selectedSeat = await showGeneralDialog( + context: context, + barrierColor: Colors.black12.withOpacity(0.6), + barrierDismissible: false, + barrierLabel: 'Dialog', + transitionDuration: const Duration(milliseconds: 300), + pageBuilder: (context, __, ___) { + return SeatReservationWidget( + secret: secret, + formDataKey: form.formKey!, + blueprintId: form.blueprint!, + selectedSeat: field.value, + ); + }, + ); + + if (selectedSeat != null) { + // Update the form field and display value + field.didChange(selectedSeat); + textController.text = + selectedSeat.objectModel?.title ?? "---"; + } }, ); - formKey.currentState?.fields[name]?.didChange(selectedSeat?.objectModel?.title??"---"); }, ); } diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index 618f1e35..80fa30e9 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fstapp/components/seatReservation/widgets/SeatWidget.dart'; @@ -17,15 +18,15 @@ class SeatReservationWidget extends StatefulWidget { final int blueprintId; final String secret; final String formDataKey; + SeatModel? selectedSeat; - SeatReservationWidget({Key? key, required this.blueprintId, required this.secret, required this.formDataKey}) : super(key: key); + SeatReservationWidget({Key? key, required this.blueprintId, required this.secret, required this.formDataKey, this.selectedSeat}) : super(key: key); @override State createState() => _SeatReservationWidgetState(); } class _SeatReservationWidgetState extends State { - SeatModel? selectedSeat; List allObjects = []; @@ -112,15 +113,15 @@ class _SeatReservationWidgetState extends State { ? const Center(child: CircularProgressIndicator()) : SeatLayoutWidget( onSeatTap: (model) { - if (model.seatState == SeatState.selected_by_me) { + if (model.seatState == SeatState.selected_by_me_focused) { model.seatState = SeatState.available; - selectedSeat = null; + widget.selectedSeat = null; } else if (model.seatState == SeatState.available) { - if (selectedSeat != null) { - selectedSeat!.seatState = SeatState.available; + if (widget.selectedSeat != null) { + widget.selectedSeat!.seatState = SeatState.available; } - model.seatState = SeatState.selected_by_me; - selectedSeat = model; + model.seatState = SeatState.selected_by_me_focused; + widget.selectedSeat = model; } setState(() {}); }, @@ -143,11 +144,11 @@ class _SeatReservationWidgetState extends State { }, child: const Text("Storno").tr(), ), - const SizedBox(width: 12), + const SizedBox(width: 24), ElevatedButton( onPressed: () { // Save changes logic - Navigator.pop(context, selectedSeat); + Navigator.pop(context, widget.selectedSeat); }, child: const Text("Save").tr(), ), @@ -168,6 +169,13 @@ class _SeatReservationWidgetState extends State { return; } + if (widget.selectedSeat != null) { + blueprint?.objects + ?.firstWhereOrNull((object) => object.id == widget.selectedSeat?.objectModel?.id) + ?.stateEnum = SeatState.selected_by_me_focused; + } + + setState(() {}); } } From 720bdaff787b7673a036957394f7b1176f7244ee Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 4 Dec 2024 00:00:10 +0100 Subject: [PATCH 076/159] price on spot --- lib/dataModelsEshop/BlueprintModel.dart | 21 ++++++-- lib/dataModelsEshop/BlueprintObjectModel.dart | 9 ++-- lib/dataServices/DbEshop.dart | 2 +- lib/pages/FormPage.dart | 4 +- lib/services/FormHelper.dart | 12 +++-- scripts/database/eshop/get_blueprint.sql | 52 +++++++++++++------ 6 files changed, 72 insertions(+), 28 deletions(-) diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index 6674628c..f2eb5158 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -1,5 +1,6 @@ import 'package:fstapp/components/seatReservation/utils/SeatState.dart'; import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; +import 'package:fstapp/dataModelsEshop/ProductModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; import 'BlueprintConfiguration.dart'; import 'BlueprintObjectModel.dart'; @@ -7,6 +8,7 @@ import 'package:collection/collection.dart'; class BlueprintModel { static const String metaSpots = "spots"; + static const String metaProducts = "products"; static const String metaDefaultProduct = "default_product"; static const String metaTableAreaType = "table"; static const String metaSpotType = "spot"; @@ -17,7 +19,7 @@ class BlueprintModel { String? title; int? organization; int? occasion; - int? defaultProduct; + ProductModel? defaultProduct; BlueprintConfiguration? configuration; List? objects; List? spots; @@ -27,8 +29,8 @@ class BlueprintModel { final List groups = _parseGroups(json); final List? rawObjects = _parseObjects(json); final List? spots = _parseSpots(json); - - final List? enrichedObjects = _enrichObjects(rawObjects, spots); + final List? products = _parseProducts(json); + final List? enrichedObjects = _enrichObjects(rawObjects, spots, products); _assignObjectsToGroups(enrichedObjects, groups); @@ -72,8 +74,16 @@ class BlueprintModel { : null; } + static List? _parseProducts(Map json) { + return json[BlueprintModel.metaProducts] != null + ? List.from( + json[BlueprintModel.metaProducts].map((spot) => ProductModel.fromJson(spot))) + : null; + } + + static List? _enrichObjects( - List? rawObjects, List? spots) { + List? rawObjects, List? spots, List? products) { if (rawObjects == null) return null; return rawObjects.map((object) { @@ -82,7 +92,8 @@ class BlueprintModel { return BlueprintObjectModel( id: object.id, type: object.type, - product: matchingSpot?.product, + spotProduct: matchingSpot?.spotProduct, + product: products?.firstWhereOrNull((p)=>p.id == matchingSpot?.spotProduct), groupId: object.groupId, title: matchingSpot?.title ?? object.title, stateEnum: BlueprintObjectModel.States.entries diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index a6d4ce29..ca02d036 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -1,5 +1,6 @@ import 'package:fstapp/components/seatReservation/utils/SeatState.dart'; import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; +import 'package:fstapp/dataModelsEshop/ProductModel.dart'; class BlueprintObjectModel { static const String metaX = "x"; @@ -42,7 +43,8 @@ class BlueprintObjectModel { String? type; String? state; String? title; - int? product; + ProductModel? product; + int? spotProduct; BlueprintGroupModel? group; SeatState? stateEnum; @@ -55,7 +57,7 @@ class BlueprintObjectModel { type: json[metaType], state: json[metaState], title: json[metaTitle], - product: json[metaProduct], + spotProduct: json[metaProduct], ); } @@ -66,7 +68,7 @@ class BlueprintObjectModel { metaTitle: title, if (id != null) metaId: id, if (group?.id != null) metaGroupId: group?.id, - if (product != null) metaProduct: product, + if (spotProduct != null) metaProduct: spotProduct, }; BlueprintObjectModel({ @@ -78,6 +80,7 @@ class BlueprintObjectModel { this.state, this.stateEnum, this.title, + this.spotProduct, this.product, }); } diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 699c7e2f..6305513c 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -94,7 +94,7 @@ class DbEshop { .eq(TbEshop.product_types.type, ProductModel.spotType); var x = defaultProducts.map((x) => ProductTypeModel.fromJson(x)).first.products!.first; - response[BlueprintModel.metaDefaultProduct] = x.id; + response[BlueprintModel.metaDefaultProduct] = x; return BlueprintModel.fromJson(response); } diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index fac435e3..dc91d220 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataModels/FormOptionModel.dart'; -import 'package:fstapp/dataModelsEshop/ProductModel.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/services/FormHelper.dart'; @@ -75,6 +75,8 @@ class _FormPageState extends State { for (var ticketValue in ticketData.values) { if (ticketValue is FormOptionModel) { _totalPrice += ticketValue.price; + } else if (ticketValue is BlueprintObjectModel){ + _totalPrice += ticketValue.product?.price??0; } } } diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index b4d2b952..787b6bc6 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -88,7 +88,7 @@ class FormHelper { return (fieldValue != null && fieldValue.isNotEmpty) ? int.tryParse(fieldValue) : null; } else if (fieldType == fieldTypeSpot) { if (fieldValue is SeatModel) { - return fieldValue.objectModel?.id; + return fieldValue.objectModel; } return null; } else if (fieldType == fieldTypeTicket) { @@ -133,7 +133,7 @@ class FormHelper { case fieldTypeCity: return buildTextField(fieldTypeCity, cityLabel(), isRequiredField, [AutofillHints.addressCity]); case fieldTypeSpot: - return buildSpotField(context, formKey, form, fieldTypeSpot, spotLabel()); + return buildSpotField(context, formKey, form, fieldTypeSpot, spotLabel(), updateTotalPrice); case fieldTypeEmail: return buildEmailField(isRequiredField); case fieldTypeSex: @@ -260,7 +260,12 @@ class FormHelper { // Build a simple text field with optional validation static Widget buildSpotField( - BuildContext context, GlobalKey formKey, FormModel form, String name, String label) { + BuildContext context, + GlobalKey formKey, + FormModel form, + String name, + String label, + void Function()? updateTotalPrice) { // Create a TextEditingController to control the displayed text TextEditingController textController = TextEditingController(); @@ -309,6 +314,7 @@ class FormHelper { if (selectedSeat != null) { // Update the form field and display value + updateTotalPrice?.call(); field.didChange(selectedSeat); textController.text = selectedSeat.objectModel?.title ?? "---"; diff --git a/scripts/database/eshop/get_blueprint.sql b/scripts/database/eshop/get_blueprint.sql index bae95607..90de7570 100644 --- a/scripts/database/eshop/get_blueprint.sql +++ b/scripts/database/eshop/get_blueprint.sql @@ -10,6 +10,8 @@ AS $$ DECLARE blueprintData JSONB; spotsData JSONB; + productsData JSONB; + valid_spots JSONB; -- Temporary variable to hold valid spot IDs BEGIN -- Validate the form and blueprint association IF NOT EXISTS ( @@ -30,8 +32,6 @@ BEGIN ); END IF; - - -- Fetch blueprint details SELECT jsonb_build_object( 'id', b.id, @@ -46,19 +46,20 @@ BEGIN FROM eshop.blueprints b WHERE b.id = blueprint_id; - -- Extract valid spot IDs from blueprint.objects - WITH valid_spots AS ( - SELECT (obj->>'id')::BIGINT AS spot_id - FROM jsonb_array_elements( - (SELECT b.objects FROM eshop.blueprints b WHERE b.id = blueprint_id) - ) obj - WHERE obj->>'type' = 'spot' - AND obj->>'id' IS NOT NULL - ) + -- Fetch valid spot IDs + SELECT jsonb_agg((obj->>'id')::BIGINT) + INTO valid_spots + FROM jsonb_array_elements( + (SELECT b.objects FROM eshop.blueprints b WHERE b.id = blueprint_id) + ) obj + WHERE obj->>'type' = 'spot' + AND obj->>'id' IS NOT NULL; + -- Fetch enriched spots data SELECT jsonb_agg(jsonb_build_object( 'id', s.id, 'title', s.title, + 'product', s.product, 'state', CASE WHEN s.order_product_ticket IS NOT NULL THEN 'ordered' WHEN s.secret IS NOT NULL AND s.secret_expiration_time > now() THEN @@ -71,10 +72,24 @@ BEGIN )) INTO spotsData FROM eshop.spots s - JOIN valid_spots v ON s.id = v.spot_id - WHERE s.occasion = ( + WHERE s.id = ANY(SELECT jsonb_array_elements_text(valid_spots)::BIGINT) + AND s.occasion = ( SELECT occasion FROM public.forms WHERE key = form_key - ); + ); + + -- Fetch product data for all spots + SELECT jsonb_agg(jsonb_build_object( + 'id', p.id, + 'title', p.title, + 'price', p.price + )) + INTO productsData + FROM eshop.products p + JOIN eshop.spots s ON s.product = p.id + WHERE s.id = ANY(SELECT jsonb_array_elements_text(valid_spots)::BIGINT) + AND s.occasion = ( + SELECT occasion FROM public.forms WHERE key = form_key + ); -- Add spots data to the blueprint object blueprintData = jsonb_set( @@ -83,10 +98,17 @@ BEGIN COALESCE(spotsData, '[]'::jsonb) ); + -- Add products data to the blueprint object + blueprintData = jsonb_set( + blueprintData, + '{products}', + COALESCE(productsData, '[]'::jsonb) + ); + -- Return combined data RETURN jsonb_build_object( 'code', 200, 'data', blueprintData ); END; -$$; \ No newline at end of file +$$; From a9203195ac9335db9c2c01e50b5fe08f49f8c242 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:18:47 +0100 Subject: [PATCH 077/159] select_spot --- lib/dataServices/DbEshop.dart | 21 ++++++ lib/widgets/SeatReservationWidget.dart | 15 +++-- scripts/database/eshop/select_spot.sql | 92 ++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 scripts/database/eshop/select_spot.sql diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 6305513c..bbacfc16 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -115,4 +115,25 @@ class DbEshop { return true; } + + static Future selectSpot(context, String formKey, String secret, int spotId, bool selecting) async { + final response = await _supabase.rpc( + 'select_spot', + params: { + 'form_key': formKey, + 'secret_id': secret, + 'spot_id': spotId, + 'selecting': selecting, + }, + ); + + var code = response["code"]; + var message = response["message"]; + + if (code != 200) { + ToastHelper.Show(context, message.toString(), severity: ToastSeverity.NotOk); + return false; + } + return true; + } } diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index 80fa30e9..8f390aa7 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -112,16 +112,23 @@ class _SeatReservationWidgetState extends State { blueprint == null ? const Center(child: CircularProgressIndicator()) : SeatLayoutWidget( - onSeatTap: (model) { + onSeatTap: (model) async { if (model.seatState == SeatState.selected_by_me_focused) { - model.seatState = SeatState.available; - widget.selectedSeat = null; + if(await DbEshop.selectSpot(context, widget.formDataKey, widget.secret, widget.selectedSeat!.objectModel!.id!, false)){ + model.seatState = SeatState.available; + widget.selectedSeat = null; + } } else if (model.seatState == SeatState.available) { if (widget.selectedSeat != null) { - widget.selectedSeat!.seatState = SeatState.available; + if(await DbEshop.selectSpot(context, widget.formDataKey, widget.secret, widget.selectedSeat!.objectModel!.id!, false)){ + widget.selectedSeat!.seatState = SeatState.available; + } } model.seatState = SeatState.selected_by_me_focused; widget.selectedSeat = model; + if(!await DbEshop.selectSpot(context, widget.formDataKey, widget.secret, widget.selectedSeat!.objectModel!.id!, true)){ + model.seatState = SeatState.available; + } } setState(() {}); }, diff --git a/scripts/database/eshop/select_spot.sql b/scripts/database/eshop/select_spot.sql new file mode 100644 index 00000000..7cc1c0b2 --- /dev/null +++ b/scripts/database/eshop/select_spot.sql @@ -0,0 +1,92 @@ +CREATE OR REPLACE FUNCTION select_spot( + form_key UUID, + secret_id UUID, + spot_id BIGINT, + selecting BOOLEAN +) +RETURNS JSONB +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + blueprint_id BIGINT; + spot_record RECORD; +BEGIN + -- Check if the spot exists under the current form's blueprint + SELECT b.id INTO blueprint_id + FROM public.forms f + JOIN eshop.blueprints b ON f.blueprint = b.id + WHERE f.key = form_key; + + IF blueprint_id IS NULL THEN + RETURN jsonb_build_object( + 'code', 404, + 'message', 'Form key does not exist or is not associated with a blueprint' + ); + END IF; + + -- Check if the spot exists in the blueprint + SELECT * INTO spot_record + FROM eshop.spots + WHERE id = spot_id AND blueprint = blueprint_id; + + IF NOT FOUND THEN + RETURN jsonb_build_object( + 'code', 404, + 'message', 'Spot does not exist under the current blueprint' + ); + END IF; + + -- Check if the spot is already ordered + IF spot_record.order_product_ticket IS NOT NULL THEN + RETURN jsonb_build_object( + 'code', 400, + 'message', 'Spot is already ordered' + ); + END IF; + + -- Check if the secret expiration time is valid + IF spot_record.secret_expiration_time IS NOT NULL + AND spot_record.secret_expiration_time < now() THEN + -- Expire the current secret + UPDATE eshop.spots + SET secret = NULL, + secret_expiration_time = NULL + WHERE id = spot_id; + END IF; + + -- Handle selection or deselection + IF selecting THEN + -- Select the spot + UPDATE eshop.spots + SET secret = secret_id, + secret_expiration_time = now() + interval '15 minutes', + updated_at = now() + WHERE id = spot_id; + + RETURN jsonb_build_object( + 'code', 200, + 'message', 'Spot successfully selected' + ); + ELSE + -- Deselect the spot + IF spot_record.secret IS DISTINCT FROM secret_id THEN + RETURN jsonb_build_object( + 'code', 403, + 'message', 'Cannot deselect spot. Provided secret does not match.' + ); + END IF; + + UPDATE eshop.spots + SET secret = NULL, + secret_expiration_time = NULL, + updated_at = now() + WHERE id = spot_id; + + RETURN jsonb_build_object( + 'code', 200, + 'message', 'Spot successfully deselected' + ); + END IF; +END; +$$; From 70ddc294d58ef0baa3674ce6b398860300689d17 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 4 Dec 2024 18:04:41 +0100 Subject: [PATCH 078/159] order fixes --- lib/pages/FormPage.dart | 1 + lib/services/FormHelper.dart | 21 +++++++++++++++++++ supabase/functions/send-ticket-order/index.ts | 11 ++++++---- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index dc91d220..a83d816f 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -161,6 +161,7 @@ class _FormPageState extends State { var data = FormHelper.getDataFromForm( _formKey, form?.data?[FormHelper.metaFields]); + data = FormHelper.replaceSpotWithId(data); data["secret"] = "0fb80818-4c8d-4eb7-8205-859b1d786fb3"; data["form"] = "7f4e3892-a544-4385-b933-61117e9755c3"; formResult = data; diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 787b6bc6..480b002a 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -6,6 +6,7 @@ import 'package:fstapp/dataModels/UserInfoModel.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/SeatReservationWidget.dart'; @@ -416,4 +417,24 @@ class FormHelper { keyboardType: TextInputType.number, ); } + + static Map replaceSpotWithId(Map inputData) { + if (inputData.containsKey(fieldTypeTicket) && inputData[fieldTypeTicket] is List) { + List tickets = inputData[fieldTypeTicket]; + + // Process each ticket in the list using an index + for (int i = 0; i < tickets.length; i++) { + var ticket = tickets[i]; + if (ticket is Map && ticket.containsKey(fieldTypeSpot)) { + // Replace the spot value with spot['id'] + var spot = ticket[fieldTypeSpot]; + if (spot is BlueprintObjectModel) { + ticket[fieldTypeSpot] = spot.id; + } + } + } + } + return inputData; + } + } \ No newline at end of file diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index 94c39d30..fe84381e 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -65,10 +65,10 @@ function generateFullOrder(orderDetails: any, tickets: any[]): string { const ticketsDetails = tickets .map((ticket) => { const ticketSymbol = ticket.ticket_symbol; - const seat = ticket.items.find((item: any) => item.type === "spot")?.spot_title || "N/A"; - const food = ticket.items.find((item: any) => item.type === "food")?.title; - const taxi = ticket.items.find((item: any) => item.type === "taxi")?.title; - const note = ticket.items.find((item: any) => item.note) || ""; + const seat = ticket.products.find((p: any) => p.type === "spot")?.spot_title || "N/A"; + const food = ticket.products.find((p: any) => p.type === "food")?.title; + const taxi = ticket.products.find((p: any) => p.type === "taxi")?.title; + const note = ticket.note || ""; return `

@@ -106,6 +106,9 @@ Deno.serve(async (req) => { }); } + console.log(orderDetails); + console.log(ticketOrder); + const occasion = ticketOrder.occasion; const paymentInfo = ticketOrder.payment_info; From 7b54aa6a77c180bb7c9503004119afadef8af3d5 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 4 Dec 2024 19:30:38 +0100 Subject: [PATCH 079/159] order fixes --- lib/dataModels/FormModel.dart | 8 ++++++-- lib/pages/FormPage.dart | 5 +++-- lib/services/FormHelper.dart | 8 ++++---- scripts/database/eshop/create_ticket_order.sql | 1 - scripts/database/eshop/get_form.sql | 4 +++- supabase/functions/send-ticket-order/index.ts | 5 +++-- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/dataModels/FormModel.dart b/lib/dataModels/FormModel.dart index 3520b332..04203411 100644 --- a/lib/dataModels/FormModel.dart +++ b/lib/dataModels/FormModel.dart @@ -13,6 +13,8 @@ class FormModel { bool? isOpen; String? accountNumber; + String? secret; + FormModel({ this.id, this.createdAt, @@ -25,6 +27,7 @@ class FormModel { this.deadlineDuration, this.isOpen, this.accountNumber, + this.secret }); factory FormModel.fromJson(Map json) { @@ -41,7 +44,8 @@ class FormModel { bankAccount: json[Tb.forms.bank_account], deadlineDuration: json[Tb.forms.deadline_duration_seconds], isOpen: json[Tb.forms.is_open], - accountNumber: json['account_number'], // Assuming it's added to the RPC response + accountNumber: json['account_number'], + secret: json['secret'], ); } @@ -56,6 +60,6 @@ class FormModel { Tb.forms.bank_account: bankAccount, Tb.forms.deadline_duration_seconds: deadlineDuration, Tb.forms.is_open: isOpen, - 'account_number': accountNumber, // Including in serialization + 'account_number': accountNumber, }; } diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index a83d816f..af8a1d1e 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -162,8 +162,8 @@ class _FormPageState extends State { _formKey, form?.data?[FormHelper.metaFields]); data = FormHelper.replaceSpotWithId(data); - data["secret"] = "0fb80818-4c8d-4eb7-8205-859b1d786fb3"; - data["form"] = "7f4e3892-a544-4385-b933-61117e9755c3"; + data[FormHelper.metaSecret] = form!.secret; + data[FormHelper.metaForm] = form!.formKey; formResult = data; @@ -242,6 +242,7 @@ class _FormPageState extends State { // return; // } //var key = UuidConverter.base62ToUuid(widget.id!); + form = await DbEshop.getForm("7f4e3892-a544-4385-b933-61117e9755c3"); if(form == null) { return; diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 480b002a..9abfa2fb 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -30,7 +30,8 @@ class FormHelper { static const String metaLabel = "label"; static const String metaOptions = "options"; static const String metaOptionsType = "optionsType"; - + static const String metaSecret = "secret"; + static const String metaForm = "form"; static const String fieldTypeOptions = "options"; @@ -54,9 +55,8 @@ class FormHelper { static List> ticketValues = []; static List> ticketKeys = []; - //static String secret = UniqueKey().toString(); - static String secret = "0fb80818-4c8d-4eb7-8205-859b1d786fb3"; + //static String secret = "0fb80818-4c8d-4eb7-8205-859b1d786fb3"; static List getAllFormFields(BuildContext context, GlobalKey formKey, FormModel formData, [void Function()? updateTotalPrice]) { @@ -305,7 +305,7 @@ class FormHelper { transitionDuration: const Duration(milliseconds: 300), pageBuilder: (context, __, ___) { return SeatReservationWidget( - secret: secret, + secret: form.secret!, formDataKey: form.formKey!, blueprintId: form.blueprint!, selectedSeat: field.value, diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index 79a6f116..33d8daa8 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -7,7 +7,6 @@ DECLARE order_id BIGINT; ticket_data JSONB; spot_data RECORD; - spot_id BIGINT; now TIMESTAMP WITH TIME ZONE := NOW(); calculated_price NUMERIC(10,2) := 0; spot_secret UUID; diff --git a/scripts/database/eshop/get_form.sql b/scripts/database/eshop/get_form.sql index 7a554f76..596aad40 100644 --- a/scripts/database/eshop/get_form.sql +++ b/scripts/database/eshop/get_form.sql @@ -2,6 +2,7 @@ CREATE OR REPLACE FUNCTION get_form(form_key UUID) RETURNS JSON LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE allData JSON; + generated_secret UUID := gen_random_uuid(); BEGIN -- Check if the form is open IF NOT EXISTS ( @@ -28,7 +29,8 @@ BEGIN 'occasion', f.occasion, 'blueprint', f.blueprint, 'deadline_duration_seconds', f.deadline_duration_seconds, - 'account_number', ba.account_number + 'account_number', ba.account_number, + 'secret', generated_secret ) ) INTO allData diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index fe84381e..acd22616 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -94,19 +94,20 @@ Deno.serve(async (req) => { const reqData = await req.json(); const { orderDetails } = reqData; + console.log(orderDetails); + const { data: ticketOrder, error: ticketError } = await supabaseAdmin.rpc("create_ticket_order", { input_data: orderDetails, }); if (ticketError || ticketOrder.code !== 200) { console.error("Error creating ticket order:", ticketError); - return new Response(JSON.stringify({ "code": ticketOrder.code }), { + return new Response(JSON.stringify({ "code": ticketOrder.code + ": " + ticketOrder.message }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 200, }); } - console.log(orderDetails); console.log(ticketOrder); const occasion = ticketOrder.occasion; From 6bc21b12477efebfd0d917e85d71f93eccd5c462 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 4 Dec 2024 20:28:25 +0100 Subject: [PATCH 080/159] specific error codes --- scripts/database/eshop/update_blueprint.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/database/eshop/update_blueprint.sql b/scripts/database/eshop/update_blueprint.sql index 8069aa86..fbafbda9 100644 --- a/scripts/database/eshop/update_blueprint.sql +++ b/scripts/database/eshop/update_blueprint.sql @@ -19,19 +19,19 @@ DECLARE BEGIN -- Validate input data IF input_data IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 400, 'message', 'Input data is missing'); + RETURN JSONB_BUILD_OBJECT('code', 4001, 'message', 'Input data is missing'); END IF; -- Get occasion ID from input data IF input_data->>'occasion' IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 400, 'message', 'Missing occasion ID in input data'); + RETURN JSONB_BUILD_OBJECT('code', 4002, 'message', 'Missing occasion ID in input data'); END IF; occasion_id := (input_data->>'occasion')::BIGINT; -- Get organization ID from input data IF input_data->>'organization' IS NULL THEN - RETURN JSONB_BUILD_OBJECT('code', 400, 'message', 'Missing organization ID in input data'); + RETURN JSONB_BUILD_OBJECT('code', 4003, 'message', 'Missing organization ID in input data'); END IF; organization_id := (input_data->>'organization')::BIGINT; @@ -90,7 +90,7 @@ BEGIN -- Return error for invalid or missing product_id IF product_id IS NULL THEN RETURN JSONB_BUILD_OBJECT( - 'code', 400, + 'code', 4004, 'message', 'Invalid or missing product. Ensure the product exists and is part of the occasion.', 'details', JSONB_BUILD_OBJECT( 'product_id', NULL, @@ -169,7 +169,7 @@ BEGIN ELSE -- Spot cannot be deleted, return error RETURN JSONB_BUILD_OBJECT( - 'code', 403, + 'code', 4031, 'message', 'Cannot delete spot due to invalid state', 'spot_id', spot.id, 'details', JSONB_BUILD_OBJECT( @@ -205,6 +205,6 @@ BEGIN EXCEPTION WHEN OTHERS THEN - RETURN JSONB_BUILD_OBJECT('code', 500, 'message', SQLERRM); + RETURN JSONB_BUILD_OBJECT('code', 5001, 'message', SQLERRM); END; $$; From 0608a1edfdfbd485aed3083f494a6bda01250745 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 4 Dec 2024 20:54:46 +0100 Subject: [PATCH 081/159] blueprint product --- lib/dataModelsEshop/BlueprintObjectModel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index ca02d036..f12736e2 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -68,7 +68,7 @@ class BlueprintObjectModel { metaTitle: title, if (id != null) metaId: id, if (group?.id != null) metaGroupId: group?.id, - if (spotProduct != null) metaProduct: spotProduct, + if (spotProduct != null || product != null) metaProduct: spotProduct ?? product?.id, }; BlueprintObjectModel({ From 9f9a2238679b971f65cee53403e4e993f04cf813 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 4 Dec 2024 21:13:01 +0100 Subject: [PATCH 082/159] blueprint product tooltip --- .../widgets/SeatLayoutWidget.dart | 17 +++++++++++++++-- lib/pages/BlueprintEditorPage.dart | 2 +- lib/services/Utilities.dart | 4 +++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index 5cfa989a..16d87953 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; +import 'package:fstapp/services/Utilities.dart'; import '../model/SeatLayoutStateModel.dart'; import '../model/SeatModel.dart'; @@ -100,9 +101,10 @@ class _SeatLayoutWidgetState extends State { children: List.generate(widget.stateModel.cols, (colI) { final seatModel = _seats.firstWhere((seat) => seat.rowI == rowI && seat.colI == colI); - return Tooltip( + return seatModel.objectModel != null && seatModel.objectModel!.id != null + ? Tooltip( showDuration: const Duration(seconds: 0), - message: seatModel.objectModel?.title ?? "", + message: "${seatModel.objectModel?.title ?? ""}\n${Utilities.formatPrice(context, seatModel.objectModel?.product?.price ?? 0)}", child: GestureDetector( onTap: () { if (widget.onSeatTap != null) { @@ -114,6 +116,17 @@ class _SeatLayoutWidgetState extends State { size: seatModel.seatSize.toDouble(), ), ), + ) + : GestureDetector( + onTap: () { + if (widget.onSeatTap != null) { + widget.onSeatTap!(seatModel); + } + }, + child: SeatWidgetHelper.buildSeat( + state: seatModel.seatState, + size: seatModel.seatSize.toDouble(), + ), ); }), ); diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 89cfa06b..61bdd118 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -241,7 +241,7 @@ class _BlueprintEditorPageState extends State { Row( children: [ Text( - "${group.objects.length}", + "(${group.objects.length})", style: Theme.of(context).textTheme.bodySmall, ), ], diff --git a/lib/services/Utilities.dart b/lib/services/Utilities.dart index eb442dc2..ccde34a8 100644 --- a/lib/services/Utilities.dart +++ b/lib/services/Utilities.dart @@ -5,12 +5,14 @@ import 'package:intl/intl.dart'; class Utilities { static formatPrice(BuildContext context, double price) { // Get locale from context or fallback to Czech locale - final locale = EasyLocalization.of(context)?.locale.toString() ?? 'cs_CZ'; + //final locale = EasyLocalization.of(context)?.locale.toString() ?? 'cs_CZ'; + final locale = 'cs_CZ'; // Configure the currency formatter final NumberFormat currencyFormatter = NumberFormat.currency( locale: locale, symbol: 'KČ', // Use the CZK symbol + decimalDigits: 0 ); return currencyFormatter.format(price); // Format the price as currency From 4acfc02af579f88ca31204713649424f92cedb92 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 02:55:39 +0100 Subject: [PATCH 083/159] getting groups fix --- .../model/SeatLayoutStateModel.dart | 3 +- .../widgets/SeatLayoutWidget.dart | 91 ++++++++++++------- .../seatReservation/widgets/SeatWidget.dart | 4 +- lib/dataModelsEshop/BlueprintModel.dart | 21 +++-- lib/dataModelsEshop/TbEshop.dart | 1 + lib/pages/BlueprintEditorPage.dart | 1 + scripts/database/eshop/get_blueprint.sql | 2 +- scripts/database/eshop/update_blueprint.sql | 4 +- 8 files changed, 77 insertions(+), 50 deletions(-) diff --git a/lib/components/seatReservation/model/SeatLayoutStateModel.dart b/lib/components/seatReservation/model/SeatLayoutStateModel.dart index f72e9eb7..63d4ac86 100644 --- a/lib/components/seatReservation/model/SeatLayoutStateModel.dart +++ b/lib/components/seatReservation/model/SeatLayoutStateModel.dart @@ -1,4 +1,3 @@ - import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'SeatModel.dart'; @@ -8,6 +7,7 @@ class SeatLayoutStateModel { final List currentObjects; final List allBoxes; final int seatSize; + final String? backgroundSvg; const SeatLayoutStateModel({ required this.rows, @@ -15,5 +15,6 @@ class SeatLayoutStateModel { required this.currentObjects, required this.allBoxes, this.seatSize = 50, + this.backgroundSvg, }); } diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index 16d87953..bc505942 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; +import 'package:flutter_svg/flutter_svg.dart'; // Add flutter_svg package import 'package:fstapp/services/Utilities.dart'; import '../model/SeatLayoutStateModel.dart'; @@ -87,50 +88,70 @@ class _SeatLayoutWidgetState extends State { @override Widget build(BuildContext context) { + int layoutWidth = widget.stateModel.cols * widget.stateModel.seatSize; + int layoutHeight = widget.stateModel.rows * widget.stateModel.seatSize; + return InteractiveViewer( minScale: 0.1, maxScale: 5, boundaryMargin: const EdgeInsets.all(double.infinity), constrained: false, transformationController: _controller, - child: Column( - mainAxisSize: MainAxisSize.min, - children: List.generate(widget.stateModel.rows, (rowI) { - return Row( + child: Stack( + children: [ + // Background SVG + if (widget.stateModel.backgroundSvg != null) + Positioned.fill( + child: SvgPicture.string( + widget.stateModel.backgroundSvg!, + width: layoutWidth.toDouble(), + height: layoutHeight.toDouble(), + fit: BoxFit.fitWidth, + ), + ), + // Seat Layout + Column( mainAxisSize: MainAxisSize.min, - children: List.generate(widget.stateModel.cols, (colI) { - final seatModel = - _seats.firstWhere((seat) => seat.rowI == rowI && seat.colI == colI); - return seatModel.objectModel != null && seatModel.objectModel!.id != null - ? Tooltip( - showDuration: const Duration(seconds: 0), - message: "${seatModel.objectModel?.title ?? ""}\n${Utilities.formatPrice(context, seatModel.objectModel?.product?.price ?? 0)}", - child: GestureDetector( - onTap: () { - if (widget.onSeatTap != null) { - widget.onSeatTap!(seatModel); - } - }, - child: SeatWidgetHelper.buildSeat( - state: seatModel.seatState, - size: seatModel.seatSize.toDouble(), - ), - ), - ) - : GestureDetector( - onTap: () { - if (widget.onSeatTap != null) { - widget.onSeatTap!(seatModel); - } - }, - child: SeatWidgetHelper.buildSeat( - state: seatModel.seatState, - size: seatModel.seatSize.toDouble(), - ), + children: List.generate(widget.stateModel.rows, (rowI) { + return Row( + mainAxisSize: MainAxisSize.min, + children: List.generate(widget.stateModel.cols, (colI) { + final seatModel = _seats.firstWhere( + (seat) => seat.rowI == rowI && seat.colI == colI); + return seatModel.objectModel != null && + seatModel.objectModel!.id != null + ? Tooltip( + showDuration: const Duration(seconds: 0), + message: + "${seatModel.objectModel?.title ?? ""}\n${Utilities.formatPrice(context, seatModel.objectModel?.product?.price ?? 0)}", + child: GestureDetector( + onTap: () { + if (widget.onSeatTap != null) { + widget.onSeatTap!(seatModel); + } + }, + child: SeatWidgetHelper.buildSeat( + state: seatModel.seatState, + size: seatModel.seatSize.toDouble(), + ), + ), + ) + : GestureDetector( + onTap: () { + if (widget.onSeatTap != null) { + widget.onSeatTap!(seatModel); + } + }, + child: SeatWidgetHelper.buildSeat( + state: seatModel.seatState, + size: seatModel.seatSize.toDouble(), + ), + ); + }), ); }), - ); - }), + ), + ], ), ); } diff --git a/lib/components/seatReservation/widgets/SeatWidget.dart b/lib/components/seatReservation/widgets/SeatWidget.dart index 242cac94..16e83bf9 100644 --- a/lib/components/seatReservation/widgets/SeatWidget.dart +++ b/lib/components/seatReservation/widgets/SeatWidget.dart @@ -48,9 +48,9 @@ class SeatWidgetHelper { case SeatState.ordered: return Colors.black38; case SeatState.empty: - return Colors.grey.shade300; + return Colors.black.withOpacity(0.1); default: - return Colors.grey.shade300; + return Colors.black.withOpacity(0.1); } } } diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index f2eb5158..d662f147 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -24,6 +24,7 @@ class BlueprintModel { List? objects; List? spots; List? groups; + String? backgroundSvg; factory BlueprintModel.fromJson(Map json) { final List groups = _parseGroups(json); @@ -49,6 +50,7 @@ class BlueprintModel { : null, objects: enrichedObjects, groups: groups, + backgroundSvg: json[TbEshop.blueprints.background_svg], ); } @@ -81,9 +83,10 @@ class BlueprintModel { : null; } - static List? _enrichObjects( - List? rawObjects, List? spots, List? products) { + List? rawObjects, + List? spots, + List? products) { if (rawObjects == null) return null; return rawObjects.map((object) { @@ -93,12 +96,12 @@ class BlueprintModel { id: object.id, type: object.type, spotProduct: matchingSpot?.spotProduct, - product: products?.firstWhereOrNull((p)=>p.id == matchingSpot?.spotProduct), + product: products?.firstWhereOrNull((p) => p.id == matchingSpot?.spotProduct), groupId: object.groupId, title: matchingSpot?.title ?? object.title, stateEnum: BlueprintObjectModel.States.entries - .firstWhereOrNull( - (entry) => entry.value == matchingSpot?.state)?.key ?? + .firstWhereOrNull((entry) => entry.value == matchingSpot?.state) + ?.key ?? SeatState.available, x: object.x, y: object.y, @@ -119,11 +122,11 @@ class BlueprintModel { static void _assignObjectsToGroups( List? objects, List? groups) { - // Assign objects to groups if (objects != null) { for (var obj in objects) { if (obj.groupId != null) { final group = groups?.firstWhereOrNull((g) => g.id == obj.groupId); + obj.group = group; if (group != null) { group.objects.add(obj); } @@ -132,7 +135,6 @@ class BlueprintModel { } } - Map toJson() => { TbEshop.blueprints.id: id, TbEshop.blueprints.created_at: createdAt?.toIso8601String(), @@ -143,11 +145,11 @@ class BlueprintModel { TbEshop.blueprints.configuration: configuration, TbEshop.blueprints.objects: objects, TbEshop.blueprints.groups: groups, + TbEshop.blueprints.background_svg: backgroundSvg, // Include background SVG }; String toBasicString() => title ?? id.toString(); - BlueprintModel({ this.id, this.createdAt, @@ -160,11 +162,12 @@ class BlueprintModel { this.objects, this.spots, this.groups, + this.backgroundSvg, // Add to constructor }); int getFirstAvailableGroupId() { int id = 1; - while (groups?.any((group) => group.id == id)??false) { + while (groups?.any((group) => group.id == id) ?? false) { id++; } return id; diff --git a/lib/dataModelsEshop/TbEshop.dart b/lib/dataModelsEshop/TbEshop.dart index 58ce4cb7..07e6a039 100644 --- a/lib/dataModelsEshop/TbEshop.dart +++ b/lib/dataModelsEshop/TbEshop.dart @@ -81,6 +81,7 @@ class BlueprintTb { String get configuration => "configuration"; String get objects => "objects"; String get groups => "groups"; + String get background_svg => "background_svg"; } class SpotsTb { diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 61bdd118..8464fa19 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -113,6 +113,7 @@ class _BlueprintEditorPageState extends State { seatSize: SeatReservationWidget.boxSize, currentObjects: blueprint!.objects!, allBoxes: allBoxes, + backgroundSvg: blueprint!.backgroundSvg ), ), ), diff --git a/scripts/database/eshop/get_blueprint.sql b/scripts/database/eshop/get_blueprint.sql index 90de7570..0ec3e3be 100644 --- a/scripts/database/eshop/get_blueprint.sql +++ b/scripts/database/eshop/get_blueprint.sql @@ -11,7 +11,7 @@ DECLARE blueprintData JSONB; spotsData JSONB; productsData JSONB; - valid_spots JSONB; -- Temporary variable to hold valid spot IDs + valid_spots JSONB; BEGIN -- Validate the form and blueprint association IF NOT EXISTS ( diff --git a/scripts/database/eshop/update_blueprint.sql b/scripts/database/eshop/update_blueprint.sql index fbafbda9..52bbe3a7 100644 --- a/scripts/database/eshop/update_blueprint.sql +++ b/scripts/database/eshop/update_blueprint.sql @@ -107,7 +107,7 @@ BEGIN UPDATE eshop.spots SET product = product_id, - title = spot_title, -- Save the extracted spot title + title = spot_title, updated_at = now WHERE id = (object_data->>'id')::BIGINT; ELSE @@ -117,7 +117,7 @@ BEGIN updated_at, occasion, blueprint, - title, -- Save the extracted spot title + title, product ) VALUES ( From 60c76e4d01552dc5b93a4f04f9854d41b11c7b42 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:02:00 +0100 Subject: [PATCH 084/159] seat color fix --- lib/components/seatReservation/widgets/SeatWidget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/seatReservation/widgets/SeatWidget.dart b/lib/components/seatReservation/widgets/SeatWidget.dart index 16e83bf9..254b062f 100644 --- a/lib/components/seatReservation/widgets/SeatWidget.dart +++ b/lib/components/seatReservation/widgets/SeatWidget.dart @@ -19,7 +19,7 @@ class SeatWidgetHelper { state == SeatState.available; return Container( - color: _getSeatColor(SeatState.empty), + color: hasPadding ? Colors.black.withOpacity(0.2) : _getSeatColor(SeatState.empty), height: size, width: size, child: Container( From 0f3c2c0271c5abbbf5c2b61343e15c96310bd926 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 03:40:25 +0100 Subject: [PATCH 085/159] validate spotfield --- lib/pages/FormPage.dart | 2 +- lib/services/FormHelper.dart | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index af8a1d1e..6ed6db4d 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -154,7 +154,7 @@ class _FormPageState extends State { ? null : () async { TextInput.finishAutofillContext(); - if (_formKey.currentState?.saveAndValidate() ?? false) { + if (FormHelper.saveAndValidate(_formKey)) { setState(() { _isLoading = true; }); diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 9abfa2fb..afda296e 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -32,6 +32,7 @@ class FormHelper { static const String metaOptionsType = "optionsType"; static const String metaSecret = "secret"; static const String metaForm = "form"; + static const String metaEmpty = "---"; static const String fieldTypeOptions = "options"; @@ -55,10 +56,6 @@ class FormHelper { static List> ticketValues = []; static List> ticketKeys = []; - - //static String secret = "0fb80818-4c8d-4eb7-8205-859b1d786fb3"; - - static List getAllFormFields(BuildContext context, GlobalKey formKey, FormModel formData, [void Function()? updateTotalPrice]) { return formData.data?[FormHelper.metaFields].map((field) => createFormField(context, formKey, formData, field, updateTotalPrice)).toList(); } @@ -67,6 +64,16 @@ class FormHelper { return fields.map((field) => createFormField(context, formKey, formData, field, updateTotalPrice)).toList(); } + static bool saveAndValidate(GlobalKey key){ + bool toReturn = key.currentState?.saveAndValidate() ?? false; + for(var k in ticketKeys){ + if(!(k.currentState?.saveAndValidate() ?? false)){ + toReturn = false; + } + } + return toReturn; + } + // Retrieve form data by iterating over defined fields static Map getDataFromForm(GlobalKey key, dynamic fields) { Map toReturn = {}; @@ -283,7 +290,7 @@ class FormHelper { ]), builder: (FormFieldState field) { SeatModel? seat = field.value; - textController.text = seat?.objectModel?.title ?? "---"; + textController.text = seat?.objectModel?.title ?? metaEmpty; return TextField( controller: textController, @@ -318,7 +325,8 @@ class FormHelper { updateTotalPrice?.call(); field.didChange(selectedSeat); textController.text = - selectedSeat.objectModel?.title ?? "---"; + selectedSeat.objectModel?.title ?? metaEmpty; + field.validate(); } }, ); From 238deed3fb9fbf169516258921bb7e593c9c5859 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 04:18:36 +0100 Subject: [PATCH 086/159] bigger font --- lib/services/FormHelper.dart | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index afda296e..18cef5d9 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -53,6 +53,8 @@ class FormHelper { static String femaleLabel() => "Female".tr(); static String notSpecifiedLabel() => "Not specified".tr(); + static double fontSizeFactor = 1.2; + static List> ticketValues = []; static List> ticketKeys = []; @@ -222,7 +224,7 @@ class FormHelper { "Ticket {number}".tr(namedArgs: {"number": (i + 1).toString()}), // Use translated string style: TextStyle( fontWeight: FontWeight.bold, - fontSize: 16, + fontSize: 16 * fontSizeFactor, ), ), ), @@ -291,7 +293,6 @@ class FormHelper { builder: (FormFieldState field) { SeatModel? seat = field.value; textController.text = seat?.objectModel?.title ?? metaEmpty; - return TextField( controller: textController, readOnly: true, @@ -299,7 +300,7 @@ class FormHelper { decoration: InputDecoration( labelText: label, suffixIcon: const Icon(Icons.event_seat), - labelStyle: StylesConfig.textStyleBig, + labelStyle: StylesConfig.textStyleBig.copyWith(fontSize: 16 * fontSizeFactor), errorText: field.errorText, // Display validation error ), onTap: () async { @@ -339,7 +340,8 @@ class FormHelper { return FormBuilderTextField( name: name, autofillHints: autofillHints, - decoration: InputDecoration(labelText: label), + decoration: InputDecoration(labelText: label, + labelStyle: TextStyle(fontSize: 16 * fontSizeFactor),), validator: isRequired ? FormBuilderValidators.required() : null, ); } @@ -349,7 +351,8 @@ class FormHelper { return FormBuilderTextField( name: fieldTypeEmail, autofillHints: [AutofillHints.email], - decoration: InputDecoration(labelText: emailLabel()), + decoration: InputDecoration(labelText: emailLabel(), + labelStyle: TextStyle(fontSize: 16 * fontSizeFactor),), validator: FormBuilderValidators.compose([ if (isRequired) FormBuilderValidators.required(), FormBuilderValidators.email(errorText: emailInvalidMessage()), @@ -368,7 +371,8 @@ class FormHelper { } return FormBuilderRadioGroup( name: name, - decoration: InputDecoration(labelText: label), + decoration: InputDecoration(labelText: label, + labelStyle: StylesConfig.textStyleBig.copyWith(fontSize: 16 * fontSizeFactor),), validator: isRequired ? FormBuilderValidators.required() : null, options: options, ); @@ -384,7 +388,12 @@ class FormHelper { o[FormOptionModel.metaOptionsId], o[FormOptionModel.metaOptionsName], price: o[FormOptionModel.metaOptionsPrice] ?? 0.0, // Use price from the option or default to 0.0 - ))); + ), + child: Text( + o[FormOptionModel.metaOptionsName], + style: TextStyle(fontSize: 14.0 * fontSizeFactor), // Adjust font size dynamically + ), + )); } // Use the first option as the default initial value @@ -392,7 +401,8 @@ class FormHelper { return FormBuilderRadioGroup( name: name, - decoration: InputDecoration(labelText: label), + decoration: InputDecoration(labelText: label, + labelStyle: StylesConfig.textStyleBig.copyWith(fontSize: 16 * fontSizeFactor),), validator: FormBuilderValidators.required(), options: options, initialValue: initialValue, @@ -404,7 +414,8 @@ class FormHelper { static FormBuilderTextField buildBirthYearField(String name, String label, bool isRequired) { return FormBuilderTextField( name: name, - decoration: InputDecoration(labelText: label), + decoration: InputDecoration(labelText: label, + labelStyle: TextStyle(fontSize: 16 * fontSizeFactor),), validator: FormBuilderValidators.compose([ if (isRequired) FormBuilderValidators.required(), // Allow empty for optional field; validate only if non-empty From 7b2305dce5daed65a4f42e6c41a3b695466cc19e Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 04:50:26 +0100 Subject: [PATCH 087/159] ticket note, email adjust --- lib/widgets/SeatReservationWidget.dart | 1 + .../database/eshop/create_ticket_order.sql | 20 +++++++++++++------ supabase/functions/send-ticket-order/index.ts | 4 ++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index 8f390aa7..3ce9bb69 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -127,6 +127,7 @@ class _SeatReservationWidgetState extends State { model.seatState = SeatState.selected_by_me_focused; widget.selectedSeat = model; if(!await DbEshop.selectSpot(context, widget.formDataKey, widget.secret, widget.selectedSeat!.objectModel!.id!, true)){ + widget.selectedSeat = null; model.seatState = SeatState.available; } } diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index 33d8daa8..c8f338a3 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -29,8 +29,8 @@ DECLARE form_key UUID; deadline TIMESTAMPTZ; form_deadline_duration BIGINT; - currency_code TEXT; -- Variable for currency_code - first_currency_code TEXT := NULL; -- Variable to store the first product's currency + currency_code TEXT; + first_currency_code TEXT := NULL; BEGIN -- Validate input data and extract form key IF input_data IS NULL OR input_data->'form' IS NULL THEN @@ -118,9 +118,16 @@ BEGIN -- Generate ticket symbol ticket_symbol := generate_ticket_symbol(organization_id, occasion_id); - -- Create ticket with ticket symbol - INSERT INTO eshop.tickets (state, occasion, ticket_symbol, created_at, updated_at) - VALUES ('ordered', occasion_id, ticket_symbol, now, now) + -- Create ticket with ticket symbol and note + INSERT INTO eshop.tickets (state, occasion, ticket_symbol, note, created_at, updated_at) + VALUES ( + 'ordered', + occasion_id, + ticket_symbol, + ticket_data->>'note', + now, + now + ) RETURNING id INTO ticket_id; -- Initialize ticket products array @@ -199,10 +206,11 @@ BEGIN END IF; END LOOP; - -- Add ticket with its products to ticket details + -- Add ticket with its products and note to ticket details ticket_details := ticket_details || JSONB_BUILD_OBJECT( 'ticket_id', ticket_id, 'ticket_symbol', ticket_symbol, + 'note', ticket_data->>'note', 'products', ticket_products ); END LOOP; diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index acd22616..039717fc 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -36,8 +36,6 @@ function formatDatetime(datetime: string): string { year: "numeric", month: "2-digit", day: "2-digit", - hour: "2-digit", - minute: "2-digit", }).format(date); } @@ -45,6 +43,8 @@ function formatCurrency(amount, currencyCode) { return new Intl.NumberFormat("cs-CZ", { style: "currency", currency: currencyCode, + minimumFractionDigits: 0, + maximumFractionDigits: 0, }).format(amount); } From 3fe28112098c1d595dfc329834c232bf559051da Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 07:12:20 +0100 Subject: [PATCH 088/159] added items --- lib/dataModelsEshop/BlueprintModel.dart | 52 +++++++++++++++++++++---- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index d662f147..53a638c5 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -1,7 +1,10 @@ import 'package:fstapp/components/seatReservation/utils/SeatState.dart'; import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; +import 'package:fstapp/dataModelsEshop/OrderModel.dart'; +import 'package:fstapp/dataModelsEshop/OrderProductTicketModel.dart'; import 'package:fstapp/dataModelsEshop/ProductModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/dataModelsEshop/TicketModel.dart'; import 'BlueprintConfiguration.dart'; import 'BlueprintObjectModel.dart'; import 'package:collection/collection.dart'; @@ -9,7 +12,6 @@ import 'package:collection/collection.dart'; class BlueprintModel { static const String metaSpots = "spots"; static const String metaProducts = "products"; - static const String metaDefaultProduct = "default_product"; static const String metaTableAreaType = "table"; static const String metaSpotType = "spot"; @@ -19,18 +21,24 @@ class BlueprintModel { String? title; int? organization; int? occasion; - ProductModel? defaultProduct; BlueprintConfiguration? configuration; List? objects; List? spots; List? groups; String? backgroundSvg; + List? tickets; + List? orders; + List? orderProductTickets; factory BlueprintModel.fromJson(Map json) { final List groups = _parseGroups(json); final List? rawObjects = _parseObjects(json); final List? spots = _parseSpots(json); final List? products = _parseProducts(json); + final List? tickets = _parseTickets(json); + final List? orders = _parseOrders(json); + final List? orderProductTickets = _parseOrderProductTickets(json); + final List? enrichedObjects = _enrichObjects(rawObjects, spots, products); _assignObjectsToGroups(enrichedObjects, groups); @@ -44,13 +52,15 @@ class BlueprintModel { title: json[TbEshop.blueprints.title], organization: json[TbEshop.blueprints.organization], occasion: json[TbEshop.blueprints.occasion], - defaultProduct: json[BlueprintModel.metaDefaultProduct], configuration: json[TbEshop.blueprints.configuration] != null ? BlueprintConfiguration.fromJson(json[TbEshop.blueprints.configuration]) : null, objects: enrichedObjects, groups: groups, backgroundSvg: json[TbEshop.blueprints.background_svg], + tickets: tickets, + orders: orders, + orderProductTickets: orderProductTickets, ); } @@ -77,9 +87,30 @@ class BlueprintModel { } static List? _parseProducts(Map json) { - return json[BlueprintModel.metaProducts] != null + return json[TbEshop.products.table] != null ? List.from( - json[BlueprintModel.metaProducts].map((spot) => ProductModel.fromJson(spot))) + json[TbEshop.products.table].map((p) => ProductModel.fromJson(p))) + : null; + } + + static List? _parseTickets(Map json) { + return json[TbEshop.tickets.table] != null + ? List.from( + json[TbEshop.tickets.table].map((t) => TicketModel.fromJson(t))) + : null; + } + + static List? _parseOrders(Map json) { + return json[TbEshop.orders.table] != null + ? List.from( + json[TbEshop.orders.table].map((o) => OrderModel.fromJson(o))) + : null; + } + + static List? _parseOrderProductTickets(Map json) { + return json[TbEshop.order_product_ticket.table] != null + ? List.from( + json[TbEshop.order_product_ticket.table].map((o) => OrderProductTicketModel.fromJson(o))) : null; } @@ -145,7 +176,10 @@ class BlueprintModel { TbEshop.blueprints.configuration: configuration, TbEshop.blueprints.objects: objects, TbEshop.blueprints.groups: groups, - TbEshop.blueprints.background_svg: backgroundSvg, // Include background SVG + TbEshop.blueprints.background_svg: backgroundSvg, + TbEshop.tickets.table: tickets, + TbEshop.orders.table: orders, + TbEshop.order_product_ticket.table: orderProductTickets, }; String toBasicString() => title ?? id.toString(); @@ -157,12 +191,14 @@ class BlueprintModel { this.title, this.organization, this.occasion, - this.defaultProduct, this.configuration, this.objects, this.spots, this.groups, - this.backgroundSvg, // Add to constructor + this.backgroundSvg, + this.tickets, + this.orders, + this.orderProductTickets, }); int getFirstAvailableGroupId() { From 0f38ab48832e20f8c27fec3727bd7931156a114d Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 07:53:16 +0100 Subject: [PATCH 089/159] get blueprint 1 --- .../database/eshop/get_blueprint_editor.sql | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 scripts/database/eshop/get_blueprint_editor.sql diff --git a/scripts/database/eshop/get_blueprint_editor.sql b/scripts/database/eshop/get_blueprint_editor.sql new file mode 100644 index 00000000..89fdbabc --- /dev/null +++ b/scripts/database/eshop/get_blueprint_editor.sql @@ -0,0 +1,162 @@ +CREATE OR REPLACE FUNCTION get_blueprint_editor( + blueprint_id BIGINT +) +RETURNS JSONB +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + blueprintData JSONB; + spotsData JSONB; + productsData JSONB; + ticketsData JSONB; + ordersData JSONB; + orderProductTicketsData JSONB; + valid_spots JSONB; + occasion_id BIGINT; +BEGIN + -- Fetch occasion_id for the blueprint + SELECT occasion + INTO occasion_id + FROM eshop.blueprints + WHERE id = blueprint_id; + + -- Authorization check + IF (SELECT get_is_editor_on_occasion(occasion_id)) <> TRUE THEN + RETURN jsonb_build_object('code', 403, 'message', 'User is not authorized to edit this occasion'); + END IF; + + -- Fetch blueprint details + SELECT jsonb_build_object( + 'id', b.id, + 'created_at', b.created_at, + 'data', b.data, + 'title', b.title, + 'configuration', b.configuration, + 'objects', b.objects, + 'groups', b.groups, + 'background_svg', b.groups + ) + INTO blueprintData + FROM eshop.blueprints b + WHERE b.id = blueprint_id; + + -- Fetch valid spot IDs + SELECT jsonb_agg((obj->>'id')::BIGINT) + INTO valid_spots + FROM jsonb_array_elements( + (SELECT objects FROM eshop.blueprints WHERE id = blueprint_id) + ) obj + WHERE obj->>'type' = 'spot' + AND obj->>'id' IS NOT NULL; + + -- Fetch enriched spots data + SELECT jsonb_agg(jsonb_build_object( + 'id', s.id, + 'title', s.title, + 'product', s.product, + 'state', CASE + WHEN s.order_product_ticket IS NOT NULL THEN 'ordered' + WHEN s.secret IS NOT NULL AND s.secret_expiration_time > now() THEN 'selected' + ELSE 'available' + END + )) + INTO spotsData + FROM eshop.spots s + WHERE s.id = ANY(SELECT jsonb_array_elements_text(valid_spots)::BIGINT) + AND s.occasion = occasion_id; + + -- Fetch all products of type "spot", "taxi", "food" + SELECT jsonb_agg(jsonb_build_object( + 'id', p.id, + 'title', p.title, + 'price', p.price, + 'type', pt.type, + 'description', pt.description + )) + INTO productsData + FROM eshop.products p + JOIN eshop.product_types pt ON pt.id = p.id + WHERE pt.type IN ('spot', 'taxi', 'food') + AND pt.occasion = occasion_id; + + -- Fetch tickets associated with the occasion + SELECT jsonb_agg(jsonb_build_object( + 'id', t.id, + 'ticket_symbol', t.ticket_symbol, + 'state', t.state, + 'note', t.note + )) + INTO ticketsData + FROM eshop.tickets t + WHERE t.occasion = occasion_id; + + -- Fetch orders associated with the occasion + SELECT jsonb_agg(jsonb_build_object( + 'id', o.id, + 'created_at', o.created_at, + 'updated_at', o.updated_at, + 'price', o.price, + 'state', o.state, + 'currency_code', o.currency_code, + 'data', o.data + )) + INTO ordersData + FROM eshop.orders o + WHERE o.occasion = occasion_id; + + -- Fetch order-product-ticket data + SELECT jsonb_agg(jsonb_build_object( + 'id', opt.id, + 'order_id', opt."order", + 'product_id', opt.product, + 'ticket_id', opt.ticket, + 'created_at', opt.created_at + )) + INTO orderProductTicketsData + FROM eshop.order_product_ticket opt + JOIN eshop.orders o ON o.id = opt."order" + WHERE o.occasion = occasion_id; + + -- Add spots data to the blueprint object + blueprintData = jsonb_set( + blueprintData, + '{spots}', + COALESCE(spotsData, '[]'::jsonb) + ); + + -- Add products data to the blueprint object + blueprintData = jsonb_set( + blueprintData, + '{products}', + COALESCE(productsData, '[]'::jsonb) + ); + + -- Add tickets data to the blueprint object + blueprintData = jsonb_set( + blueprintData, + '{tickets}', + COALESCE(ticketsData, '[]'::jsonb) + ); + + -- Add orders data to the blueprint object + blueprintData = jsonb_set( + blueprintData, + '{orders}', + COALESCE(ordersData, '[]'::jsonb) + ); + + -- Add order-product-ticket data to the blueprint object + blueprintData = jsonb_set( + blueprintData, + '{order_product_ticket}', + COALESCE(orderProductTicketsData, '[]'::jsonb) + ); + + -- Return combined data + RETURN jsonb_build_object( + 'code', 200, + 'data', blueprintData + ); +END; +$$; From 36cfa827739be15919dd9b82a078fbb1921b062b Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 08:09:41 +0100 Subject: [PATCH 090/159] sql fix --- .../database/eshop/get_blueprint_editor.sql | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/scripts/database/eshop/get_blueprint_editor.sql b/scripts/database/eshop/get_blueprint_editor.sql index 89fdbabc..80f75b1c 100644 --- a/scripts/database/eshop/get_blueprint_editor.sql +++ b/scripts/database/eshop/get_blueprint_editor.sql @@ -35,7 +35,7 @@ BEGIN 'configuration', b.configuration, 'objects', b.objects, 'groups', b.groups, - 'background_svg', b.groups + 'background_svg', b.background_svg ) INTO blueprintData FROM eshop.blueprints b @@ -80,7 +80,7 @@ BEGIN WHERE pt.type IN ('spot', 'taxi', 'food') AND pt.occasion = occasion_id; - -- Fetch tickets associated with the occasion + -- Fetch relevant tickets associated with the valid spots SELECT jsonb_agg(jsonb_build_object( 'id', t.id, 'ticket_symbol', t.ticket_symbol, @@ -89,9 +89,11 @@ BEGIN )) INTO ticketsData FROM eshop.tickets t - WHERE t.occasion = occasion_id; + JOIN eshop.order_product_ticket opt ON opt.ticket = t.id + WHERE opt.product = ANY(SELECT jsonb_array_elements_text(valid_spots)::BIGINT) + AND t.occasion = occasion_id; - -- Fetch orders associated with the occasion + -- Fetch relevant orders associated with the valid spots SELECT jsonb_agg(jsonb_build_object( 'id', o.id, 'created_at', o.created_at, @@ -103,9 +105,11 @@ BEGIN )) INTO ordersData FROM eshop.orders o - WHERE o.occasion = occasion_id; + JOIN eshop.order_product_ticket opt ON opt."order" = o.id + WHERE opt.product = ANY(SELECT jsonb_array_elements_text(valid_spots)::BIGINT) + AND o.occasion = occasion_id; - -- Fetch order-product-ticket data + -- Fetch relevant order-product-ticket data associated with the valid spots SELECT jsonb_agg(jsonb_build_object( 'id', opt.id, 'order_id', opt."order", @@ -115,8 +119,7 @@ BEGIN )) INTO orderProductTicketsData FROM eshop.order_product_ticket opt - JOIN eshop.orders o ON o.id = opt."order" - WHERE o.occasion = occasion_id; + WHERE opt.product = ANY(SELECT jsonb_array_elements_text(valid_spots)::BIGINT); -- Add spots data to the blueprint object blueprintData = jsonb_set( @@ -132,21 +135,21 @@ BEGIN COALESCE(productsData, '[]'::jsonb) ); - -- Add tickets data to the blueprint object + -- Add relevant tickets data to the blueprint object blueprintData = jsonb_set( blueprintData, '{tickets}', COALESCE(ticketsData, '[]'::jsonb) ); - -- Add orders data to the blueprint object + -- Add relevant orders data to the blueprint object blueprintData = jsonb_set( blueprintData, '{orders}', COALESCE(ordersData, '[]'::jsonb) ); - -- Add order-product-ticket data to the blueprint object + -- Add relevant order-product-ticket data to the blueprint object blueprintData = jsonb_set( blueprintData, '{order_product_ticket}', From 5443cdc09870c70f0a4ea6e309f841a9108e55ab Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:22:34 +0100 Subject: [PATCH 091/159] show full date in blueprinteditor --- assets/translations/en.json | 2 + .../widgets/SeatLayoutWidget.dart | 2 +- lib/dataModelsEshop/BlueprintModel.dart | 11 ++++ lib/dataModelsEshop/BlueprintObjectModel.dart | 57 ++++++++++++++++- lib/dataModelsEshop/OrderModel.dart | 61 +++++++++++++++++++ .../OrderProductTicketModel.dart | 39 ++++++++++++ lib/dataModelsEshop/ProductModel.dart | 4 ++ lib/dataModelsEshop/TbEshop.dart | 7 ++- lib/dataModelsEshop/TicketModel.dart | 50 +++++++++++++++ lib/dataServices/DbEshop.dart | 39 +++++------- lib/pages/BlueprintEditorPage.dart | 10 +-- scripts/database/eshop/get_blueprint.sql | 3 +- .../database/eshop/get_blueprint_editor.sql | 34 +++++++---- scripts/database/eshop/update_blueprint.sql | 4 ++ 14 files changed, 277 insertions(+), 46 deletions(-) create mode 100644 lib/dataModelsEshop/OrderModel.dart create mode 100644 lib/dataModelsEshop/OrderProductTicketModel.dart create mode 100644 lib/dataModelsEshop/TicketModel.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index e4af9b3a..b60ad0d5 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -301,5 +301,7 @@ "Total Price: {price}": "Total Price: {price}", "Ticket {number}": "Ticket {number}", "Add another ticket": "Add another ticket", + "Price": "Price", + "Ticket": "Ticket", "_":"_" } \ No newline at end of file diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index bc505942..5da56028 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -123,7 +123,7 @@ class _SeatLayoutWidgetState extends State { ? Tooltip( showDuration: const Duration(seconds: 0), message: - "${seatModel.objectModel?.title ?? ""}\n${Utilities.formatPrice(context, seatModel.objectModel?.product?.price ?? 0)}", + "${seatModel.objectModel?.blueprintTooltip(context)}", child: GestureDetector( onTap: () { if (widget.onSeatTap != null) { diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index 53a638c5..ed8b52d5 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -26,6 +26,7 @@ class BlueprintModel { List? spots; List? groups; String? backgroundSvg; + List? products; List? tickets; List? orders; List? orderProductTickets; @@ -58,6 +59,7 @@ class BlueprintModel { objects: enrichedObjects, groups: groups, backgroundSvg: json[TbEshop.blueprints.background_svg], + products: products, tickets: tickets, orders: orders, orderProductTickets: orderProductTickets, @@ -128,6 +130,7 @@ class BlueprintModel { type: object.type, spotProduct: matchingSpot?.spotProduct, product: products?.firstWhereOrNull((p) => p.id == matchingSpot?.spotProduct), + orderProductTicket: matchingSpot?.orderProductTicket, groupId: object.groupId, title: matchingSpot?.title ?? object.title, stateEnum: BlueprintObjectModel.States.entries @@ -177,6 +180,7 @@ class BlueprintModel { TbEshop.blueprints.objects: objects, TbEshop.blueprints.groups: groups, TbEshop.blueprints.background_svg: backgroundSvg, + TbEshop.products.table: products, TbEshop.tickets.table: tickets, TbEshop.orders.table: orders, TbEshop.order_product_ticket.table: orderProductTickets, @@ -196,11 +200,18 @@ class BlueprintModel { this.spots, this.groups, this.backgroundSvg, + this.products, this.tickets, this.orders, this.orderProductTickets, }); + void assignAllSpotsWithBlueprint(){ + for(var o in objects??[]){ + o.blueprint = this; + } + } + int getFirstAvailableGroupId() { int id = 1; while (groups?.any((group) => group.id == id) ?? false) { diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index f12736e2..ed1b3fa5 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -1,6 +1,12 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; import 'package:fstapp/components/seatReservation/utils/SeatState.dart'; import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; import 'package:fstapp/dataModelsEshop/ProductModel.dart'; +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/services/Utilities.dart'; +import 'package:collection/collection.dart'; class BlueprintObjectModel { static const String metaX = "x"; @@ -11,7 +17,6 @@ class BlueprintObjectModel { static const String metaTitle = "title"; static const String metaId = "id"; static const String metaGroupId = "group"; - static const String metaProduct = "product"; static const String orderedType = "ordered"; static const String selectedType = "selected"; @@ -40,6 +45,7 @@ class BlueprintObjectModel { int? y; int? id; int? groupId; + int? orderProductTicket; String? type; String? state; String? title; @@ -47,6 +53,48 @@ class BlueprintObjectModel { int? spotProduct; BlueprintGroupModel? group; SeatState? stateEnum; + BlueprintModel? blueprint; + String blueprintTooltip(BuildContext context) { + // Find the matching order product ticket + var opt = blueprint?.orderProductTickets?.firstWhereOrNull((t) => t.id == orderProductTicket); + + if (opt != null) { + // Find the corresponding ticket + var ticket = blueprint?.tickets?.firstWhereOrNull((t) => t.id == opt.ticketId); + + if (ticket != null) { + // Get other products associated with the ticket + var otherProducts = blueprint?.orderProductTickets + ?.where((o) => o.ticketId == ticket.id) + .map((t) => t.productId) + .toList(); + + // Generate string for other products + var productsString = otherProducts + ?.where((p) => p != product?.id) + .map((op) { + var pr = blueprint?.products?.firstWhere((p) => p.id == op); + return pr?.title ?? ""; + }) + .where((title) => title.isNotEmpty) + .join("\n") ?? ""; + + // Get the order details + var order = blueprint?.orders?.firstWhere((p) => p.id == opt.orderId); + var orderString = order != null + ? "\n${order.data?["name"]} ${order.data?["surname"]} (${order.data?["email"]})\n${order.data?["note"] ?? ""}" + : ""; + + // Add ticket note if available + var ticketNoteString = ticket.note != null ? "\n${ticket.note}" : ""; + + return "${product?.title} ${title ?? ""}\n${"Ticket".tr()} ${ticket.ticketSymbol}$ticketNoteString\n$productsString$orderString"; + } + } + + // Fallback for when no matching ticket or order product ticket is found + return "${product?.title} ${title ?? ""}\n${"Price".tr()}: ${Utilities.formatPrice(context, product?.price ?? 0)}"; + } factory BlueprintObjectModel.fromJson(Map json) { return BlueprintObjectModel( @@ -54,10 +102,11 @@ class BlueprintObjectModel { y: json[metaY], id: json[metaId], groupId: json[metaGroupId], + orderProductTicket: json[TbEshop.spots.order_product_ticket], type: json[metaType], state: json[metaState], title: json[metaTitle], - spotProduct: json[metaProduct], + spotProduct: json[TbEshop.spots.product], ); } @@ -68,7 +117,7 @@ class BlueprintObjectModel { metaTitle: title, if (id != null) metaId: id, if (group?.id != null) metaGroupId: group?.id, - if (spotProduct != null || product != null) metaProduct: spotProduct ?? product?.id, + if (spotProduct != null || product != null) TbEshop.spots.product: spotProduct ?? product?.id, }; BlueprintObjectModel({ @@ -76,11 +125,13 @@ class BlueprintObjectModel { this.y, this.id, this.groupId, + this.orderProductTicket, this.type, this.state, this.stateEnum, this.title, this.spotProduct, this.product, + this.blueprint, }); } diff --git a/lib/dataModelsEshop/OrderModel.dart b/lib/dataModelsEshop/OrderModel.dart new file mode 100644 index 00000000..dfd0f3f3 --- /dev/null +++ b/lib/dataModelsEshop/OrderModel.dart @@ -0,0 +1,61 @@ +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; + +class OrderModel { + int? id; + DateTime? createdAt; + DateTime? updatedAt; + double? price; + String? state; + Map? data; + int? occasion; + int? paymentInfo; + int? form; + String? currencyCode; + + static const String pendingState = "pending"; + static const String completedState = "completed"; + static const String canceledState = "canceled"; + + factory OrderModel.fromJson(Map json) { + return OrderModel( + id: json[TbEshop.orders.id], + createdAt: json[TbEshop.orders.created_at] != null ? DateTime.parse(json[TbEshop.orders.created_at]) : null, + updatedAt: json[TbEshop.orders.updated_at] != null ? DateTime.parse(json[TbEshop.orders.updated_at]) : null, + price: json[TbEshop.orders.price] != null ? double.tryParse(json[TbEshop.orders.price].toString()) : null, + state: json[TbEshop.orders.state], + data: json[TbEshop.orders.data], + occasion: json[TbEshop.orders.occasion], + paymentInfo: json[TbEshop.orders.payment_info], + form: json[TbEshop.orders.form], + currencyCode: json[TbEshop.orders.currency_code], + ); + } + + Map toJson() => { + TbEshop.orders.id: id, + TbEshop.orders.created_at: createdAt?.toIso8601String(), + TbEshop.orders.updated_at: updatedAt?.toIso8601String(), + TbEshop.orders.price: price, + TbEshop.orders.state: state, + TbEshop.orders.data: data, + TbEshop.orders.occasion: occasion, + TbEshop.orders.payment_info: paymentInfo, + TbEshop.orders.form: form, + TbEshop.orders.currency_code: currencyCode, + }; + + String toBasicString() => id != null ? "Order #$id" : "Order"; + + OrderModel({ + this.id, + this.createdAt, + this.updatedAt, + this.price, + this.state, + this.data, + this.occasion, + this.paymentInfo, + this.form, + this.currencyCode, + }); +} diff --git a/lib/dataModelsEshop/OrderProductTicketModel.dart b/lib/dataModelsEshop/OrderProductTicketModel.dart new file mode 100644 index 00000000..e5166592 --- /dev/null +++ b/lib/dataModelsEshop/OrderProductTicketModel.dart @@ -0,0 +1,39 @@ +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; + +class OrderProductTicketModel { + int? id; + DateTime? createdAt; + int? orderId; + int? productId; + int? ticketId; + + factory OrderProductTicketModel.fromJson(Map json) { + return OrderProductTicketModel( + id: json[TbEshop.order_product_ticket.id], + createdAt: json[TbEshop.order_product_ticket.created_at] != null + ? DateTime.parse(json[TbEshop.order_product_ticket.created_at]) + : null, + orderId: json[TbEshop.order_product_ticket.order], + productId: json[TbEshop.order_product_ticket.product], + ticketId: json[TbEshop.order_product_ticket.ticket], + ); + } + + Map toJson() => { + TbEshop.order_product_ticket.id: id, + TbEshop.order_product_ticket.created_at: createdAt?.toIso8601String(), + TbEshop.order_product_ticket.order: orderId, + TbEshop.order_product_ticket.product: productId, + TbEshop.order_product_ticket.ticket: ticketId, + }; + + String toBasicString() => "OrderProductTicket #${id ?? ''}"; + + OrderProductTicketModel({ + this.id, + this.createdAt, + this.orderId, + this.productId, + this.ticketId, + }); +} diff --git a/lib/dataModelsEshop/ProductModel.dart b/lib/dataModelsEshop/ProductModel.dart index 3c4c3b83..535dea04 100644 --- a/lib/dataModelsEshop/ProductModel.dart +++ b/lib/dataModelsEshop/ProductModel.dart @@ -11,10 +11,12 @@ class ProductModel { Map? data; int? productType; int? occasion; + String? productTypeString; static const String foodType = "food"; static const String taxiType = "taxi"; static const String spotType = "spot"; + static const String metaTypeField = "type"; factory ProductModel.fromJson(Map json) { return ProductModel( @@ -28,6 +30,7 @@ class ProductModel { data: json[TbEshop.products.data], productType: json[TbEshop.products.product_type], occasion: json[TbEshop.products.occasion], + productTypeString: json[metaTypeField], ); } @@ -57,5 +60,6 @@ class ProductModel { this.data, this.productType, this.occasion, + this.productTypeString, }); } diff --git a/lib/dataModelsEshop/TbEshop.dart b/lib/dataModelsEshop/TbEshop.dart index 07e6a039..c35cf3f1 100644 --- a/lib/dataModelsEshop/TbEshop.dart +++ b/lib/dataModelsEshop/TbEshop.dart @@ -56,6 +56,10 @@ class OrdersTb { String get state => "state"; String get data => "data"; String get occasion => "occasion"; + String get form => "form"; + String get payment_info => "payment_info"; + String get currency_code => "currency_code"; + } class TicketsTb { @@ -64,9 +68,10 @@ class TicketsTb { String get id => "id"; String get created_at => "created_at"; String get updated_at => "updated_at"; - String get alias => "alias"; + String get ticket_symbol => "ticket_symbol"; String get state => "state"; String get occasion => "occasion"; + String get note => "note"; } class BlueprintTb { diff --git a/lib/dataModelsEshop/TicketModel.dart b/lib/dataModelsEshop/TicketModel.dart new file mode 100644 index 00000000..ba2bea39 --- /dev/null +++ b/lib/dataModelsEshop/TicketModel.dart @@ -0,0 +1,50 @@ +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; + +class TicketModel { + int? id; + DateTime? createdAt; + DateTime? updatedAt; + String? ticketSymbol; + String? state; + int? occasion; + String? note; + + static const String orderedState = "ordered"; + static const String activeState = "active"; + static const String usedState = "used"; + static const String stornoState = "storno"; + + factory TicketModel.fromJson(Map json) { + return TicketModel( + id: json[TbEshop.tickets.id], + createdAt: json[TbEshop.tickets.created_at] != null ? DateTime.parse(json[TbEshop.tickets.created_at]) : null, + updatedAt: json[TbEshop.tickets.updated_at] != null ? DateTime.parse(json[TbEshop.tickets.updated_at]) : null, + ticketSymbol: json[TbEshop.tickets.ticket_symbol], + state: json[TbEshop.tickets.state], + occasion: json[TbEshop.tickets.occasion], + note: json[TbEshop.tickets.note], + ); + } + + Map toJson() => { + TbEshop.tickets.id: id, + TbEshop.tickets.created_at: createdAt?.toIso8601String(), + TbEshop.tickets.updated_at: updatedAt?.toIso8601String(), + TbEshop.tickets.ticket_symbol: ticketSymbol, + TbEshop.tickets.state: state, + TbEshop.tickets.occasion: occasion, + TbEshop.tickets.note: note, + }; + + String toBasicString() => ticketSymbol ?? id.toString(); + + TicketModel({ + this.id, + this.createdAt, + this.updatedAt, + this.ticketSymbol, + this.state, + this.occasion, + this.note, + }); +} diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index bbacfc16..7912622d 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -68,34 +68,27 @@ class DbEshop { return null; } - return BlueprintModel.fromJson(response["data"]); + var b = BlueprintModel.fromJson(response["data"]); + b.assignAllSpotsWithBlueprint(); + return b; } - static Future getBlueprintForEdit(int blueprintId, int occasion) async { + static Future getBlueprintForEdit(int blueprintId) async { - final response = await _supabaseEshop - .from(TbEshop.blueprints.table) - .select() - .eq(TbEshop.blueprints.id, blueprintId) - .single(); - - var spots = await _supabaseEshop.from(TbEshop.spots.table).select().eq(TbEshop.spots.occasion, occasion); - response[BlueprintModel.metaSpots] = spots; + final response = await _supabase.rpc( + 'get_blueprint_editor', + params: { + 'blueprint_id': blueprintId, + }, + ); - var defaultProducts = await _supabaseEshop - .from(TbEshop.product_types.table) - .select( - "${TbEshop.product_types.id}," - "${TbEshop.product_types.type}," - "${TbEshop.product_types.title}," - "${TbEshop.products.table}(${TbEshop.products.id},${TbEshop.products.title},${TbEshop.products.price})" - ) - .eq(TbEshop.product_types.occasion, occasion) - .eq(TbEshop.product_types.type, ProductModel.spotType); - var x = defaultProducts.map((x) => ProductTypeModel.fromJson(x)).first.products!.first; + if (response["code"] != 200) { + return null; + } - response[BlueprintModel.metaDefaultProduct] = x; - return BlueprintModel.fromJson(response); + var b = BlueprintModel.fromJson(response["data"]); + b.assignAllSpotsWithBlueprint(); + return b; } static Future updateBlueprint(context, BlueprintModel blueprint) async { diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index 8464fa19..e90160ea 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -6,13 +6,15 @@ import 'package:fstapp/components/seatReservation/widgets/SeatWidget.dart'; import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; +import 'package:fstapp/dataModelsEshop/ProductModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; -import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/services/DialogHelper.dart'; import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/services/Utilities.dart'; import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/SeatReservationWidget.dart'; +import 'package:collection/collection.dart'; + import '../components/seatReservation/model/SeatLayoutStateModel.dart'; import '../components/seatReservation/model/SeatModel.dart'; @@ -113,7 +115,7 @@ class _BlueprintEditorPageState extends State { seatSize: SeatReservationWidget.boxSize, currentObjects: blueprint!.objects!, allBoxes: allBoxes, - backgroundSvg: blueprint!.backgroundSvg + //backgroundSvg: blueprint!.backgroundSvg ), ), ), @@ -564,7 +566,7 @@ class _BlueprintEditorPageState extends State { // Create or update the object model model.objectModel = model.objectModel ?? BlueprintObjectModel(x: model.colI, y: model.rowI); model.objectModel!.type = BlueprintModel.metaSpotType; - model.objectModel!.product = blueprint!.defaultProduct; + model.objectModel!.product = blueprint!.products?.firstWhereOrNull((p)=>p.productTypeString == ProductModel.spotType); model.objectModel!.group = currentGroup; model.objectModel!.title = currentGroup?.getNextBoxName(); @@ -607,7 +609,7 @@ class _BlueprintEditorPageState extends State { } Future loadData() async { - blueprint = await DbEshop.getBlueprintForEdit(widget.id!, RightsService.currentOccasion!); + blueprint = await DbEshop.getBlueprintForEdit(widget.id!); setState(() { currentBoxes = blueprint!.objects; diff --git a/scripts/database/eshop/get_blueprint.sql b/scripts/database/eshop/get_blueprint.sql index 0ec3e3be..d09a4dd6 100644 --- a/scripts/database/eshop/get_blueprint.sql +++ b/scripts/database/eshop/get_blueprint.sql @@ -40,7 +40,8 @@ BEGIN 'title', b.title, 'configuration', b.configuration, 'objects', b.objects, - 'groups', b.groups + 'groups', b.groups, + 'background_svg', b.groups ) INTO blueprintData FROM eshop.blueprints b diff --git a/scripts/database/eshop/get_blueprint_editor.sql b/scripts/database/eshop/get_blueprint_editor.sql index 80f75b1c..8619c0ca 100644 --- a/scripts/database/eshop/get_blueprint_editor.sql +++ b/scripts/database/eshop/get_blueprint_editor.sql @@ -55,6 +55,7 @@ BEGIN 'id', s.id, 'title', s.title, 'product', s.product, + 'order_product_ticket', s.order_product_ticket, 'state', CASE WHEN s.order_product_ticket IS NOT NULL THEN 'ordered' WHEN s.secret IS NOT NULL AND s.secret_expiration_time > now() THEN 'selected' @@ -76,11 +77,11 @@ BEGIN )) INTO productsData FROM eshop.products p - JOIN eshop.product_types pt ON pt.id = p.id + JOIN eshop.product_types pt ON pt.id = p.product_type WHERE pt.type IN ('spot', 'taxi', 'food') AND pt.occasion = occasion_id; - -- Fetch relevant tickets associated with the valid spots + -- Fetch relevant tickets associated with the order_product_ticket in spots SELECT jsonb_agg(jsonb_build_object( 'id', t.id, 'ticket_symbol', t.ticket_symbol, @@ -90,10 +91,11 @@ BEGIN INTO ticketsData FROM eshop.tickets t JOIN eshop.order_product_ticket opt ON opt.ticket = t.id - WHERE opt.product = ANY(SELECT jsonb_array_elements_text(valid_spots)::BIGINT) + JOIN eshop.spots s ON s.order_product_ticket = opt.id + WHERE s.id = ANY(SELECT jsonb_array_elements_text(valid_spots)::BIGINT) AND t.occasion = occasion_id; - -- Fetch relevant orders associated with the valid spots + -- Fetch relevant orders associated with the order_product_ticket in spots SELECT jsonb_agg(jsonb_build_object( 'id', o.id, 'created_at', o.created_at, @@ -106,20 +108,26 @@ BEGIN INTO ordersData FROM eshop.orders o JOIN eshop.order_product_ticket opt ON opt."order" = o.id - WHERE opt.product = ANY(SELECT jsonb_array_elements_text(valid_spots)::BIGINT) + JOIN eshop.spots s ON s.order_product_ticket = opt.id + WHERE s.id = ANY(SELECT jsonb_array_elements_text(valid_spots)::BIGINT) AND o.occasion = occasion_id; - -- Fetch relevant order-product-ticket data associated with the valid spots + -- Extract order IDs from ordersData JSONB + WITH order_ids AS ( + SELECT (order_obj->>'id')::BIGINT AS id + FROM jsonb_array_elements(ordersData) order_obj + ) + -- Fetch relevant order-product-ticket data based on extracted order IDs SELECT jsonb_agg(jsonb_build_object( - 'id', opt.id, - 'order_id', opt."order", - 'product_id', opt.product, - 'ticket_id', opt.ticket, - 'created_at', opt.created_at - )) + 'id', opt.id, + 'order', opt."order", + 'product', opt.product, + 'ticket', opt.ticket, + 'created_at', opt.created_at + )) INTO orderProductTicketsData FROM eshop.order_product_ticket opt - WHERE opt.product = ANY(SELECT jsonb_array_elements_text(valid_spots)::BIGINT); + WHERE opt."order" IN (SELECT id FROM order_ids); -- Add spots data to the blueprint object blueprintData = jsonb_set( diff --git a/scripts/database/eshop/update_blueprint.sql b/scripts/database/eshop/update_blueprint.sql index 52bbe3a7..fd6fffcc 100644 --- a/scripts/database/eshop/update_blueprint.sql +++ b/scripts/database/eshop/update_blueprint.sql @@ -29,6 +29,10 @@ BEGIN occasion_id := (input_data->>'occasion')::BIGINT; + IF (SELECT get_is_editor_on_occasion(occasion_id)) <> TRUE THEN + RETURN jsonb_build_object('code', 403, 'message', 'User is not authorized to edit this occasion'); + END IF; + -- Get organization ID from input data IF input_data->>'organization' IS NULL THEN RETURN JSONB_BUILD_OBJECT('code', 4003, 'message', 'Missing organization ID in input data'); From 4a5aa85e56ffab88c3d7b8df60ff3fa41d90cf74 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:13:04 +0100 Subject: [PATCH 092/159] ordered check --- lib/dataModelsEshop/BlueprintObjectModel.dart | 79 ++++++++++--------- lib/pages/BlueprintEditorPage.dart | 2 +- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index ed1b3fa5..b49a60d6 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -54,6 +54,46 @@ class BlueprintObjectModel { BlueprintGroupModel? group; SeatState? stateEnum; BlueprintModel? blueprint; + + factory BlueprintObjectModel.fromJson(Map json) { + return BlueprintObjectModel( + x: json[metaX], + y: json[metaY], + id: json[metaId], + groupId: json[metaGroupId], + orderProductTicket: json[TbEshop.spots.order_product_ticket], + type: json[metaType], + state: json[metaState], + title: json[metaTitle], + spotProduct: json[TbEshop.spots.product], + ); + } + + Map toJson() => { + metaX: x, + metaY: y, + metaType: type, + metaTitle: title, + if (id != null) metaId: id, + if (group?.id != null) metaGroupId: group?.id, + if (spotProduct != null || product != null) TbEshop.spots.product: spotProduct ?? product?.id, + }; + + BlueprintObjectModel({ + this.x, + this.y, + this.id, + this.groupId, + this.orderProductTicket, + this.type, + this.state, + this.stateEnum, + this.title, + this.spotProduct, + this.product, + this.blueprint, + }); + String blueprintTooltip(BuildContext context) { // Find the matching order product ticket var opt = blueprint?.orderProductTickets?.firstWhereOrNull((t) => t.id == orderProductTicket); @@ -96,42 +136,7 @@ class BlueprintObjectModel { return "${product?.title} ${title ?? ""}\n${"Price".tr()}: ${Utilities.formatPrice(context, product?.price ?? 0)}"; } - factory BlueprintObjectModel.fromJson(Map json) { - return BlueprintObjectModel( - x: json[metaX], - y: json[metaY], - id: json[metaId], - groupId: json[metaGroupId], - orderProductTicket: json[TbEshop.spots.order_product_ticket], - type: json[metaType], - state: json[metaState], - title: json[metaTitle], - spotProduct: json[TbEshop.spots.product], - ); + bool isOrdered(){ + return orderProductTicket != null; } - - Map toJson() => { - metaX: x, - metaY: y, - metaType: type, - metaTitle: title, - if (id != null) metaId: id, - if (group?.id != null) metaGroupId: group?.id, - if (spotProduct != null || product != null) TbEshop.spots.product: spotProduct ?? product?.id, - }; - - BlueprintObjectModel({ - this.x, - this.y, - this.id, - this.groupId, - this.orderProductTicket, - this.type, - this.state, - this.stateEnum, - this.title, - this.spotProduct, - this.product, - this.blueprint, - }); } diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index e90160ea..ea346b4b 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -506,7 +506,7 @@ class _BlueprintEditorPageState extends State { } void handleSeatTap(SeatModel model) { - if (model.seatState == SeatState.ordered) { + if (model.objectModel?.isOrdered() ?? false) { ToastHelper.Show(context, "Místa, která byla objednána není možné měnit.", severity: ToastSeverity.NotOk); return; } From a5fc7f4cc10218df6e0cda48eb74358ea8f70b9e Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:42:47 +0100 Subject: [PATCH 093/159] change background --- assets/translations/en.json | 1 + lib/pages/BlueprintEditorPage.dart | 22 +++++++++++++++++++++- lib/widgets/DropFile.dart | 3 ++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index b60ad0d5..74d6d290 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -303,5 +303,6 @@ "Add another ticket": "Add another ticket", "Price": "Price", "Ticket": "Ticket", + "Drop files here": "Drop files here", "_":"_" } \ No newline at end of file diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index ea346b4b..c8c27fae 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -115,7 +115,7 @@ class _BlueprintEditorPageState extends State { seatSize: SeatReservationWidget.boxSize, currentObjects: blueprint!.objects!, allBoxes: allBoxes, - //backgroundSvg: blueprint!.backgroundSvg + backgroundSvg: blueprint!.backgroundSvg ), ), ), @@ -425,6 +425,26 @@ class _BlueprintEditorPageState extends State { seatLayoutController.fitLayout(); // Fit layout after height change }, ), + const SizedBox(width: 12), + IconButton( + icon: const Icon(Icons.grid_on), + tooltip: "Background of the blueprint", + onPressed: () async { + var file = await DialogHelper.dropFilesHere( + context, + "Import SVG pozadí".tr(), + "Ok".tr(), + "Storno".tr(), + ); + if (file != null) { + var content = await file.readAsString(); + setState(() { + blueprint!.backgroundSvg = content; + }); + ToastHelper.Show(context, "SVG pozadí bylo úspěšně nahráno."); + } + }, + ), ], ); } diff --git a/lib/widgets/DropFile.dart b/lib/widgets/DropFile.dart index 5f57a5b6..23f58a89 100644 --- a/lib/widgets/DropFile.dart +++ b/lib/widgets/DropFile.dart @@ -1,4 +1,5 @@ import 'package:desktop_drop/desktop_drop.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:cross_file/cross_file.dart'; @@ -59,7 +60,7 @@ class _DropFileState extends State { child: Stack( children: [ if (file == null) - const Center(child: Text("Soubor zde")) + Center(child: Text("Drop files here").tr()) else Text(file?.path??"") ], From 0ab8eafb9f0378aad82bf8cbc14db73f2f90cf14 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:08:52 +0100 Subject: [PATCH 094/159] drag and drop blueprint background --- assets/translations/en.json | 2 +- .../widgets/SeatLayoutWidget.dart | 2 +- lib/dataServices/DbEshop.dart | 3 +- lib/services/DialogHelper.dart | 22 +++-- lib/widgets/DropFile.dart | 82 ++++++++++++++----- .../database/eshop/get_blueprint_editor.sql | 2 + scripts/database/eshop/update_blueprint.sql | 21 +---- 7 files changed, 87 insertions(+), 47 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 74d6d290..268532c5 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -303,6 +303,6 @@ "Add another ticket": "Add another ticket", "Price": "Price", "Ticket": "Ticket", - "Drop files here": "Drop files here", + "Drop file here": "Drop file here", "_":"_" } \ No newline at end of file diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index 5da56028..12168d21 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -106,7 +106,7 @@ class _SeatLayoutWidgetState extends State { widget.stateModel.backgroundSvg!, width: layoutWidth.toDouble(), height: layoutHeight.toDouble(), - fit: BoxFit.fitWidth, + fit: BoxFit.cover, ), ), // Seat Layout diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 7912622d..6d2b16cd 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -92,11 +92,10 @@ class DbEshop { } static Future updateBlueprint(context, BlueprintModel blueprint) async { - var json = blueprint.toJson(); final response = await _supabase.rpc( 'update_blueprint', params: { - 'input_data': json, + 'input_data': blueprint, }, ); diff --git a/lib/services/DialogHelper.dart b/lib/services/DialogHelper.dart index deb5fc0f..fd92f053 100644 --- a/lib/services/DialogHelper.dart +++ b/lib/services/DialogHelper.dart @@ -233,21 +233,29 @@ class DialogHelper{ return result; } - static Future dropFilesHere( + static Future dropFilesHere( BuildContext context, String titleMessage, String confirmButtonMessage, String cancelButtonMessage, ) async { XFile? filePath; - Widget dropFile = DropFile(onFilePathChanged: (f) => filePath = f); + final dropFileWidget = DropFile( + onFilePathChanged: (file) => filePath = file, + ); + await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: Text(titleMessage), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + title: Text( + titleMessage, + ), content: SingleChildScrollView( - child: dropFile, + child: dropFileWidget, ), actions: [ TextButton( @@ -257,8 +265,8 @@ class DialogHelper{ }, child: Text(cancelButtonMessage), ), - TextButton( - onPressed: () async { + ElevatedButton( + onPressed: () { Navigator.pop(context); }, child: Text(confirmButtonMessage), @@ -267,10 +275,12 @@ class DialogHelper{ ); }, ); + return filePath; } + static Future showNotificationPermissionDialog(BuildContext context) async { bool result = false; await showDialog( diff --git a/lib/widgets/DropFile.dart b/lib/widgets/DropFile.dart index 23f58a89..a9ac6233 100644 --- a/lib/widgets/DropFile.dart +++ b/lib/widgets/DropFile.dart @@ -7,15 +7,14 @@ class DropFile extends StatefulWidget { const DropFile({Key? key, required this.onFilePathChanged}) : super(key: key); final void Function(XFile) onFilePathChanged; + @override _DropFileState createState() => _DropFileState(); } class _DropFileState extends State { XFile? file; - bool _dragging = false; - Offset? offset; @override @@ -27,13 +26,14 @@ class _DropFileState extends State { widget.onFilePathChanged(file!); }); - - debugPrint('onDragDone:'); - for (final file in detail.files) { - debugPrint(' ${file.path} ${file.name}' - ' ${await file.lastModified()}' - ' ${await file.length()}' - ' ${file.mimeType}'); + debugPrint('File dropped:'); + for (final droppedFile in detail.files) { + debugPrint( + ' ${droppedFile.path} ${droppedFile.name} ' + ' ${await droppedFile.lastModified()} ' + ' ${await droppedFile.length()} ' + ' ${droppedFile.mimeType}', + ); } }, onDragUpdated: (details) { @@ -54,18 +54,60 @@ class _DropFileState extends State { }); }, child: Container( - height: 200, - width: 200, - color: _dragging ? Colors.blue.withOpacity(0.4) : Colors.black26, - child: Stack( - children: [ - if (file == null) - Center(child: Text("Drop files here").tr()) - else - Text(file?.path??"") - ], + height: 250, + width: double.infinity, + margin: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: _dragging ? Colors.blue.withOpacity(0.2) : Colors.grey.shade200, + border: Border.all( + color: _dragging ? Colors.blue : Colors.grey.shade400, + width: 2, + ), + ), + child: Center( + child: file == null + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.cloud_upload, + size: 50, + color: _dragging ? Colors.blue : Colors.grey, + ), + const SizedBox(height: 12), + Text( + "Drop file here".tr(), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: _dragging ? Colors.blue : Colors.black54, + ), + ), + ], + ) + : Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.insert_drive_file, + size: 50, + color: Colors.green, + ), + const SizedBox(height: 12), + Text( + file!.name, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + ), + ], + ), ), ), ); } -} \ No newline at end of file +} diff --git a/scripts/database/eshop/get_blueprint_editor.sql b/scripts/database/eshop/get_blueprint_editor.sql index 8619c0ca..a9c1d6b1 100644 --- a/scripts/database/eshop/get_blueprint_editor.sql +++ b/scripts/database/eshop/get_blueprint_editor.sql @@ -32,6 +32,8 @@ BEGIN 'created_at', b.created_at, 'data', b.data, 'title', b.title, + 'occasion', b.occasion, + 'organization', b.organization, 'configuration', b.configuration, 'objects', b.objects, 'groups', b.groups, diff --git a/scripts/database/eshop/update_blueprint.sql b/scripts/database/eshop/update_blueprint.sql index fd6fffcc..e97035e2 100644 --- a/scripts/database/eshop/update_blueprint.sql +++ b/scripts/database/eshop/update_blueprint.sql @@ -46,24 +46,10 @@ BEGIN IF blueprint_id IS NULL THEN -- Create a new blueprint INSERT INTO eshop.blueprints ( - created_at, - data, - title, - organization, - configuration, - objects, - groups, - occasion + created_at ) VALUES ( - now, - input_data->'data', - input_data->>'title', - organization_id, - input_data->'configuration', - '[]'::JSONB, - input_data->'groups', - occasion_id + now ) RETURNING id INTO blueprint_id; END IF; @@ -189,12 +175,13 @@ BEGIN -- Update blueprint's objects field UPDATE eshop.blueprints SET - objects = updated_objects, updated_at = now, data = input_data->'data', title = input_data->>'title', configuration = input_data->'configuration', groups = input_data->'groups', + objects = updated_objects, + background_svg = (input_data->>'background_svg')::TEXT, occasion = occasion_id, organization = organization_id WHERE id = blueprint_id; From 78851795f995ed7d482041af72ceab5a85643e45 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:24:26 +0100 Subject: [PATCH 095/159] background for user --- lib/widgets/SeatReservationWidget.dart | 1 + scripts/database/eshop/get_blueprint.sql | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index 3ce9bb69..aefd96e5 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -139,6 +139,7 @@ class _SeatReservationWidgetState extends State { seatSize: SeatReservationWidget.boxSize, currentObjects: blueprint!.objects!, allBoxes: allObjects, + backgroundSvg: blueprint!.backgroundSvg ), ), ), diff --git a/scripts/database/eshop/get_blueprint.sql b/scripts/database/eshop/get_blueprint.sql index d09a4dd6..9faa4f4e 100644 --- a/scripts/database/eshop/get_blueprint.sql +++ b/scripts/database/eshop/get_blueprint.sql @@ -41,7 +41,7 @@ BEGIN 'configuration', b.configuration, 'objects', b.objects, 'groups', b.groups, - 'background_svg', b.groups + 'background_svg', b.background_svg ) INTO blueprintData FROM eshop.blueprints b From d2c36d6c93d2c6ac48e7237b91b9855706a565fd Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:32:03 +0100 Subject: [PATCH 096/159] modified toast position --- lib/services/ToastHelper.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/ToastHelper.dart b/lib/services/ToastHelper.dart index 1fac4931..a1b3012c 100644 --- a/lib/services/ToastHelper.dart +++ b/lib/services/ToastHelper.dart @@ -16,6 +16,6 @@ class ToastHelper { color = Colors.red; webColor = "#d8392b"; } - Fluttertoast.showToast(msg: value, timeInSecForIosWeb: 3, webBgColor: webColor, backgroundColor: color); + Fluttertoast.showToast(msg: value, timeInSecForIosWeb: 3, webBgColor: webColor, backgroundColor: color, webPosition: "left"); } } \ No newline at end of file From b3ee998feabbf602f82aa0aba4e8c36e5b56ae9f Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:46:41 +0100 Subject: [PATCH 097/159] header and footer --- lib/dataModels/FormModel.dart | 12 ++++++++++-- lib/dataModels/Tb.dart | 2 ++ lib/pages/FormPage.dart | 11 +++++++++++ scripts/database/eshop/get_form.sql | 2 ++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/dataModels/FormModel.dart b/lib/dataModels/FormModel.dart index 04203411..13d55d76 100644 --- a/lib/dataModels/FormModel.dart +++ b/lib/dataModels/FormModel.dart @@ -12,8 +12,9 @@ class FormModel { int? deadlineDuration; bool? isOpen; String? accountNumber; - String? secret; + String? header; + String? footer; FormModel({ this.id, @@ -27,7 +28,9 @@ class FormModel { this.deadlineDuration, this.isOpen, this.accountNumber, - this.secret + this.secret, + this.header, + this.footer, }); factory FormModel.fromJson(Map json) { @@ -46,6 +49,8 @@ class FormModel { isOpen: json[Tb.forms.is_open], accountNumber: json['account_number'], secret: json['secret'], + header: json[Tb.forms.header], + footer: json[Tb.forms.footer], ); } @@ -61,5 +66,8 @@ class FormModel { Tb.forms.deadline_duration_seconds: deadlineDuration, Tb.forms.is_open: isOpen, 'account_number': accountNumber, + 'secret': secret, + Tb.forms.header: header, + Tb.forms.footer: footer, }; } diff --git a/lib/dataModels/Tb.dart b/lib/dataModels/Tb.dart index 27c92900..118b2d83 100644 --- a/lib/dataModels/Tb.dart +++ b/lib/dataModels/Tb.dart @@ -326,4 +326,6 @@ class FormsTb { String get bank_account => "bank_account"; String get deadline_duration_seconds => "deadline_duration_seconds"; String get is_open => "is_open"; + String get header => "header"; + String get footer => "footer"; } \ No newline at end of file diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 6ed6db4d..2bff8afe 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -18,6 +18,7 @@ import 'package:fstapp/widgets/ButtonsHelper.dart'; import 'package:flutter/services.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:fstapp/services/ToastHelper.dart'; +import 'package:fstapp/widgets/HtmlView.dart'; @RoutePage() class FormPage extends StatefulWidget { @@ -135,6 +136,11 @@ class _FormPageState extends State { child: AutofillGroup( child: Column( children: [ + if(form!.footer!=null) + Column(children: [ + HtmlView(html: form!.header!, isSelectable: true,), + const SizedBox(height: 16), + ],), ...FormHelper.getAllFormFields(context, _formKey, form!, _updateTotalPrice), const SizedBox(height: 16), if (_totalPrice > 0) @@ -148,6 +154,11 @@ class _FormPageState extends State { ), ), const SizedBox(height: 16), + if(form!.footer!=null) + Column(children: [ + HtmlView(html: form!.footer!, isSelectable: true,), + const SizedBox(height: 16), + ],), ButtonsHelper.bigButton( context: context, onPressed: _isLoading diff --git a/scripts/database/eshop/get_form.sql b/scripts/database/eshop/get_form.sql index 596aad40..65819704 100644 --- a/scripts/database/eshop/get_form.sql +++ b/scripts/database/eshop/get_form.sql @@ -26,6 +26,8 @@ BEGIN 'created_at', f.created_at, 'data', f.data, 'type', f.type, + 'header', f.header, + 'footer', f.footer, 'occasion', f.occasion, 'blueprint', f.blueprint, 'deadline_duration_seconds', f.deadline_duration_seconds, From cdc220c54617310c1da7d03c4c2047bc4073b430 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:06:26 +0100 Subject: [PATCH 098/159] full spot string --- lib/dataModelsEshop/BlueprintObjectModel.dart | 2 +- lib/services/FormHelper.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index b49a60d6..37a95ea2 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -34,7 +34,7 @@ class BlueprintObjectModel { @override toString() { - return "stůl ${group?.title}, sedadlo $title"; + return "Stůl ${group?.title}, Sedadlo $title"; } toShortString() { diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 18cef5d9..4029f76e 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -292,7 +292,7 @@ class FormHelper { ]), builder: (FormFieldState field) { SeatModel? seat = field.value; - textController.text = seat?.objectModel?.title ?? metaEmpty; + textController.text = seat?.objectModel?.toString() ?? metaEmpty; return TextField( controller: textController, readOnly: true, @@ -326,7 +326,7 @@ class FormHelper { updateTotalPrice?.call(); field.didChange(selectedSeat); textController.text = - selectedSeat.objectModel?.title ?? metaEmpty; + selectedSeat.objectModel?.toString() ?? metaEmpty; field.validate(); } }, From 8a0bb93928d36402b53213a1a93a5a610df48944 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:33:41 +0100 Subject: [PATCH 099/159] adjust --- lib/components/seatReservation/widgets/SeatLayoutWidget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index 12168d21..dd40462d 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -92,7 +92,7 @@ class _SeatLayoutWidgetState extends State { int layoutHeight = widget.stateModel.rows * widget.stateModel.seatSize; return InteractiveViewer( - minScale: 0.1, + minScale: 0.5, maxScale: 5, boundaryMargin: const EdgeInsets.all(double.infinity), constrained: false, From e5bcaf29f215c9cefa9217eb75f88ec7131e2158 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:44:53 +0100 Subject: [PATCH 100/159] formholder --- lib/dataModels/FormFields.dart | 175 ++++++++++++++++++++++++++++ lib/dataModels/FormOptionModel.dart | 12 +- lib/pages/FormPage.dart | 22 ++-- lib/pages/SignupPage.dart | 14 ++- lib/services/FormHelper.dart | 101 ++++++++-------- 5 files changed, 254 insertions(+), 70 deletions(-) create mode 100644 lib/dataModels/FormFields.dart diff --git a/lib/dataModels/FormFields.dart b/lib/dataModels/FormFields.dart new file mode 100644 index 00000000..5cc746a6 --- /dev/null +++ b/lib/dataModels/FormFields.dart @@ -0,0 +1,175 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:fstapp/dataModels/FormOptionModel.dart'; + +class FieldHolder { + static const String metaIsRequired = "is_required"; + static const String metaType = "type"; + static const String metaValue = "value"; + + final bool isRequired; + final String fieldType; + dynamic value; + + FieldHolder({ + required this.fieldType, + required this.isRequired, + this.value, + }); + + factory FieldHolder.fromJson(Map json) { + return FieldHolder( + isRequired: json[metaIsRequired] ?? false, + fieldType: json[metaType], + value: json[metaValue], + ); + } + + Map toJson() { + return { + metaIsRequired: isRequired, + metaType: fieldType, + metaValue: value, + }; + } + + @override + String toString() => 'FieldHolder(type: $fieldType, value: $value)'; +} + +class OptionsFieldHolder extends FieldHolder { + static const String metaLabel = "label"; + static const String metaOptionsType = "optionsType"; + static const String metaOptions = "options"; + + final String label; + final String optionsType; + final List options; + + OptionsFieldHolder({ + required String fieldType, + dynamic value, + required this.options, + required this.label, + required this.optionsType, + }) : super(fieldType: fieldType, value: value, isRequired: true); + + factory OptionsFieldHolder.fromJson(Map json) { + return OptionsFieldHolder( + fieldType: json[FieldHolder.metaType], + value: json[FieldHolder.metaValue], + label: json[metaLabel], + options: List.from(json[metaOptions].map((o)=>FormOptionModel.fromJson(o))), + optionsType: json[metaOptionsType], + ); + } + + @override + Map toJson() { + return super.toJson() + ..addAll({ + metaLabel: label, + metaOptionsType: optionsType, + }); + } + + @override + String toString() => + 'OptionsFieldHolder(fieldType: $fieldType, id: $label, name: $optionsType)'; +} + +class TicketHolder extends FieldHolder { + static const String metaFields = "fields"; + static const String metaTicket = "ticket"; + static const String metaMaxTickets = "max_tickets"; + List> ticketValues = []; + + final int maxTickets; + final List fields; + + TicketHolder({ + required super.fieldType, + required this.maxTickets, + required this.fields, + super.isRequired = true, + }); + + factory TicketHolder.fromJson(Map json) { + return TicketHolder( + maxTickets: json[metaMaxTickets] ?? 1, + fieldType: json[FieldHolder.metaType], + fields: (json[metaFields] as List>?) + ?.map((e) => FormHolder.determineFieldType(e)) + .toList() ?? [], + ); + } + + @override + Map toJson() { + return super.toJson() + ..addAll({ + metaFields: fields.map((e) => e.toJson()).toList(), + }); + } + + @override + String toString() => + 'TicketHolder(fields: $fields)'; +} + +class FormHolderController{ + final String? secret; + final int? blueprintId; + final GlobalKey globalKey; + final String? formKey; + void Function()? updateTotalPrice; + + FormHolderController({this.secret, this.blueprintId, required this.globalKey, this.formKey, this.updateTotalPrice}); +} + +class FormHolder { + static const String metaFields = "fields"; + + FormHolderController? controller; + final List fields; + + + FormHolder({required this.fields}); + + factory FormHolder.fromJson(Map json) { + return FormHolder( + fields: (json[metaFields] as List>?) + ?.map((e) => determineFieldType(e)) + .toList() ?? + [], + ); + } + + static FieldHolder determineFieldType(Map json) { + final fieldType = json[FieldHolder.metaType]; + if (fieldType == TicketHolder.metaTicket) { + return TicketHolder.fromJson(json); + } else if (fieldType == OptionsFieldHolder.metaOptions) { + return OptionsFieldHolder.fromJson(json); + } else { + return FieldHolder.fromJson(json); + } + } + + Map toJson() { + return { + metaFields: fields.map((e) => e.toJson()).toList(), + }; + } + + void addField(FieldHolder field) { + fields.add(field); + } + + void addTicket(TicketHolder ticket) { + fields.add(ticket); + } + + @override + String toString() => 'FormHolder(fields: $fields)'; +} diff --git a/lib/dataModels/FormOptionModel.dart b/lib/dataModels/FormOptionModel.dart index 83cb48a2..8c11a3ca 100644 --- a/lib/dataModels/FormOptionModel.dart +++ b/lib/dataModels/FormOptionModel.dart @@ -3,12 +3,20 @@ class FormOptionModel { static const String metaOptionsName = "name"; static const String metaOptionsPrice = "price"; - FormOptionModel(this.id, this.name, {this.price = 0.0}); - final String name; final String id; final double price; + FormOptionModel(this.id, this.name, {this.price = 0.0}); + + factory FormOptionModel.fromJson(Map json) { + return FormOptionModel( + json[metaOptionsId], + json[metaOptionsName], + price: (json[metaOptionsPrice] as num?)?.toDouble() ?? 0.0, + ); + } + @override String toString() => name; diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 2bff8afe..9d3f7bcb 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -4,6 +4,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:fstapp/dataModels/FormFields.dart'; import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataModels/FormOptionModel.dart'; import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; @@ -36,6 +37,7 @@ class _FormPageState extends State { bool _isSendSuccess = false; double _totalPrice = 0.0; // Total price Map? formResult; + FormHolder? formHolder; FormModel? form; final _formKey = GlobalKey(); @@ -68,8 +70,7 @@ class _FormPageState extends State { if (field[FormHelper.metaType] == FormHelper.fieldTypeTicket) { var ticketDataList = FormHelper.getFieldData( _formKey, - FormHelper.fieldTypeTicket, - ticketKeys: FormHelper.ticketKeys + FormHelper.fieldTypeTicket ) ?? []; for (var ticketData in ticketDataList) { @@ -127,7 +128,7 @@ class _FormPageState extends State { ), ), ) - : form?.data == null + : formHolder == null ? const Center( child: CircularProgressIndicator(), ) @@ -141,7 +142,7 @@ class _FormPageState extends State { HtmlView(html: form!.header!, isSelectable: true,), const SizedBox(height: 16), ],), - ...FormHelper.getAllFormFields(context, _formKey, form!, _updateTotalPrice), + ...FormHelper.getAllFormFields(context, _formKey, formHolder!), const SizedBox(height: 16), if (_totalPrice > 0) Text( @@ -169,8 +170,7 @@ class _FormPageState extends State { setState(() { _isLoading = true; }); - var data = FormHelper.getDataFromForm( - _formKey, form?.data?[FormHelper.metaFields]); + var data = FormHelper.getDataFromForm(formHolder!); data = FormHelper.replaceSpotWithId(data); data[FormHelper.metaSecret] = form!.secret; @@ -261,14 +261,14 @@ class _FormPageState extends State { // Fetching items var allItems = await DbEshop.getProducts(context, form!.occasion!); // New fields to replace existing ones - List updatedFields = []; + List> updatedFields = []; // Loop through the fields in form.data for (var field in form?.data![FormHelper.metaFields]) { // Check if the field is a ticket if (field[FormHelper.metaType] == FormHelper.fieldTypeTicket) { // Process the fields inside the ticket - List updatedTicketFields = []; + List> updatedTicketFields = []; for (var ticketField in field[FormHelper.metaFields]) { // Check if the ticket field has an optionsType if (ticketField.containsKey(FormHelper.metaOptionsType)) { @@ -292,9 +292,11 @@ class _FormPageState extends State { updatedFields.add(field); } } + Map json = {FormHelper.metaFields : updatedFields}; - - form?.data![FormHelper.metaFields] = updatedFields; + formHolder = FormHolder.fromJson(json); + formHolder!.controller = FormHolderController(secret: form!.secret, blueprintId: form!.blueprint, globalKey: _formKey, formKey: form!.formKey!, updateTotalPrice: _updateTotalPrice); + form!.data![FormHelper.metaFields] = updatedFields; setState(() { _isLoading = false; diff --git a/lib/pages/SignupPage.dart b/lib/pages/SignupPage.dart index bac23cd8..cf150020 100644 --- a/lib/pages/SignupPage.dart +++ b/lib/pages/SignupPage.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/cupertino.dart'; import 'package:fstapp/RouterService.dart'; -import 'package:fstapp/dataModels/FormModel.dart'; +import 'package:fstapp/dataModels/FormFields.dart'; import 'package:fstapp/dataServices/AuthService.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:fstapp/services/FormHelper.dart'; @@ -26,8 +26,9 @@ class _SignupPageState extends State { bool _isLoading = false; bool _isRegistrationSuccess = false; Map? fieldsData; + FormHolder? formHolder; - final FormModel form = FormModel(data: {FormHelper.metaFields: + Map entryJson = {FormHelper.metaFields: [ { FormHelper.metaType : FormHelper.fieldTypeName, FormHelper.IS_REQUIRED: true}, { FormHelper.metaType : FormHelper.fieldTypeSurname, FormHelper.IS_REQUIRED: true}, @@ -35,7 +36,8 @@ class _SignupPageState extends State { { FormHelper.metaType : FormHelper.fieldTypeEmail, FormHelper.IS_REQUIRED: true}, { FormHelper.metaType : FormHelper.fieldTypeCity, FormHelper.IS_REQUIRED: true}, { FormHelper.metaType : FormHelper.fieldTypeBirthYear}, - ]}); + ]}; + final _formKey = GlobalKey(); @override @@ -45,6 +47,8 @@ class _SignupPageState extends State { @override Widget build(BuildContext context) { + formHolder = FormHolder.fromJson(entryJson); + formHolder!.controller = FormHolderController(globalKey: _formKey); return Scaffold( appBar: AppBar( centerTitle: true, @@ -84,7 +88,7 @@ class _SignupPageState extends State { child: AutofillGroup( child: Column( children: [ - ...FormHelper.getAllFormFields(context, _formKey, form.data![FormHelper.metaFields]), + ...FormHelper.getAllFormFields(context, formHolder!.controller!.globalKey!, formHolder!), const SizedBox( height: 16, ), @@ -96,7 +100,7 @@ class _SignupPageState extends State { setState(() { _isLoading = true; }); - var data = FormHelper.getDataFromForm(_formKey, form.data![FormHelper.metaFields]); + var data = FormHelper.getDataFromForm(formHolder!); fieldsData = data; var resp = await AuthService.register(data); if (resp["code"] == 200) { diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 4029f76e..9e7a0547 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -1,6 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:fstapp/components/seatReservation/model/SeatModel.dart'; -import 'package:fstapp/dataModels/FormModel.dart'; +import 'package:fstapp/dataModels/FormFields.dart'; import 'package:fstapp/dataModels/FormOptionModel.dart'; import 'package:fstapp/dataModels/UserInfoModel.dart'; import 'package:flutter/material.dart'; @@ -55,15 +55,15 @@ class FormHelper { static double fontSizeFactor = 1.2; - static List> ticketValues = []; + static List> ticketValues = []; static List> ticketKeys = []; - static List getAllFormFields(BuildContext context, GlobalKey formKey, FormModel formData, [void Function()? updateTotalPrice]) { - return formData.data?[FormHelper.metaFields].map((field) => createFormField(context, formKey, formData, field, updateTotalPrice)).toList(); + static List getAllFormFields(BuildContext context, GlobalKey formKey, FormHolder formHolder) { + return formHolder.fields.map((field) => createFormField(context, formKey, formHolder, field)).toList(); } - static List getFormFields(BuildContext context, GlobalKey formKey, FormModel formData, dynamic fields, [void Function()? updateTotalPrice]) { - return fields.map((field) => createFormField(context, formKey, formData, field, updateTotalPrice)).toList(); + static List getFormFields(BuildContext context, GlobalKey formKey, FormHolder formHolder, List fields) { + return fields.map((field) => createFormField(context, formKey, formHolder, field)).toList(); } static bool saveAndValidate(GlobalKey key){ @@ -77,16 +77,16 @@ class FormHelper { } // Retrieve form data by iterating over defined fields - static Map getDataFromForm(GlobalKey key, dynamic fields) { + static Map getDataFromForm(FormHolder formHolder) { Map toReturn = {}; - for (var k in fields) { - toReturn[k[metaType]] = getFieldData(key, k[metaType], ticketKeys: ticketKeys); + for (var k in formHolder.fields) { + toReturn[k.fieldType] = getFieldData(formHolder.controller!.globalKey, k.fieldType); } return toReturn; } // Determine the correct data from the form based on type - static dynamic getFieldData(GlobalKey formKey, String fieldType, {List>? ticketKeys}) { + static dynamic getFieldData(GlobalKey formKey, String fieldType) { var fieldValue = formKey.currentState?.fields[fieldType]?.value; if (fieldType == fieldTypeSex) { @@ -105,19 +105,17 @@ class FormHelper { // Collect ticket data from multiple ticket forms List> tickets = []; - if (ticketKeys != null) { - for (int i = 0; i < ticketKeys.length; i++) { - final ticketKey = ticketKeys[i]; - if (ticketKey.currentState == null) continue; + for (int i = 0; i < ticketKeys.length; i++) { + final ticketKey = ticketKeys[i]; + if (ticketKey.currentState == null) continue; - Map ticketData = {}; + Map ticketData = {}; - for (MapEntry, dynamic>> ticketSubField in ticketKey.currentState?.fields.entries ?? []) { - var subFieldType = ticketSubField.key; - ticketData[subFieldType] = getFieldData(ticketKey, subFieldType, ticketKeys: ticketKeys); - } - tickets.add(ticketData); + for (MapEntry, dynamic>> ticketSubField in ticketKey.currentState?.fields.entries ?? []) { + var subFieldType = ticketSubField.key; + ticketData[subFieldType] = getFieldData(ticketKey, subFieldType); } + tickets.add(ticketData); } return tickets; @@ -131,9 +129,9 @@ class FormHelper { // Create individual form field widget based on configuration - static Widget createFormField(BuildContext context, GlobalKey formKey, FormModel form, Map field, [void Function()? updateTotalPrice]) { - final bool isRequiredField = field[IS_REQUIRED] ?? false; - switch (field[metaType]) { + static Widget createFormField(BuildContext context, GlobalKey formKey, FormHolder formHolder, FieldHolder field) { + final bool isRequiredField = field.isRequired; + switch (field.fieldType) { case fieldTypeNote: return buildTextField(fieldTypeNote, noteLabel(), isRequiredField, []); case fieldTypeName: @@ -143,34 +141,32 @@ class FormHelper { case fieldTypeCity: return buildTextField(fieldTypeCity, cityLabel(), isRequiredField, [AutofillHints.addressCity]); case fieldTypeSpot: - return buildSpotField(context, formKey, form, fieldTypeSpot, spotLabel(), updateTotalPrice); + return buildSpotField(context, formKey, formHolder, fieldTypeSpot, spotLabel()); case fieldTypeEmail: return buildEmailField(isRequiredField); case fieldTypeSex: return buildRadioField(fieldTypeSex, sexLabel(), isRequiredField); case fieldTypeOptions: - return buildGenericOptions(field[metaOptionsType], field[metaLabel], field[metaOptions]); + var opt = field as OptionsFieldHolder; + return buildGenericOptions(opt.optionsType, opt.label, field.options); case fieldTypeBirthYear: return buildBirthYearField(fieldTypeBirthYear, birthYearLabel(), isRequiredField); case fieldTypeTicket: - return buildTicketField(formKey, form, field, ticketValues, ticketKeys, updateTotalPrice); + var ticketHolder = field as TicketHolder; + return buildTicketField(formHolder, ticketHolder); default: return const SizedBox.shrink(); } } static Widget buildTicketField( - GlobalKey formKey, - FormModel formData, - Map field, - List> ticketValues, - List> ticketKeys, - [void Function()? updateTotalPrice] // Pass the updateTotalPrice function + FormHolder formHolder, + TicketHolder ticket ) { - final maxTickets = field[FormHelper.metaMaxTickets] ?? 1; + final maxTickets = ticket.maxTickets; if (ticketValues.isEmpty) { - ticketValues.add({...field}); + ticketValues.add(ticket.fields); ticketKeys.add(GlobalKey()); } @@ -179,11 +175,11 @@ class FormHelper { void addTicket() { if (ticketValues.length < maxTickets) { setState(() { - ticketValues.add({...field}); + ticketValues.add(ticket.fields); ticketKeys.add(GlobalKey()); }); } - updateTotalPrice?.call(); // Update the total price when adding a ticket + formHolder.controller!.updateTotalPrice?.call(); // Update the total price when adding a ticket } void removeTicket(int index) { @@ -193,7 +189,7 @@ class FormHelper { ticketKeys.removeAt(index); }); } - updateTotalPrice?.call(); // Update the total price when removing a ticket + formHolder.controller!.updateTotalPrice?.call(); } return Column( @@ -219,7 +215,7 @@ class FormHelper { Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Align( - alignment: Alignment.center, // Center the title across the entire row + alignment: Alignment.center, child: Text( "Ticket {number}".tr(namedArgs: {"number": (i + 1).toString()}), // Use translated string style: TextStyle( @@ -242,9 +238,9 @@ class FormHelper { ), FormBuilder( key: ticketKeys[i], // Assign the corresponding key - onChanged: updateTotalPrice, // Trigger price update on change + onChanged: formHolder.controller!.updateTotalPrice, // Trigger price update on change child: Column( - children: getFormFields(context, ticketKeys[i], formData, ticketValues[i][FormHelper.metaFields]), + children: getFormFields(context, ticketKeys[i], formHolder, ticketValues[i]), ), ), ], @@ -272,10 +268,9 @@ class FormHelper { static Widget buildSpotField( BuildContext context, GlobalKey formKey, - FormModel form, + FormHolder formHolder, String name, - String label, - void Function()? updateTotalPrice) { + String label) { // Create a TextEditingController to control the displayed text TextEditingController textController = TextEditingController(); @@ -313,9 +308,9 @@ class FormHelper { transitionDuration: const Duration(milliseconds: 300), pageBuilder: (context, __, ___) { return SeatReservationWidget( - secret: form.secret!, - formDataKey: form.formKey!, - blueprintId: form.blueprint!, + secret: formHolder.controller!.secret!, + formDataKey: formHolder.controller!.formKey!, + blueprintId: formHolder.controller!.blueprintId!, selectedSeat: field.value, ); }, @@ -323,7 +318,7 @@ class FormHelper { if (selectedSeat != null) { // Update the form field and display value - updateTotalPrice?.call(); + formHolder.controller!.updateTotalPrice?.call(); field.didChange(selectedSeat); textController.text = selectedSeat.objectModel?.toString() ?? metaEmpty; @@ -379,18 +374,18 @@ class FormHelper { } static FormBuilderRadioGroup buildGenericOptions( - String name, String label, List optionsIn) { + String name, String label, List optionsIn) { List> options = []; for (var o in optionsIn) { options.add(FormBuilderFieldOption( value: FormOptionModel( - o[FormOptionModel.metaOptionsId], - o[FormOptionModel.metaOptionsName], - price: o[FormOptionModel.metaOptionsPrice] ?? 0.0, // Use price from the option or default to 0.0 + o.id, + o.name, + price: o.price, ), child: Text( - o[FormOptionModel.metaOptionsName], + o.name, style: TextStyle(fontSize: 14.0 * fontSizeFactor), // Adjust font size dynamically ), )); @@ -399,7 +394,7 @@ class FormHelper { // Use the first option as the default initial value final initialValue = options.isNotEmpty ? options.first.value : null; - return FormBuilderRadioGroup( + return FormBuilderRadioGroup( name: name, decoration: InputDecoration(labelText: label, labelStyle: StylesConfig.textStyleBig.copyWith(fontSize: 16 * fontSizeFactor),), From 996ea7c8e73d16dad2d4594ba84b2562521b1dce Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Fri, 6 Dec 2024 18:58:32 +0100 Subject: [PATCH 101/159] ticketkeys and ticketvalues --- lib/dataModels/FormFields.dart | 4 +++ lib/pages/FormPage.dart | 5 ++-- lib/services/FormHelper.dart | 46 ++++++++++++++++------------------ 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/lib/dataModels/FormFields.dart b/lib/dataModels/FormFields.dart index 5cc746a6..7683fd85 100644 --- a/lib/dataModels/FormFields.dart +++ b/lib/dataModels/FormFields.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:fstapp/dataModels/FormOptionModel.dart'; @@ -82,7 +83,9 @@ class TicketHolder extends FieldHolder { static const String metaFields = "fields"; static const String metaTicket = "ticket"; static const String metaMaxTickets = "max_tickets"; + List> ticketValues = []; + List> ticketKeys = []; final int maxTickets; final List fields; @@ -133,6 +136,7 @@ class FormHolder { FormHolderController? controller; final List fields; + TicketHolder? getTicket() => fields.firstWhereOrNull((f)=>f is TicketHolder) as TicketHolder; FormHolder({required this.fields}); diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 9d3f7bcb..2828f41d 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -70,7 +70,8 @@ class _FormPageState extends State { if (field[FormHelper.metaType] == FormHelper.fieldTypeTicket) { var ticketDataList = FormHelper.getFieldData( _formKey, - FormHelper.fieldTypeTicket + FormHelper.fieldTypeTicket, + formHolder! ) ?? []; for (var ticketData in ticketDataList) { @@ -166,7 +167,7 @@ class _FormPageState extends State { ? null : () async { TextInput.finishAutofillContext(); - if (FormHelper.saveAndValidate(_formKey)) { + if (FormHelper.saveAndValidate(formHolder!)) { setState(() { _isLoading = true; }); diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 9e7a0547..317bbe10 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -55,9 +55,6 @@ class FormHelper { static double fontSizeFactor = 1.2; - static List> ticketValues = []; - static List> ticketKeys = []; - static List getAllFormFields(BuildContext context, GlobalKey formKey, FormHolder formHolder) { return formHolder.fields.map((field) => createFormField(context, formKey, formHolder, field)).toList(); } @@ -66,9 +63,9 @@ class FormHelper { return fields.map((field) => createFormField(context, formKey, formHolder, field)).toList(); } - static bool saveAndValidate(GlobalKey key){ - bool toReturn = key.currentState?.saveAndValidate() ?? false; - for(var k in ticketKeys){ + static bool saveAndValidate(FormHolder formHolder){ + bool toReturn = formHolder.controller!.globalKey.currentState?.saveAndValidate() ?? false; + for(var k in formHolder.getTicket()!.ticketKeys){ if(!(k.currentState?.saveAndValidate() ?? false)){ toReturn = false; } @@ -80,13 +77,13 @@ class FormHelper { static Map getDataFromForm(FormHolder formHolder) { Map toReturn = {}; for (var k in formHolder.fields) { - toReturn[k.fieldType] = getFieldData(formHolder.controller!.globalKey, k.fieldType); + toReturn[k.fieldType] = getFieldData(formHolder.controller!.globalKey, k.fieldType, formHolder); } return toReturn; } // Determine the correct data from the form based on type - static dynamic getFieldData(GlobalKey formKey, String fieldType) { + static dynamic getFieldData(GlobalKey formKey, String fieldType, FormHolder formHolder) { var fieldValue = formKey.currentState?.fields[fieldType]?.value; if (fieldType == fieldTypeSex) { @@ -105,15 +102,16 @@ class FormHelper { // Collect ticket data from multiple ticket forms List> tickets = []; - for (int i = 0; i < ticketKeys.length; i++) { - final ticketKey = ticketKeys[i]; + var ticket = formHolder.getTicket()!; + for (int i = 0; i < ticket.ticketKeys.length; i++) { + final ticketKey = ticket.ticketKeys[i]; if (ticketKey.currentState == null) continue; Map ticketData = {}; for (MapEntry, dynamic>> ticketSubField in ticketKey.currentState?.fields.entries ?? []) { var subFieldType = ticketSubField.key; - ticketData[subFieldType] = getFieldData(ticketKey, subFieldType); + ticketData[subFieldType] = getFieldData(ticketKey, subFieldType, formHolder); } tickets.add(ticketData); } @@ -165,28 +163,28 @@ class FormHelper { ) { final maxTickets = ticket.maxTickets; - if (ticketValues.isEmpty) { - ticketValues.add(ticket.fields); - ticketKeys.add(GlobalKey()); + if (ticket.ticketValues.isEmpty) { + ticket.ticketValues.add(ticket.fields); + ticket.ticketKeys.add(GlobalKey()); } return StatefulBuilder( builder: (context, setState) { void addTicket() { - if (ticketValues.length < maxTickets) { + if (ticket.ticketValues.length < maxTickets) { setState(() { - ticketValues.add(ticket.fields); - ticketKeys.add(GlobalKey()); + ticket.ticketValues.add(ticket.fields); + ticket.ticketKeys.add(GlobalKey()); }); } formHolder.controller!.updateTotalPrice?.call(); // Update the total price when adding a ticket } void removeTicket(int index) { - if (ticketValues.length > 1) { + if (ticket.ticketValues.length > 1) { setState(() { - ticketValues.removeAt(index); - ticketKeys.removeAt(index); + ticket.ticketValues.removeAt(index); + ticket.ticketKeys.removeAt(index); }); } formHolder.controller!.updateTotalPrice?.call(); @@ -195,7 +193,7 @@ class FormHelper { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - for (int i = 0; i < ticketValues.length; i++) + for (int i = 0; i < ticket.ticketValues.length; i++) Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Align( @@ -237,10 +235,10 @@ class FormHelper { ], ), FormBuilder( - key: ticketKeys[i], // Assign the corresponding key + key: ticket.ticketKeys[i], // Assign the corresponding key onChanged: formHolder.controller!.updateTotalPrice, // Trigger price update on change child: Column( - children: getFormFields(context, ticketKeys[i], formHolder, ticketValues[i]), + children: getFormFields(context, ticket.ticketKeys[i], formHolder, ticket.ticketValues[i]), ), ), ], @@ -248,7 +246,7 @@ class FormHelper { ), ), ), - if (ticketValues.length < maxTickets) + if (ticket.ticketValues.length < maxTickets) Align( alignment: Alignment.center, child: ElevatedButton.icon( From 791d8bc5cf59b4fbf5b9d18e2437fa83e148f197 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Fri, 6 Dec 2024 23:40:45 +0100 Subject: [PATCH 102/159] getting data from form --- lib/dataModels/FormFields.dart | 17 +++++++++++------ lib/pages/FormPage.dart | 11 +++++------ lib/services/FormHelper.dart | 34 ++++++++++++++++++++++------------ 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/lib/dataModels/FormFields.dart b/lib/dataModels/FormFields.dart index 7683fd85..6776a6fb 100644 --- a/lib/dataModels/FormFields.dart +++ b/lib/dataModels/FormFields.dart @@ -2,6 +2,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:fstapp/dataModels/FormOptionModel.dart'; +import 'package:fstapp/services/FormHelper.dart'; class FieldHolder { static const String metaIsRequired = "is_required"; @@ -10,19 +11,23 @@ class FieldHolder { final bool isRequired; final String fieldType; - dynamic value; + dynamic defaultValue; + dynamic getValue(GlobalKey formKey) => + FormHelper.getFieldData(formKey, this); + + String getFieldTypeValue() => FormHelper.getFieldTypeValue(this); FieldHolder({ required this.fieldType, required this.isRequired, - this.value, + this.defaultValue, }); factory FieldHolder.fromJson(Map json) { return FieldHolder( isRequired: json[metaIsRequired] ?? false, fieldType: json[metaType], - value: json[metaValue], + defaultValue: json[metaValue], ); } @@ -30,12 +35,12 @@ class FieldHolder { return { metaIsRequired: isRequired, metaType: fieldType, - metaValue: value, + metaValue: defaultValue, }; } @override - String toString() => 'FieldHolder(type: $fieldType, value: $value)'; + String toString() => 'FieldHolder(type: $fieldType, value: $defaultValue)'; } class OptionsFieldHolder extends FieldHolder { @@ -53,7 +58,7 @@ class OptionsFieldHolder extends FieldHolder { required this.options, required this.label, required this.optionsType, - }) : super(fieldType: fieldType, value: value, isRequired: true); + }) : super(fieldType: fieldType, defaultValue: value, isRequired: true); factory OptionsFieldHolder.fromJson(Map json) { return OptionsFieldHolder( diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 2828f41d..b4196a71 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -57,21 +57,20 @@ class _FormPageState extends State { _totalPrice = 0.0; // Iterate over all fields and calculate total price - for (var field in form?.data?[FormHelper.metaFields] ?? []) { + for (var field in formHolder!.fields) { // Calculate price for regular options - if (field[FormHelper.metaType] == FormHelper.fieldTypeOptions) { - var selectedOption = _formKey.currentState?.fields[field[FormHelper.metaOptionsType]]?.value; + if (field.fieldType == FormHelper.fieldTypeOptions) { + var selectedOption = field.getValue(formHolder!.controller!.globalKey); if (selectedOption is FormOptionModel) { _totalPrice += selectedOption.price; } } // Calculate price for tickets - if (field[FormHelper.metaType] == FormHelper.fieldTypeTicket) { + if (field.fieldType == FormHelper.fieldTypeTicket) { var ticketDataList = FormHelper.getFieldData( _formKey, - FormHelper.fieldTypeTicket, - formHolder! + field ) ?? []; for (var ticketData in ticketDataList) { diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 317bbe10..c5085d02 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -77,46 +77,56 @@ class FormHelper { static Map getDataFromForm(FormHolder formHolder) { Map toReturn = {}; for (var k in formHolder.fields) { - toReturn[k.fieldType] = getFieldData(formHolder.controller!.globalKey, k.fieldType, formHolder); + toReturn[k.getFieldTypeValue()] = getFieldData(formHolder.controller!.globalKey, k); } return toReturn; } + static String getFieldTypeValue(FieldHolder fieldHolder){ + if(fieldHolder is OptionsFieldHolder){ + return fieldHolder.optionsType; + } + return fieldHolder.fieldType; + } + // Determine the correct data from the form based on type - static dynamic getFieldData(GlobalKey formKey, String fieldType, FormHolder formHolder) { - var fieldValue = formKey.currentState?.fields[fieldType]?.value; + static dynamic getFieldData(GlobalKey formKey, FieldHolder fieldHolder) { + var fieldValue = formKey.currentState?.fields[fieldHolder.fieldType]?.value; - if (fieldType == fieldTypeSex) { + if (fieldHolder.fieldType == fieldTypeSex) { if (fieldValue == null) { return null; } return (fieldValue as FormOptionModel).id; - } else if (fieldType == fieldTypeBirthYear) { + } else if (fieldHolder.fieldType == fieldTypeBirthYear) { return (fieldValue != null && fieldValue.isNotEmpty) ? int.tryParse(fieldValue) : null; - } else if (fieldType == fieldTypeSpot) { + } else if (fieldHolder.fieldType == fieldTypeSpot) { if (fieldValue is SeatModel) { return fieldValue.objectModel; } return null; - } else if (fieldType == fieldTypeTicket) { + } else if (fieldHolder.fieldType == fieldTypeTicket) { // Collect ticket data from multiple ticket forms List> tickets = []; - var ticket = formHolder.getTicket()!; + var ticket = fieldHolder as TicketHolder; for (int i = 0; i < ticket.ticketKeys.length; i++) { final ticketKey = ticket.ticketKeys[i]; if (ticketKey.currentState == null) continue; Map ticketData = {}; - for (MapEntry, dynamic>> ticketSubField in ticketKey.currentState?.fields.entries ?? []) { - var subFieldType = ticketSubField.key; - ticketData[subFieldType] = getFieldData(ticketKey, subFieldType, formHolder); + for(var subFieldHolder in ticket.fields){ + ticketData[subFieldHolder.getFieldTypeValue()] = getFieldData(ticketKey, subFieldHolder); } tickets.add(ticketData); } return tickets; + } else if (fieldHolder.fieldType == fieldTypeOptions) { + if(fieldHolder is OptionsFieldHolder) { + return formKey.currentState?.fields[fieldHolder.optionsType]!.value; + } } if(fieldValue is String){ @@ -146,7 +156,7 @@ class FormHelper { return buildRadioField(fieldTypeSex, sexLabel(), isRequiredField); case fieldTypeOptions: var opt = field as OptionsFieldHolder; - return buildGenericOptions(opt.optionsType, opt.label, field.options); + return buildGenericOptions(opt.optionsType, opt.label, opt.options); case fieldTypeBirthYear: return buildBirthYearField(fieldTypeBirthYear, birthYearLabel(), isRequiredField); case fieldTypeTicket: From c604b5deb4d0c84ac5048e1859e2a51d4aed28dd Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 7 Dec 2024 00:51:31 +0100 Subject: [PATCH 103/159] label --- lib/dataModels/FormFields.dart | 10 +- lib/pages/FormPage.dart | 156 ++++++++++++++++-------------- lib/pages/OrderPreviewScreen.dart | 93 ++++++++++++++++++ lib/services/FormHelper.dart | 20 ++-- 4 files changed, 194 insertions(+), 85 deletions(-) create mode 100644 lib/pages/OrderPreviewScreen.dart diff --git a/lib/dataModels/FormFields.dart b/lib/dataModels/FormFields.dart index 6776a6fb..7bdf183b 100644 --- a/lib/dataModels/FormFields.dart +++ b/lib/dataModels/FormFields.dart @@ -17,10 +17,13 @@ class FieldHolder { String getFieldTypeValue() => FormHelper.getFieldTypeValue(this); + String? label; + FieldHolder({ required this.fieldType, required this.isRequired, this.defaultValue, + this.label }); factory FieldHolder.fromJson(Map json) { @@ -48,7 +51,6 @@ class OptionsFieldHolder extends FieldHolder { static const String metaOptionsType = "optionsType"; static const String metaOptions = "options"; - final String label; final String optionsType; final List options; @@ -56,16 +58,16 @@ class OptionsFieldHolder extends FieldHolder { required String fieldType, dynamic value, required this.options, - required this.label, required this.optionsType, - }) : super(fieldType: fieldType, defaultValue: value, isRequired: true); + required label, + }) : super(fieldType: fieldType, defaultValue: value, isRequired: true, label: label); factory OptionsFieldHolder.fromJson(Map json) { return OptionsFieldHolder( fieldType: json[FieldHolder.metaType], value: json[FieldHolder.metaValue], label: json[metaLabel], - options: List.from(json[metaOptions].map((o)=>FormOptionModel.fromJson(o))), + options: List.from(json[metaOptions].map((o) => FormOptionModel.fromJson(o))), optionsType: json[metaOptionsType], ); } diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index b4196a71..b48d6646 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -10,6 +10,7 @@ import 'package:fstapp/dataModels/FormOptionModel.dart'; import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/pages/OrderPreviewScreen.dart'; import 'package:fstapp/services/FormHelper.dart'; import 'package:fstapp/services/Utilities.dart'; import 'package:fstapp/services/UuidConverter.dart'; @@ -53,12 +54,9 @@ class _FormPageState extends State { } void _updateTotalPrice() { - // Reset total price _totalPrice = 0.0; - // Iterate over all fields and calculate total price for (var field in formHolder!.fields) { - // Calculate price for regular options if (field.fieldType == FormHelper.fieldTypeOptions) { var selectedOption = field.getValue(formHolder!.controller!.globalKey); if (selectedOption is FormOptionModel) { @@ -66,19 +64,15 @@ class _FormPageState extends State { } } - // Calculate price for tickets if (field.fieldType == FormHelper.fieldTypeTicket) { - var ticketDataList = FormHelper.getFieldData( - _formKey, - field - ) ?? []; + var ticketDataList = FormHelper.getFieldData(_formKey, field) ?? []; for (var ticketData in ticketDataList) { for (var ticketValue in ticketData.values) { if (ticketValue is FormOptionModel) { _totalPrice += ticketValue.price; - } else if (ticketValue is BlueprintObjectModel){ - _totalPrice += ticketValue.product?.price??0; + } else if (ticketValue is BlueprintObjectModel) { + _totalPrice += ticketValue.product?.price ?? 0; } } } @@ -88,6 +82,57 @@ class _FormPageState extends State { setState(() {}); // Update the UI } + void _showOrderPreview() { + TextInput.finishAutofillContext(); + if (FormHelper.saveAndValidate(formHolder!)) + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (_) { + return OrderPreviewScreen( + formHolder: formHolder!, + totalPrice: _totalPrice, + onSendPressed: _sendOrder, + ); + }, + ); + } + + Future _sendOrder() async { + if (formHolder == null || form == null) return; + + setState(() { + _isLoading = true; + }); + + var data = FormHelper.getDataFromForm(formHolder!); + data = FormHelper.replaceSpotWithId(data); + data[FormHelper.metaSecret] = form!.secret; + data[FormHelper.metaForm] = form!.formKey; + formResult = data; + + var response = await DbEshop.sendTicketOrder(data); + + if (response.data["code"] != 200) { + ToastHelper.Show( + context, + "There was an error during ordering. Error code: ${response.data["code"]}", + severity: ToastSeverity.NotOk, + ); + } else { + setState(() { + _isSendSuccess = true; + }); + ToastHelper.Show( + context, + "Your order has been sent successfully!".tr(), + ); + } + + setState(() { + _isLoading = false; + }); + } @override Widget build(BuildContext context) { @@ -137,11 +182,13 @@ class _FormPageState extends State { child: AutofillGroup( child: Column( children: [ - if(form!.footer!=null) - Column(children: [ - HtmlView(html: form!.header!, isSelectable: true,), - const SizedBox(height: 16), - ],), + if (form!.footer != null) + Column( + children: [ + HtmlView(html: form!.header!, isSelectable: true), + const SizedBox(height: 16), + ], + ), ...FormHelper.getAllFormFields(context, _formKey, formHolder!), const SizedBox(height: 16), if (_totalPrice > 0) @@ -155,51 +202,17 @@ class _FormPageState extends State { ), ), const SizedBox(height: 16), - if(form!.footer!=null) - Column(children: [ - HtmlView(html: form!.footer!, isSelectable: true,), - const SizedBox(height: 16), - ],), + if (form!.footer != null) + Column( + children: [ + HtmlView(html: form!.footer!, isSelectable: true), + const SizedBox(height: 16), + ], + ), ButtonsHelper.bigButton( context: context, - onPressed: _isLoading - ? null - : () async { - TextInput.finishAutofillContext(); - if (FormHelper.saveAndValidate(formHolder!)) { - setState(() { - _isLoading = true; - }); - var data = FormHelper.getDataFromForm(formHolder!); - - data = FormHelper.replaceSpotWithId(data); - data[FormHelper.metaSecret] = form!.secret; - data[FormHelper.metaForm] = form!.formKey; - formResult = data; - - - var response = await DbEshop.sendTicketOrder(data); - - if(response.data["code"] != 200){ - ToastHelper.Show(context, "There was an error during ordering. Error code: ${response.data["code"]}", severity: ToastSeverity.NotOk); - setState(() { - _isLoading = false; - }); - return; - } - - setState(() { - //_isSendSuccess = true; - _isLoading = false; - }); - - ToastHelper.Show( - context, - "Your order has been sent successfully!" - .tr()); - } - }, - label: "Send".tr(), + onPressed: _isLoading ? null : _showOrderPreview, + label: "Continue".tr(), isEnabled: !_isLoading, height: 50.0, width: 250.0, @@ -255,55 +268,48 @@ class _FormPageState extends State { //var key = UuidConverter.base62ToUuid(widget.id!); form = await DbEshop.getForm("7f4e3892-a544-4385-b933-61117e9755c3"); - if(form == null) { + if (form == null) { return; } - // Fetching items var allItems = await DbEshop.getProducts(context, form!.occasion!); - // New fields to replace existing ones List> updatedFields = []; - // Loop through the fields in form.data for (var field in form?.data![FormHelper.metaFields]) { - // Check if the field is a ticket if (field[FormHelper.metaType] == FormHelper.fieldTypeTicket) { - // Process the fields inside the ticket List> updatedTicketFields = []; for (var ticketField in field[FormHelper.metaFields]) { - // Check if the ticket field has an optionsType if (ticketField.containsKey(FormHelper.metaOptionsType)) { var optionsType = ticketField[FormHelper.metaOptionsType]; var generatedOptions = generateOptionsForItemType(allItems, optionsType); updatedTicketFields.add(generatedOptions); } else { - // Directly add the ticket field if no optionsType is present updatedTicketFields.add(ticketField); } } - // Replace the fields inside the ticket updatedFields.add({ FormHelper.metaType: field[FormHelper.metaType], FormHelper.metaMaxTickets: field[FormHelper.metaMaxTickets], FormHelper.metaFields: updatedTicketFields, }); } else { - // Directly add the field if it's not a ticket updatedFields.add(field); } } - Map json = {FormHelper.metaFields : updatedFields}; + Map json = {FormHelper.metaFields: updatedFields}; formHolder = FormHolder.fromJson(json); - formHolder!.controller = FormHolderController(secret: form!.secret, blueprintId: form!.blueprint, globalKey: _formKey, formKey: form!.formKey!, updateTotalPrice: _updateTotalPrice); + formHolder!.controller = FormHolderController( + secret: form!.secret, + blueprintId: form!.blueprint, + globalKey: _formKey, + formKey: form!.formKey!, + updateTotalPrice: _updateTotalPrice, + ); form!.data![FormHelper.metaFields] = updatedFields; setState(() { _isLoading = false; }); } -} - - - - +} \ No newline at end of file diff --git a/lib/pages/OrderPreviewScreen.dart b/lib/pages/OrderPreviewScreen.dart new file mode 100644 index 00000000..d2e150e2 --- /dev/null +++ b/lib/pages/OrderPreviewScreen.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:fstapp/dataModels/FormFields.dart'; +import 'package:fstapp/services/Utilities.dart'; +import 'package:fstapp/widgets/ButtonsHelper.dart'; +import 'package:easy_localization/easy_localization.dart'; + +class OrderPreviewScreen extends StatelessWidget { + final FormHolder formHolder; + final double totalPrice; + final VoidCallback onSendPressed; + + const OrderPreviewScreen({ + Key? key, + required this.formHolder, + required this.totalPrice, + required this.onSendPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Order Preview".tr(), + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + ...[ + // List the order details + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (var entry in formHolder.fields) + Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${entry.label}: ", + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + Expanded( + child: Text( + entry.getValue(formHolder.controller!.globalKey).toString(), + style: const TextStyle( + fontSize: 16, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + const SizedBox(height: 16), + Text( + "Total Price: {price}".tr(namedArgs: { + "price": Utilities.formatPrice(context, totalPrice), + }), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + ButtonsHelper.bigButton( + context: context, + onPressed: onSendPressed, + label: "Send".tr(), + height: 50.0, + width: 250.0, + ), + ], + ), + ), + ); + } +} diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index c5085d02..be954a76 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -141,24 +141,32 @@ class FormHelper { final bool isRequiredField = field.isRequired; switch (field.fieldType) { case fieldTypeNote: + field.label = noteLabel(); return buildTextField(fieldTypeNote, noteLabel(), isRequiredField, []); case fieldTypeName: + field.label = nameLabel(); return buildTextField(fieldTypeName, nameLabel(), isRequiredField, [AutofillHints.givenName]); case fieldTypeSurname: + field.label = surnameLabel(); return buildTextField(fieldTypeSurname, surnameLabel(), isRequiredField, [AutofillHints.familyName]); case fieldTypeCity: + field.label = cityLabel(); return buildTextField(fieldTypeCity, cityLabel(), isRequiredField, [AutofillHints.addressCity]); case fieldTypeSpot: + field.label = spotLabel(); return buildSpotField(context, formKey, formHolder, fieldTypeSpot, spotLabel()); case fieldTypeEmail: + field.label = emailLabel(); return buildEmailField(isRequiredField); case fieldTypeSex: + field.label = sexLabel(); return buildRadioField(fieldTypeSex, sexLabel(), isRequiredField); case fieldTypeOptions: var opt = field as OptionsFieldHolder; - return buildGenericOptions(opt.optionsType, opt.label, opt.options); + return buildGenericOptions(opt.optionsType, opt.label!, opt.options); case fieldTypeBirthYear: - return buildBirthYearField(fieldTypeBirthYear, birthYearLabel(), isRequiredField); + field.label = birthYearLabel(); + return buildBirthYearField(field); case fieldTypeTicket: var ticketHolder = field as TicketHolder; return buildTicketField(formHolder, ticketHolder); @@ -414,13 +422,13 @@ class FormHelper { ); } - static FormBuilderTextField buildBirthYearField(String name, String label, bool isRequired) { + static FormBuilderTextField buildBirthYearField(FieldHolder fieldHolder) { return FormBuilderTextField( - name: name, - decoration: InputDecoration(labelText: label, + name: fieldHolder.fieldType, + decoration: InputDecoration(labelText: fieldHolder.label, labelStyle: TextStyle(fontSize: 16 * fontSizeFactor),), validator: FormBuilderValidators.compose([ - if (isRequired) FormBuilderValidators.required(), + if (fieldHolder.isRequired) FormBuilderValidators.required(), // Allow empty for optional field; validate only if non-empty (value) { if (value == null || value.isEmpty || value == "") { From bd860d4f61fc40b324a0aa036cd2080fc1b79fcb Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 7 Dec 2024 02:01:33 +0100 Subject: [PATCH 104/159] fixes --- lib/services/FormHelper.dart | 120 +++++++++++++++++------------------ 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index be954a76..1ef8970f 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -91,7 +91,7 @@ class FormHelper { // Determine the correct data from the form based on type static dynamic getFieldData(GlobalKey formKey, FieldHolder fieldHolder) { - var fieldValue = formKey.currentState?.fields[fieldHolder.fieldType]?.value; + var fieldValue = formKey.currentState?.fields[fieldHolder.getFieldTypeValue()]?.value; if (fieldHolder.fieldType == fieldTypeSex) { if (fieldValue == null) { @@ -123,10 +123,6 @@ class FormHelper { } return tickets; - } else if (fieldHolder.fieldType == fieldTypeOptions) { - if(fieldHolder is OptionsFieldHolder) { - return formKey.currentState?.fields[fieldHolder.optionsType]!.value; - } } if(fieldValue is String){ @@ -142,28 +138,35 @@ class FormHelper { switch (field.fieldType) { case fieldTypeNote: field.label = noteLabel(); - return buildTextField(fieldTypeNote, noteLabel(), isRequiredField, []); + return buildTextField(field, []); case fieldTypeName: field.label = nameLabel(); - return buildTextField(fieldTypeName, nameLabel(), isRequiredField, [AutofillHints.givenName]); + return buildTextField(field, [AutofillHints.givenName]); case fieldTypeSurname: field.label = surnameLabel(); - return buildTextField(fieldTypeSurname, surnameLabel(), isRequiredField, [AutofillHints.familyName]); + return buildTextField(field, [AutofillHints.familyName]); case fieldTypeCity: field.label = cityLabel(); - return buildTextField(fieldTypeCity, cityLabel(), isRequiredField, [AutofillHints.addressCity]); + return buildTextField(field, [AutofillHints.addressCity]); case fieldTypeSpot: field.label = spotLabel(); - return buildSpotField(context, formKey, formHolder, fieldTypeSpot, spotLabel()); + return buildSpotField(context, formKey, formHolder, field); case fieldTypeEmail: field.label = emailLabel(); - return buildEmailField(isRequiredField); + return buildEmailField(field); case fieldTypeSex: field.label = sexLabel(); - return buildRadioField(fieldTypeSex, sexLabel(), isRequiredField); + var sexOptions = [ + FormOptionModel(UserInfoModel.sexes[0], maleLabel()), + FormOptionModel(UserInfoModel.sexes[1], femaleLabel()), + ]; + if (!isRequiredField) { + sexOptions.insert(0, FormOptionModel(UserInfoModel.sexes[2], notSpecifiedLabel())); + } + return buildRadioField(field, sexOptions); case fieldTypeOptions: - var opt = field as OptionsFieldHolder; - return buildGenericOptions(opt.optionsType, opt.label!, opt.options); + var optionsField = field as OptionsFieldHolder; + return buildRadioField(optionsField, optionsField.options); case fieldTypeBirthYear: field.label = birthYearLabel(); return buildBirthYearField(field); @@ -281,25 +284,14 @@ class FormHelper { // Build a simple text field with optional validation - static Widget buildSpotField( - BuildContext context, - GlobalKey formKey, - FormHolder formHolder, - String name, - String label) { - // Create a TextEditingController to control the displayed text + static Widget buildSpotField(BuildContext context, GlobalKey formKey, FormHolder formHolder, FieldHolder fieldHolder) { TextEditingController textController = TextEditingController(); return FormBuilderField( - name: name, + name: fieldHolder.fieldType, validator: FormBuilderValidators.compose([ - FormBuilderValidators.required(), - (value) { - if (value == null) { - return "Please select a seat.".tr(); - } - return null; - }, + if (fieldHolder.isRequired) FormBuilderValidators.required(), + (value) => value == null ? "Please select a seat.".tr() : null, ]), builder: (FormFieldState field) { SeatModel? seat = field.value; @@ -309,13 +301,12 @@ class FormHelper { readOnly: true, canRequestFocus: false, decoration: InputDecoration( - labelText: label, + labelText: fieldHolder.label, suffixIcon: const Icon(Icons.event_seat), labelStyle: StylesConfig.textStyleBig.copyWith(fontSize: 16 * fontSizeFactor), - errorText: field.errorText, // Display validation error + errorText: field.errorText, ), onTap: () async { - // Show the seat reservation dialog and await the result SeatModel? selectedSeat = await showGeneralDialog( context: context, barrierColor: Colors.black12.withOpacity(0.6), @@ -333,11 +324,9 @@ class FormHelper { ); if (selectedSeat != null) { - // Update the form field and display value formHolder.controller!.updateTotalPrice?.call(); field.didChange(selectedSeat); - textController.text = - selectedSeat.objectModel?.toString() ?? metaEmpty; + textController.text = selectedSeat.objectModel?.toString() ?? metaEmpty; field.validate(); } }, @@ -347,45 +336,56 @@ class FormHelper { } // Build a simple text field with optional validation - static FormBuilderTextField buildTextField(String name, String label, bool isRequired, [List? autofillHints]) { + static FormBuilderTextField buildTextField(FieldHolder fieldHolder, Iterable autofillHints) { return FormBuilderTextField( - name: name, + name: fieldHolder.fieldType, autofillHints: autofillHints, - decoration: InputDecoration(labelText: label, - labelStyle: TextStyle(fontSize: 16 * fontSizeFactor),), - validator: isRequired ? FormBuilderValidators.required() : null, + decoration: InputDecoration( + labelText: fieldHolder.label, + labelStyle: TextStyle(fontSize: 16 * fontSizeFactor), + ), + validator: fieldHolder.isRequired ? FormBuilderValidators.required() : null, ); } - // Build an email field with validation for format and required status - static FormBuilderTextField buildEmailField(bool isRequired) { + static FormBuilderTextField buildEmailField(FieldHolder fieldHolder) { return FormBuilderTextField( - name: fieldTypeEmail, + name: fieldHolder.fieldType, autofillHints: [AutofillHints.email], - decoration: InputDecoration(labelText: emailLabel(), - labelStyle: TextStyle(fontSize: 16 * fontSizeFactor),), + decoration: InputDecoration( + labelText: fieldHolder.label, + labelStyle: TextStyle(fontSize: 16 * fontSizeFactor), + ), validator: FormBuilderValidators.compose([ - if (isRequired) FormBuilderValidators.required(), + if (fieldHolder.isRequired) FormBuilderValidators.required(), FormBuilderValidators.email(errorText: emailInvalidMessage()), ]), ); } - // Build a radio group for selecting sex - static FormBuilderRadioGroup buildRadioField(String name, String label, bool isRequired) { - var options = [ - FormBuilderFieldOption(value: FormOptionModel(UserInfoModel.sexes[0], maleLabel())), - FormBuilderFieldOption(value: FormOptionModel(UserInfoModel.sexes[1], femaleLabel())) - ]; - if(!isRequired){ - options.insert(0, FormBuilderFieldOption(value: FormOptionModel(UserInfoModel.sexes[2], notSpecifiedLabel()))); - } - return FormBuilderRadioGroup( - name: name, - decoration: InputDecoration(labelText: label, - labelStyle: StylesConfig.textStyleBig.copyWith(fontSize: 16 * fontSizeFactor),), - validator: isRequired ? FormBuilderValidators.required() : null, + // Build a radio group field using the FieldHolder as parameter + static FormBuilderRadioGroup buildRadioField(FieldHolder fieldHolder, List optionsIn) { + List> options = optionsIn.map( + (o) => FormBuilderFieldOption( + value: FormOptionModel(o.id, o.name, price: o.price), + child: Text( + o.name, + style: TextStyle(fontSize: 14.0 * fontSizeFactor), + ), + ), + ).toList(); + + return FormBuilderRadioGroup( + name: fieldHolder.getFieldTypeValue(), + decoration: InputDecoration( + labelText: fieldHolder.label, + labelStyle: StylesConfig.textStyleBig.copyWith(fontSize: 16 * fontSizeFactor), + ), + validator: fieldHolder.isRequired ? FormBuilderValidators.required() : null, options: options, + initialValue: options.isNotEmpty ? options.first.value : null, + orientation: OptionsOrientation.vertical, + wrapDirection: Axis.vertical, ); } From 55a348508b49115e66467b35935d09d029005501 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 7 Dec 2024 03:24:06 +0100 Subject: [PATCH 105/159] order screen update --- lib/pages/OrderPreviewScreen.dart | 209 +++++++++++++++++++++--------- 1 file changed, 150 insertions(+), 59 deletions(-) diff --git a/lib/pages/OrderPreviewScreen.dart b/lib/pages/OrderPreviewScreen.dart index d2e150e2..f04831ba 100644 --- a/lib/pages/OrderPreviewScreen.dart +++ b/lib/pages/OrderPreviewScreen.dart @@ -1,93 +1,184 @@ import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:fstapp/dataModels/FormFields.dart'; +import 'package:fstapp/services/FormHelper.dart'; import 'package:fstapp/services/Utilities.dart'; +import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/ButtonsHelper.dart'; -import 'package:easy_localization/easy_localization.dart'; class OrderPreviewScreen extends StatelessWidget { final FormHolder formHolder; - final double totalPrice; + final double totalPrice; // Injected total price final VoidCallback onSendPressed; const OrderPreviewScreen({ - Key? key, + super.key, required this.formHolder, required this.totalPrice, required this.onSendPressed, - }) : super(key: key); + }); @override Widget build(BuildContext context) { return SafeArea( child: Padding( padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Order Preview".tr(), - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - ...[ - // List the order details - Expanded( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - for (var entry in formHolder.fields) - Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${entry.label}: ", - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), - Expanded( - child: Text( - entry.getValue(formHolder.controller!.globalKey).toString(), - style: const TextStyle( - fontSize: 16, - ), - ), - ), - ], - ), - ), - ], + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Center( + child: Text( + "Rekapitulace Vaší objednávky:".tr(), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), ), ), + const SizedBox(height: 16), + + // Personal Info Section + _buildPersonalInfoSection(context), + + const SizedBox(height: 16), + const Divider(), + + // Tickets Section + _buildTicketsSection(context), + + const SizedBox(height: 16), + + // Total Price + _buildTotalPrice(context), + + const SizedBox(height: 16), + + // Submit Button + Center( + child: ButtonsHelper.bigButton( + context: context, + onPressed: onSendPressed, + label: "Odeslat objednávku".tr(), + height: 50.0, + width: 250.0, + ), + ), ], - const SizedBox(height: 16), + ), + ), + ), + ); + } + + Widget _buildPersonalInfoSection(BuildContext context) { + // Filter personal info fields based on their type + final personalInfoFields = formHolder.fields.where((field) { + return [ + FormHelper.fieldTypeName, + FormHelper.fieldTypeSurname, + FormHelper.fieldTypeEmail, + FormHelper.fieldTypeNote, + ].contains(field.fieldType); + }).toList(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: personalInfoFields.map((field) { + return _buildInfoRow( + field.label!, + field.getValue(formHolder.controller!.globalKey).toString(), + ); + }).toList(), + ); + } + Widget _buildTicketsSection(BuildContext context) { + // Filter ticket-related fields + final ticketHolder = formHolder.fields + .firstWhere((field) => field.fieldType == FormHelper.fieldTypeTicket) as TicketHolder; + + return Column( + children: ticketHolder.ticketValues.asMap().entries.map((entry) { + final index = entry.key; // Index for ticket values + final ticketFields = entry.value; + final ticketKey = ticketHolder.ticketKeys[index]; // Get the corresponding key for this ticket + + return _buildTicketCard(context, ticketFields, ticketKey, index + 1); // Pass ticket key and index + }).toList(), + ); + } + + Widget _buildTicketCard(BuildContext context, List fieldData, GlobalKey ticketKey, int index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Container( + decoration: BoxDecoration( + color: ThemeConfig.whiteColor(context), + border: Border.all( + color: Theme.of(context).primaryColor, // Use the primary color for the border + width: 2.0, + ), + borderRadius: BorderRadius.circular(8.0), + ), + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Display ticket index at the top of the container Text( - "Total Price: {price}".tr(namedArgs: { - "price": Utilities.formatPrice(context, totalPrice), - }), + "Ticket {number}".tr(namedArgs: {"number": (index).toString()}), style: const TextStyle( - fontSize: 18, + fontSize: 16, fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 16), - ButtonsHelper.bigButton( - context: context, - onPressed: onSendPressed, - label: "Send".tr(), - height: 50.0, - width: 250.0, - ), + const SizedBox(height: 8), + + // Display ticket details + ...fieldData.where((entry) { + // Filter out rows with null values + final value = entry.getValue(ticketKey); + return value != null && value.toString().isNotEmpty; + }).map((entry) { + return _buildInfoRow(entry.label!, entry.getValue(ticketKey).toString()); + }), ], ), ), ); } + + Widget _buildInfoRow(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "$label: ", + style: const TextStyle(fontWeight: FontWeight.bold), + ), + Expanded( + child: Text(value), + ), + ], + ), + ); + } + + Widget _buildTotalPrice(BuildContext context) { + return Center( + child: Text( + "Total Price: {price}".tr(namedArgs: { + "price": Utilities.formatPrice(context, totalPrice), + }), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ); + } } From 8f4b168dd769bb99ce72cd1fda01a5aed1e5d828 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 7 Dec 2024 03:46:16 +0100 Subject: [PATCH 106/159] center --- lib/pages/OrderPreviewScreen.dart | 114 +++++++++++++++++++----------- 1 file changed, 71 insertions(+), 43 deletions(-) diff --git a/lib/pages/OrderPreviewScreen.dart b/lib/pages/OrderPreviewScreen.dart index f04831ba..9ce47e7c 100644 --- a/lib/pages/OrderPreviewScreen.dart +++ b/lib/pages/OrderPreviewScreen.dart @@ -4,6 +4,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:fstapp/dataModels/FormFields.dart'; import 'package:fstapp/services/FormHelper.dart'; import 'package:fstapp/services/Utilities.dart'; +import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/ButtonsHelper.dart'; @@ -22,53 +23,80 @@ class OrderPreviewScreen extends StatelessWidget { @override Widget build(BuildContext context) { return SafeArea( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header - Center( - child: Text( - "Rekapitulace Vaší objednávky:".tr(), - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + child: Stack( + children: [ + IntrinsicHeight( + child: Align( + alignment: Alignment.bottomCenter, + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: StylesConfig.appMaxWidth, // Limit width for horizontal centering + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, // Keep height minimal + children: [ + // Header + Center( + child: Text( + "Rekapitulace Vaší objednávky:".tr(), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 16), + + // Personal Info Section + _buildPersonalInfoSection(context), + + const SizedBox(height: 16), + const Divider(), + + // Tickets Section + _buildTicketsSection(context), + + const SizedBox(height: 16), + + // Total Price + _buildTotalPrice(context), + + const SizedBox(height: 16), + + // Submit Button + Center( + child: ButtonsHelper.bigButton( + context: context, + onPressed: onSendPressed, + label: "Odeslat objednávku".tr(), + height: 50.0, + width: 250.0, + ), + ), + ], + ), ), ), ), - const SizedBox(height: 16), - - // Personal Info Section - _buildPersonalInfoSection(context), - - const SizedBox(height: 16), - const Divider(), - - // Tickets Section - _buildTicketsSection(context), - - const SizedBox(height: 16), - - // Total Price - _buildTotalPrice(context), - - const SizedBox(height: 16), + ), + ), - // Submit Button - Center( - child: ButtonsHelper.bigButton( - context: context, - onPressed: onSendPressed, - label: "Odeslat objednávku".tr(), - height: 50.0, - width: 250.0, - ), - ), - ], + // Close Icon + Positioned( + top: 0, + right: 0, + child: IconButton( + icon: const Icon(Icons.close), + onPressed: () { + Navigator.of(context).pop(); // Close the modal + }, + ), ), - ), + ], ), ); } @@ -94,8 +122,8 @@ class OrderPreviewScreen extends StatelessWidget { }).toList(), ); } + Widget _buildTicketsSection(BuildContext context) { - // Filter ticket-related fields final ticketHolder = formHolder.fields .firstWhere((field) => field.fieldType == FormHelper.fieldTypeTicket) as TicketHolder; From a360194be7dfa236926f7cde8e726a647e6c3c66 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 7 Dec 2024 04:12:31 +0100 Subject: [PATCH 107/159] design --- lib/pages/FormPage.dart | 8 ++--- lib/pages/OrderPreviewScreen.dart | 49 +++++++++++++++++++------------ lib/styles/StylesConfig.dart | 1 + 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index b48d6646..7c869a10 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -84,7 +84,7 @@ class _FormPageState extends State { void _showOrderPreview() { TextInput.finishAutofillContext(); - if (FormHelper.saveAndValidate(formHolder!)) + //if (FormHelper.saveAndValidate(formHolder!)) showModalBottomSheet( context: context, isScrollControlled: true, @@ -137,14 +137,10 @@ class _FormPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - centerTitle: true, - title: const Text("Form Page").tr(), - ), body: Align( alignment: Alignment.topCenter, child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: StylesConfig.appMaxWidth), + constraints: BoxConstraints(maxWidth: StylesConfig.formMaxWidth), child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(12.0), diff --git a/lib/pages/OrderPreviewScreen.dart b/lib/pages/OrderPreviewScreen.dart index 9ce47e7c..937fdd35 100644 --- a/lib/pages/OrderPreviewScreen.dart +++ b/lib/pages/OrderPreviewScreen.dart @@ -13,6 +13,8 @@ class OrderPreviewScreen extends StatelessWidget { final double totalPrice; // Injected total price final VoidCallback onSendPressed; + static const double fontSizeFactor = 1.2; // Reused font size factor + const OrderPreviewScreen({ super.key, required this.formHolder, @@ -30,43 +32,43 @@ class OrderPreviewScreen extends StatelessWidget { alignment: Alignment.bottomCenter, child: ConstrainedBox( constraints: BoxConstraints( - maxWidth: StylesConfig.appMaxWidth, // Limit width for horizontal centering + maxWidth: StylesConfig.formMaxWidth, ), child: Padding( padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, // Keep height minimal + mainAxisSize: MainAxisSize.min, children: [ // Header Center( child: Text( "Rekapitulace Vaší objednávky:".tr(), - style: const TextStyle( - fontSize: 18, + style: StylesConfig.textStyleBig.copyWith( + fontSize: 18 * fontSizeFactor, fontWeight: FontWeight.bold, ), ), ), const SizedBox(height: 16), - + // Personal Info Section _buildPersonalInfoSection(context), - + const SizedBox(height: 16), const Divider(), - + // Tickets Section _buildTicketsSection(context), - + const SizedBox(height: 16), - + // Total Price _buildTotalPrice(context), - + const SizedBox(height: 16), - + // Submit Button Center( child: ButtonsHelper.bigButton( @@ -90,10 +92,14 @@ class OrderPreviewScreen extends StatelessWidget { top: 0, right: 0, child: IconButton( - icon: const Icon(Icons.close), + icon: Icon( + Icons.close, + size: 24 * fontSizeFactor, // Make the icon size scalable with the factor + ), onPressed: () { Navigator.of(context).pop(); // Close the modal }, + tooltip: "Close".tr(), ), ), ], @@ -157,8 +163,8 @@ class OrderPreviewScreen extends StatelessWidget { // Display ticket index at the top of the container Text( "Ticket {number}".tr(namedArgs: {"number": (index).toString()}), - style: const TextStyle( - fontSize: 16, + style: StylesConfig.textStyleBig.copyWith( + fontSize: 16 * fontSizeFactor, fontWeight: FontWeight.bold, ), ), @@ -186,10 +192,17 @@ class OrderPreviewScreen extends StatelessWidget { children: [ Text( "$label: ", - style: const TextStyle(fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: 14 * fontSizeFactor, + ), ), Expanded( - child: Text(value), + child: Text( + value, + style: TextStyle( + fontSize: 14 * fontSizeFactor, // Regular style for value + ), + ), ), ], ), @@ -202,8 +215,8 @@ class OrderPreviewScreen extends StatelessWidget { "Total Price: {price}".tr(namedArgs: { "price": Utilities.formatPrice(context, totalPrice), }), - style: const TextStyle( - fontSize: 16, + style: StylesConfig.textStyleBig.copyWith( + fontSize: 16 * fontSizeFactor, fontWeight: FontWeight.bold, ), ), diff --git a/lib/styles/StylesConfig.dart b/lib/styles/StylesConfig.dart index 44440d86..7ca9cf73 100644 --- a/lib/styles/StylesConfig.dart +++ b/lib/styles/StylesConfig.dart @@ -19,6 +19,7 @@ class StylesConfig { // Dimensions static const double appMaxWidth = 820; + static const double formMaxWidth = 680; static TextStyle textStyleBig = TextStyle(fontWeight: FontWeight.w900, fontSize: 16); From 4d5a4dce694c76998bf3d45ccf40799bec106bcd Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 7 Dec 2024 08:29:47 +0100 Subject: [PATCH 108/159] OrderFinishScreen.dart --- .../OrderFinishScreen.dart | 209 ++++++++++++++++++ lib/pages/FormPage.dart | 74 +++---- 2 files changed, 238 insertions(+), 45 deletions(-) create mode 100644 lib/pages/AdministrationOccasion/OrderFinishScreen.dart diff --git a/lib/pages/AdministrationOccasion/OrderFinishScreen.dart b/lib/pages/AdministrationOccasion/OrderFinishScreen.dart new file mode 100644 index 00000000..e54b362f --- /dev/null +++ b/lib/pages/AdministrationOccasion/OrderFinishScreen.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:fstapp/themeConfig.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class FinishOrderScreen extends StatefulWidget { + final Future Function() orderFutureFunction; // Order future function + final VoidCallback onResetForm; // Callback to reset the form + + const FinishOrderScreen({ + super.key, + required this.orderFutureFunction, + required this.onResetForm, + }); + + @override + _FinishOrderScreenState createState() => _FinishOrderScreenState(); +} + +class _FinishOrderScreenState extends State + with TickerProviderStateMixin { + late bool _isSuccess; + bool _isLoading = true; + + late AnimationController _mainController; + late AnimationController _loadingController; + late Animation _scaleAnimation; + int? code; + + @override + void initState() { + super.initState(); + + // Initialize animation controllers + _mainController = AnimationController( + duration: const Duration(milliseconds: 1500), + vsync: this, + ); + + _loadingController = AnimationController( + duration: const Duration(milliseconds: 1500), + vsync: this, + ); + + // Scale animation for success/failure state + _scaleAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _mainController, curve: Curves.easeOutBack), + ); + + // Start the loading animation + _loadingController.repeat(reverse: true); + + // Execute the order logic + _executeOrder(); + } + + final int _minimalLoadingDurationMs = 1000; + + Future _executeOrder() async { + final startTime = DateTime.now(); + try { + final resultFc = await widget.orderFutureFunction(); + final elapsedTime = DateTime.now().difference(startTime).inMilliseconds; + final rawCode = resultFc.data["code"] ?? "0"; + code = int.tryParse(rawCode.toString().replaceAll(RegExp(r'\D'), '')) ?? 0; + _isSuccess = code == 200; + if (_isSuccess) { + final remainingTime = _minimalLoadingDurationMs - elapsedTime; + if (remainingTime > 0) { + await Future.delayed(Duration(milliseconds: remainingTime)); + } + } + + setState(() { + _isLoading = false; + }); + + _loadingController.stop(); + _mainController.forward(); + } catch (e) { + setState(() { + _isSuccess = false; + _isLoading = false; + }); + + _loadingController.stop(); + _mainController.forward(); + } + } + + @override + void dispose() { + _mainController.dispose(); + _loadingController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + body: WillPopScope( + onWillPop: () async => false, // Disable back navigation + child: Center( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 500), // Smooth transition + child: _isLoading + ? _buildLoadingAnimation(context) + : _buildResultContent(context), + ), + ), + ), + ); + } + + Widget _buildLoadingAnimation(BuildContext context) { + return AnimatedBuilder( + animation: _loadingController, + builder: (context, child) { + return Transform.scale( + scale: 1.0 + _loadingController.value * 0.2, // Pulsating effect + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey[300], + ), + child: Center( + child: CircularProgressIndicator( + color: Theme.of(context).primaryColor, + strokeWidth: 4.0, + ), + ), + ), + ); + }, + ); + } + + Widget _buildResultContent(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Animated Circle for success/failure + ScaleTransition( + scale: _scaleAnimation, + child: Container( + width: 150, + height: 150, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _isSuccess ? Colors.green : Colors.red, + ), + child: Icon( + _isSuccess ? Icons.check_circle : Icons.error, + size: 80, + color: Colors.white, + ), + ), + ), + const SizedBox(height: 24), + // Display result message + Text( + _isSuccess + ? "Your order was accepted!".tr() + : "Order Failed".tr(), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: _isSuccess ? Colors.green : Colors.red, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + _isSuccess + ? "Payment information has been sent to your email.".tr() + : "${code!}: ${"An error occurred while processing your order.".tr()}", + style: TextStyle( + fontSize: 14, + color: ThemeConfig.blackColor(context).withOpacity(0.7), + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + // Back to Form Button + ElevatedButton( + onPressed: () { + widget.onResetForm(); + Navigator.of(context).pop(); + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 32, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + child: Text( + "Back to Form".tr(), + ), + ), + ], + ); + } +} diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 7c869a10..a9563411 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -10,6 +10,7 @@ import 'package:fstapp/dataModels/FormOptionModel.dart'; import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/pages/AdministrationOccasion/OrderFinishScreen.dart'; import 'package:fstapp/pages/OrderPreviewScreen.dart'; import 'package:fstapp/services/FormHelper.dart'; import 'package:fstapp/services/Utilities.dart'; @@ -19,7 +20,6 @@ import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/ButtonsHelper.dart'; import 'package:flutter/services.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/widgets/HtmlView.dart'; @RoutePage() @@ -35,13 +35,13 @@ class FormPage extends StatefulWidget { class _FormPageState extends State { bool _isLoading = false; - bool _isSendSuccess = false; double _totalPrice = 0.0; // Total price Map? formResult; FormHolder? formHolder; FormModel? form; final _formKey = GlobalKey(); + final ScrollController _scrollController = ScrollController(); @override Future didChangeDependencies() async { @@ -98,6 +98,10 @@ class _FormPageState extends State { ); } + void _scrollToTop() { + _scrollController.jumpTo(0.0); + } + Future _sendOrder() async { if (formHolder == null || form == null) return; @@ -111,23 +115,27 @@ class _FormPageState extends State { data[FormHelper.metaForm] = form!.formKey; formResult = data; - var response = await DbEshop.sendTicketOrder(data); - - if (response.data["code"] != 200) { - ToastHelper.Show( - context, - "There was an error during ordering. Error code: ${response.data["code"]}", - severity: ToastSeverity.NotOk, - ); - } else { - setState(() { - _isSendSuccess = true; - }); - ToastHelper.Show( - context, - "Your order has been sent successfully!".tr(), - ); - } + showGeneralDialog( + context: context, + barrierDismissible: false, // Disable dismissing by tapping outside + barrierLabel: "FinishOrderDialog", + pageBuilder: (context, anim1, anim2) => FinishOrderScreen( + orderFutureFunction: () async { + return await DbEshop.sendTicketOrder(data); + }, + onResetForm: () async { + Navigator.of(context).pop(); // Close the FinishOrderScreen + await loadData(); // Reload data + _scrollToTop(); // Scroll to the top + }, + ), + transitionBuilder: (context, anim1, anim2, child) { + return FadeTransition( + opacity: anim1, // Fade animation + child: child, + ); + }, + ); setState(() { _isLoading = false; @@ -142,34 +150,10 @@ class _FormPageState extends State { child: ConstrainedBox( constraints: BoxConstraints(maxWidth: StylesConfig.formMaxWidth), child: SingleChildScrollView( + controller: _scrollController, child: Padding( padding: const EdgeInsets.all(12.0), - child: _isSendSuccess - ? Padding( - padding: const EdgeInsets.fromLTRB(12, 88, 12, 12), - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - children: [ - TextSpan( - style: TextStyle( - fontSize: 18, - color: ThemeConfig.blackColor(context), - ), - text: "Your order was successfully sent to your email {email}." - .tr(namedArgs: {"email": formResult?[FormHelper.fieldTypeEmail] ?? ""}), - ), - const WidgetSpan( - child: Padding( - padding: EdgeInsets.fromLTRB(6, 0, 0, 0), - child: Icon(Icons.check_circle), - ), - ), - ], - ), - ), - ) - : formHolder == null + child: formHolder == null ? const Center( child: CircularProgressIndicator(), ) From f39ac7385509ed51e0dbfbe47d6f36c861d9ba38 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 7 Dec 2024 08:33:55 +0100 Subject: [PATCH 109/159] adjust --- lib/widgets/ButtonsHelper.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/widgets/ButtonsHelper.dart b/lib/widgets/ButtonsHelper.dart index 42466d4b..ae968ffa 100644 --- a/lib/widgets/ButtonsHelper.dart +++ b/lib/widgets/ButtonsHelper.dart @@ -122,4 +122,5 @@ class ButtonsHelper { ), ), ); - }} + } +} From afafbbf56e86ab0a853490f392f90922dde406e2 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sat, 7 Dec 2024 20:22:35 +0100 Subject: [PATCH 110/159] total price and total tickets --- lib/pages/FormPage.dart | 149 ++++++++++++++++++++------------- lib/services/FormHelper.dart | 2 +- lib/widgets/ButtonsHelper.dart | 42 ++++++++++ 3 files changed, 135 insertions(+), 58 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index a9563411..313b0845 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -35,7 +35,8 @@ class FormPage extends StatefulWidget { class _FormPageState extends State { bool _isLoading = false; - double _totalPrice = 0.0; // Total price + double _totalPrice = 0.0; + int _totalTickets = 0; Map? formResult; FormHolder? formHolder; FormModel? form; @@ -55,6 +56,7 @@ class _FormPageState extends State { void _updateTotalPrice() { _totalPrice = 0.0; + _totalTickets = 0; for (var field in formHolder!.fields) { if (field.fieldType == FormHelper.fieldTypeOptions) { @@ -64,7 +66,8 @@ class _FormPageState extends State { } } - if (field.fieldType == FormHelper.fieldTypeTicket) { + if (field is TicketHolder) { + _totalTickets = field.ticketKeys.length; var ticketDataList = FormHelper.getFieldData(_formKey, field) ?? []; for (var ticketData in ticketDataList) { @@ -79,7 +82,52 @@ class _FormPageState extends State { } } - setState(() {}); // Update the UI + setState(() {}); + } + + Widget _buildPriceAndTicketInfo() { + if (_totalPrice <= 0) return SizedBox(); // Do not display if total price is 0 + + return Positioned( + top: 16, + right: 16, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, // Primary color background + borderRadius: BorderRadius.circular(8.0), + boxShadow: [BoxShadow(blurRadius: 5, color: Colors.black26)], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${_totalTickets}x", + style: const TextStyle( + fontSize: 18, // Increased font size + fontWeight: FontWeight.bold, + color: Colors.white, // White bold text + ), + ), + const SizedBox(width: 6), // Increased spacing + Icon( + Icons.local_activity, // Ticket icon + color: Colors.white, + size: 24, // Increased icon size + ), + const SizedBox(width: 10), // Increased spacing + Text( + Utilities.formatPrice(context, _totalPrice), + style: const TextStyle( + fontSize: 18, // Increased font size + fontWeight: FontWeight.bold, + color: Colors.white, // White bold text + ), + ), + ], + ), + ), + ); } void _showOrderPreview() { @@ -125,8 +173,9 @@ class _FormPageState extends State { }, onResetForm: () async { Navigator.of(context).pop(); // Close the FinishOrderScreen - await loadData(); // Reload data - _scrollToTop(); // Scroll to the top + _scrollToTop(); + await loadData(); + _updateTotalPrice(); }, ), transitionBuilder: (context, anim1, anim2, child) { @@ -145,65 +194,51 @@ class _FormPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: Align( - alignment: Alignment.topCenter, - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: StylesConfig.formMaxWidth), - child: SingleChildScrollView( - controller: _scrollController, - child: Padding( - padding: const EdgeInsets.all(12.0), - child: formHolder == null - ? const Center( - child: CircularProgressIndicator(), - ) - : FormBuilder( - key: _formKey, - child: AutofillGroup( - child: Column( - children: [ - if (form!.footer != null) - Column( - children: [ - HtmlView(html: form!.header!, isSelectable: true), - const SizedBox(height: 16), - ], - ), - ...FormHelper.getAllFormFields(context, _formKey, formHolder!), - const SizedBox(height: 16), - if (_totalPrice > 0) - Text( - "Total Price: {price}".tr(namedArgs: { - "price": Utilities.formatPrice(context, _totalPrice), - }), - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + body: Stack( + children: [ + Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: StylesConfig.formMaxWidth), + child: SingleChildScrollView( + controller: _scrollController, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: formHolder == null + ? const Center(child: CircularProgressIndicator()) + : FormBuilder( + key: _formKey, + child: AutofillGroup( + child: Column( + children: [ + if (form!.footer != null) + Column( + children: [ + HtmlView(html: form!.header!, isSelectable: true), + const SizedBox(height: 16), + ], + ), + ...FormHelper.getAllFormFields(context, _formKey, formHolder!), + const SizedBox(height: 32), + ButtonsHelper.primaryButton( + context: context, + onPressed: _isLoading ? null : _showOrderPreview, + label: "Continue", + isLoading: _isLoading, + height: 50.0, + width: 250.0, ), - ), - const SizedBox(height: 16), - if (form!.footer != null) - Column( - children: [ - HtmlView(html: form!.footer!, isSelectable: true), - const SizedBox(height: 16), - ], - ), - ButtonsHelper.bigButton( - context: context, - onPressed: _isLoading ? null : _showOrderPreview, - label: "Continue".tr(), - isEnabled: !_isLoading, - height: 50.0, - width: 250.0, + const SizedBox(height: 32), + ], ), - ], + ), ), ), ), ), ), - ), + _buildPriceAndTicketInfo(), + ], ), ); } diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 1ef8970f..78647107 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -198,7 +198,7 @@ class FormHelper { ticket.ticketKeys.add(GlobalKey()); }); } - formHolder.controller!.updateTotalPrice?.call(); // Update the total price when adding a ticket + formHolder.controller!.updateTotalPrice?.call(); } void removeTicket(int index) { diff --git a/lib/widgets/ButtonsHelper.dart b/lib/widgets/ButtonsHelper.dart index ae968ffa..9bc0b075 100644 --- a/lib/widgets/ButtonsHelper.dart +++ b/lib/widgets/ButtonsHelper.dart @@ -123,4 +123,46 @@ class ButtonsHelper { ), ); } + + static Widget primaryButton({ + required BuildContext context, + required VoidCallback? onPressed, + required String label, + bool isLoading = false, + double height = 50.0, + double? width, + EdgeInsetsGeometry? padding, + double borderRadius = 8.0, + }) { + return SizedBox( + height: height, + width: width ?? double.infinity, + child: ElevatedButton( + onPressed: isLoading ? null : onPressed, + style: ElevatedButton.styleFrom( + padding: padding ?? EdgeInsets.symmetric(horizontal: 16.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius), + ), + backgroundColor: Theme.of(context).primaryColor, + foregroundColor: Theme.of(context).colorScheme.onPrimary, + elevation: isLoading ? 0 : 4.0, + shadowColor: Colors.black26, + ), + child: isLoading + ? CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + Theme.of(context).colorScheme.onPrimary, + ), + ) + : Text( + label.tr(), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ); + } } From c79246e320253c38441c0d134fef3dbb75fc9d2d Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 8 Dec 2024 03:49:15 +0100 Subject: [PATCH 111/159] transformation fix --- .../widgets/SeatLayoutWidget.dart | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index dd40462d..f894c46c 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; -import 'package:flutter_svg/flutter_svg.dart'; // Add flutter_svg package -import 'package:fstapp/services/Utilities.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import '../model/SeatLayoutStateModel.dart'; import '../model/SeatModel.dart'; @@ -27,7 +26,9 @@ class SeatLayoutWidget extends StatefulWidget { class _SeatLayoutWidgetState extends State { final TransformationController _controller = TransformationController(); late List _seats; + double _minScale = 1.0; // Dynamic minimum scale + @override @override void initState() { super.initState(); @@ -36,11 +37,19 @@ class _SeatLayoutWidgetState extends State { _seats = _generateSeatModels(); + _controller.addListener(_onTransformationChanged); // Add listener + WidgetsBinding.instance.addPostFrameCallback((_) { _fitLayout(); }); } + @override + void dispose() { + _controller.removeListener(_onTransformationChanged); // Remove listener + super.dispose(); + } + List _generateSeatModels() { final List seats = []; for (int row = 0; row < widget.stateModel.rows; row++) { @@ -64,18 +73,22 @@ class _SeatLayoutWidgetState extends State { void _fitLayout() { if (!mounted) return; + // Get the size of the widget (visible area) final RenderBox renderBox = context.findRenderObject() as RenderBox; double widgetWidth = renderBox.size.width; double widgetHeight = renderBox.size.height; + // Calculate the size of the content int layoutWidth = widget.stateModel.cols * widget.stateModel.seatSize; int layoutHeight = widget.stateModel.rows * widget.stateModel.seatSize; + // Determine the scale needed to fit the content fully within the widget double scaleX = widgetWidth / layoutWidth; double scaleY = widgetHeight / layoutHeight; double scaleFactor = scaleX < scaleY ? scaleX : scaleY; setState(() { + _minScale = scaleFactor; // Ensure the minScale allows the content to fit fully _controller.value = Matrix4.identity() ..scale(scaleFactor) ..setTranslationRaw( @@ -92,7 +105,7 @@ class _SeatLayoutWidgetState extends State { int layoutHeight = widget.stateModel.rows * widget.stateModel.seatSize; return InteractiveViewer( - minScale: 0.5, + minScale: _minScale, maxScale: 5, boundaryMargin: const EdgeInsets.all(double.infinity), constrained: false, @@ -174,6 +187,47 @@ class _SeatLayoutWidgetState extends State { }); } } + + void _onTransformationChanged() { + final Matrix4 matrix = _controller.value; + final RenderBox renderBox = context.findRenderObject() as RenderBox; + + double widgetWidth = renderBox.size.width; + double widgetHeight = renderBox.size.height; + + int layoutWidth = widget.stateModel.cols * widget.stateModel.seatSize; + int layoutHeight = widget.stateModel.rows * widget.stateModel.seatSize; + + double scale = matrix.getMaxScaleOnAxis(); + double translateX = matrix.getTranslation().x; + double translateY = matrix.getTranslation().y; + + // Calculate the scaled content size + double scaledWidth = layoutWidth * scale; + double scaledHeight = layoutHeight * scale; + + // Calculate the center offsets for zoomed-out state + double centerOffsetX = (widgetWidth - scaledWidth).clamp(0, double.infinity) / 2; + double centerOffsetY = (widgetHeight - scaledHeight).clamp(0, double.infinity) / 2; + + // Clamping ranges for each side + double minX = widgetWidth < scaledWidth ? widgetWidth - scaledWidth : centerOffsetX; + double maxX = widgetWidth < scaledWidth ? 0 : centerOffsetX; + double minY = widgetHeight < scaledHeight ? widgetHeight - scaledHeight : centerOffsetY; + double maxY = widgetHeight < scaledHeight ? 0 : centerOffsetY; + + // Clamp translation to prevent empty spaces + double clampedX = translateX.clamp(minX, maxX); + double clampedY = translateY.clamp(minY, maxY); + + // Apply only if there is a change + if (clampedX != translateX || clampedY != translateY) { + _controller.value = matrix + ..setTranslationRaw(clampedX, clampedY, 0); + } + } + + } class SeatLayoutWidgetController { From 959a1542b04c6cc4025060c2d7237dceb3cb095a Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 8 Dec 2024 04:40:43 +0100 Subject: [PATCH 112/159] stack optimization --- .../widgets/SeatLayoutWidget.dart | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index f894c46c..a35477cc 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -11,12 +11,13 @@ class SeatLayoutWidget extends StatefulWidget { final SeatLayoutStateModel stateModel; final SeatLayoutWidgetController? controller; final void Function(SeatModel model)? onSeatTap; - + final bool? isEditor; const SeatLayoutWidget({ Key? key, required this.stateModel, this.controller, this.onSeatTap, + this.isEditor, }) : super(key: key); @override @@ -122,18 +123,18 @@ class _SeatLayoutWidgetState extends State { fit: BoxFit.cover, ), ), - // Seat Layout - Column( - mainAxisSize: MainAxisSize.min, - children: List.generate(widget.stateModel.rows, (rowI) { - return Row( - mainAxisSize: MainAxisSize.min, - children: List.generate(widget.stateModel.cols, (colI) { - final seatModel = _seats.firstWhere( - (seat) => seat.rowI == rowI && seat.colI == colI); - return seatModel.objectModel != null && - seatModel.objectModel!.id != null - ? Tooltip( + // Seat Layout with Positioned widgets + SizedBox( + width: layoutWidth.toDouble(), + height: layoutHeight.toDouble(), + child: Stack( + children: _seats.where((seatModel)=>(seatModel.objectModel != null && + seatModel.objectModel!.id != null) || widget.isEditor == true).map((seatModel) { + return Positioned( + left: seatModel.colI * seatModel.seatSize.toDouble(), + top: seatModel.rowI * seatModel.seatSize.toDouble(), + child: + Tooltip( showDuration: const Duration(seconds: 0), message: "${seatModel.objectModel?.blueprintTooltip(context)}", @@ -149,24 +150,14 @@ class _SeatLayoutWidgetState extends State { ), ), ) - : GestureDetector( - onTap: () { - if (widget.onSeatTap != null) { - widget.onSeatTap!(seatModel); - } - }, - child: SeatWidgetHelper.buildSeat( - state: seatModel.seatState, - size: seatModel.seatSize.toDouble(), - ), - ); - }), - ); - }), + ); + }).toList(), + ), ), ], ), ); + } @override From cc078520995953b89757e2d0caff0dea58765629 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 8 Dec 2024 08:55:44 +0100 Subject: [PATCH 113/159] multiseat selection --- .../seatReservation/model/SeatModel.dart | 10 + .../seatReservation/utils/SeatState.dart | 3 - .../widgets/SeatLayoutWidget.dart | 48 +++- .../seatReservation/widgets/SeatWidget.dart | 37 +-- lib/dataModels/FormFields.dart | 63 ++++- .../OrderFinishScreen.dart | 7 +- lib/pages/BlueprintEditorPage.dart | 19 +- lib/pages/FormPage.dart | 85 ++++++- lib/pages/OrderPreviewScreen.dart | 43 ++-- lib/services/FormHelper.dart | 113 ++++----- lib/themeConfig.dart | 1 + lib/widgets/ButtonsHelper.dart | 36 ++- lib/widgets/SeatReservationWidget.dart | 239 ++++++++---------- 13 files changed, 411 insertions(+), 293 deletions(-) diff --git a/lib/components/seatReservation/model/SeatModel.dart b/lib/components/seatReservation/model/SeatModel.dart index ce8327de..9d720bbf 100644 --- a/lib/components/seatReservation/model/SeatModel.dart +++ b/lib/components/seatReservation/model/SeatModel.dart @@ -18,4 +18,14 @@ class SeatModel { this.seatSize = 50, }); + @override + bool operator ==(Object other) => + identical(this, other) || + other is SeatModel && + runtimeType == other.runtimeType && + objectModel?.id == other.objectModel?.id; + + @override + int get hashCode => objectModel?.id.hashCode ?? 0; } + diff --git a/lib/components/seatReservation/utils/SeatState.dart b/lib/components/seatReservation/utils/SeatState.dart index d73290b8..b5315c63 100644 --- a/lib/components/seatReservation/utils/SeatState.dart +++ b/lib/components/seatReservation/utils/SeatState.dart @@ -3,9 +3,6 @@ enum SeatState { /// some user selected this seat selected, - /// current user selected this seat - selected_by_me_focused, - /// current user selected this seat selected_by_me, diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index a35477cc..b0225e3e 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -11,13 +11,13 @@ class SeatLayoutWidget extends StatefulWidget { final SeatLayoutStateModel stateModel; final SeatLayoutWidgetController? controller; final void Function(SeatModel model)? onSeatTap; - final bool? isEditor; + final bool? isEditorMode; const SeatLayoutWidget({ Key? key, required this.stateModel, this.controller, this.onSeatTap, - this.isEditor, + this.isEditorMode, }) : super(key: key); @override @@ -113,14 +113,24 @@ class _SeatLayoutWidgetState extends State { transformationController: _controller, child: Stack( children: [ - // Background SVG if (widget.stateModel.backgroundSvg != null) Positioned.fill( - child: SvgPicture.string( - widget.stateModel.backgroundSvg!, + child: Container( width: layoutWidth.toDouble(), height: layoutHeight.toDouble(), - fit: BoxFit.cover, + decoration: BoxDecoration( + color: Colors.transparent, // No background color + borderRadius: BorderRadius.circular(12.0), // Rounded corners + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(12.0), // Match the rounded corners + child: SvgPicture.string( + widget.stateModel.backgroundSvg!, + width: layoutWidth.toDouble(), + height: layoutHeight.toDouble(), + fit: BoxFit.cover, + ), + ), ), ), // Seat Layout with Positioned widgets @@ -128,13 +138,28 @@ class _SeatLayoutWidgetState extends State { width: layoutWidth.toDouble(), height: layoutHeight.toDouble(), child: Stack( - children: _seats.where((seatModel)=>(seatModel.objectModel != null && - seatModel.objectModel!.id != null) || widget.isEditor == true).map((seatModel) { + children: _seats + .where((seatModel) => + (seatModel.objectModel != null && + seatModel.objectModel!.id != null) || + widget.isEditorMode == true) + .map((seatModel) { return Positioned( left: seatModel.colI * seatModel.seatSize.toDouble(), top: seatModel.rowI * seatModel.seatSize.toDouble(), - child: - Tooltip( + child: seatModel.objectModel == null ? + GestureDetector( + onTap: () { + if (widget.onSeatTap != null) { + widget.onSeatTap!(seatModel); + } + }, + child: SeatWidgetHelper.buildSeat( + state: seatModel.seatState, + size: seatModel.seatSize.toDouble(), + ), + ) + : Tooltip( showDuration: const Duration(seconds: 0), message: "${seatModel.objectModel?.blueprintTooltip(context)}", @@ -149,7 +174,7 @@ class _SeatLayoutWidgetState extends State { size: seatModel.seatSize.toDouble(), ), ), - ) + ), ); }).toList(), ), @@ -157,7 +182,6 @@ class _SeatLayoutWidgetState extends State { ], ), ); - } @override diff --git a/lib/components/seatReservation/widgets/SeatWidget.dart b/lib/components/seatReservation/widgets/SeatWidget.dart index 254b062f..46beab55 100644 --- a/lib/components/seatReservation/widgets/SeatWidget.dart +++ b/lib/components/seatReservation/widgets/SeatWidget.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:fstapp/themeConfig.dart'; import '../utils/SeatState.dart'; class SeatWidgetHelper { - static const double padding = 2.0; - static const double focusedPadding = 1.0; + static const double padding = 2.5; + static const double focusedPadding = 0.8; /// Static method to create a seat widget with a given seat state and size. /// This allows external calls to render a seat without relying on the `SeatModel`. @@ -15,42 +16,48 @@ class SeatWidgetHelper { final bool hasPadding = state == SeatState.ordered || state == SeatState.selected || state == SeatState.selected_by_me || - state == SeatState.selected_by_me_focused || state == SeatState.available; return Container( - color: hasPadding ? Colors.black.withOpacity(0.2) : _getSeatColor(SeatState.empty), + color: hasPadding ? Colors.black.withOpacity(0) : getSeatColor(SeatState.empty), height: size, width: size, child: Container( - margin: hasPadding ? EdgeInsets.all(state == SeatState.selected_by_me_focused ? focusedPadding : padding) : EdgeInsets.zero, + margin: EdgeInsets.all(state == SeatState.selected_by_me ? focusedPadding : (hasPadding ? padding : 0.0)), decoration: BoxDecoration( - color: _getSeatColor(state), + color: getSeatColor(state), borderRadius: BorderRadius.circular(hasPadding ? padding : 0.0), ), + child: state == SeatState.selected_by_me + ? Center( + child: Icon( + Icons.check, + size: size * 0.7, + color: Colors.white, + ), + ) + : null, ), ); } /// Helper method to get seat color based on its state. - static Color _getSeatColor(SeatState state) { + static Color getSeatColor(SeatState state) { switch (state) { case SeatState.available: - return Colors.blueAccent; + return ThemeConfig.greenColor(); case SeatState.selected_by_me: - return Colors.purple; - case SeatState.selected_by_me_focused: - return Colors.purple; + return ThemeConfig.greenColor(); case SeatState.selected: - return Colors.black38; + return Colors.black26; case SeatState.black: return Colors.black87; case SeatState.ordered: - return Colors.black38; + return Colors.black26; case SeatState.empty: - return Colors.black.withOpacity(0.1); + return Colors.black.withOpacity(0); default: - return Colors.black.withOpacity(0.1); + return Colors.black.withOpacity(0); } } } diff --git a/lib/dataModels/FormFields.dart b/lib/dataModels/FormFields.dart index 7bdf183b..042a408d 100644 --- a/lib/dataModels/FormFields.dart +++ b/lib/dataModels/FormFields.dart @@ -1,6 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:fstapp/components/seatReservation/model/SeatModel.dart'; import 'package:fstapp/dataModels/FormOptionModel.dart'; import 'package:fstapp/services/FormHelper.dart'; @@ -91,8 +92,7 @@ class TicketHolder extends FieldHolder { static const String metaTicket = "ticket"; static const String metaMaxTickets = "max_tickets"; - List> ticketValues = []; - List> ticketKeys = []; + List tickets = []; final int maxTickets; final List fields; @@ -125,6 +125,36 @@ class TicketHolder extends FieldHolder { @override String toString() => 'TicketHolder(fields: $fields)'; + + /// Updates the tickets based on the provided list of [seats]. + void updateTickets(List seats) { + // Create a map of existing tickets for fast lookup. + final existingTicketsMap = { + for (var ticket in tickets) ticket.seat.objectModel!.id!: ticket + }; + + // New list of tickets to build. + final List updatedTickets = []; + + for (var seat in seats) { + if (existingTicketsMap.containsKey(seat.objectModel!.id!)) { + // Keep the existing ticket if it matches the seat. + updatedTickets.add(existingTicketsMap[seat.objectModel!.id!]!); + } else { + // Add a new ticket for the seat if it does not exist. + updatedTickets.add( + FormTicketModel( + seat, + fields, + GlobalKey(), + ), + ); + } + } + + // Replace the tickets list with the updated tickets. + tickets = updatedTickets; + } } class FormHolderController{ @@ -133,8 +163,10 @@ class FormHolderController{ final GlobalKey globalKey; final String? formKey; void Function()? updateTotalPrice; + Future?> Function(List)? showSeatReservation; + void Function(List?)? onCloseSeatReservation; - FormHolderController({this.secret, this.blueprintId, required this.globalKey, this.formKey, this.updateTotalPrice}); + FormHolderController({this.secret, this.blueprintId, required this.globalKey, this.formKey, this.updateTotalPrice, this.showSeatReservation, this.onCloseSeatReservation}); } class FormHolder { @@ -173,14 +205,25 @@ class FormHolder { }; } - void addField(FieldHolder field) { - fields.add(field); - } + @override + String toString() => 'FormHolder(fields: $fields)'; +} - void addTicket(TicketHolder ticket) { - fields.add(ticket); - } +class FormTicketModel { + + final SeatModel seat; + final List ticketValues; + final GlobalKey ticketKey; + + FormTicketModel(this.seat, this.ticketValues, this.ticketKey); @override - String toString() => 'FormHolder(fields: $fields)'; + bool operator ==(Object other) => + identical(this, other) || + other is FormTicketModel && + runtimeType == other.runtimeType && + seat.objectModel!.id! == other.seat.objectModel!.id!; + + @override + int get hashCode => seat.objectModel!.id!.hashCode; } diff --git a/lib/pages/AdministrationOccasion/OrderFinishScreen.dart b/lib/pages/AdministrationOccasion/OrderFinishScreen.dart index e54b362f..ab480d7a 100644 --- a/lib/pages/AdministrationOccasion/OrderFinishScreen.dart +++ b/lib/pages/AdministrationOccasion/OrderFinishScreen.dart @@ -99,8 +99,7 @@ class _FinishOrderScreenState extends State Widget build(BuildContext context) { return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, - body: WillPopScope( - onWillPop: () async => false, // Disable back navigation + body: PopScope( child: Center( child: AnimatedSwitcher( duration: const Duration(milliseconds: 500), // Smooth transition @@ -150,7 +149,7 @@ class _FinishOrderScreenState extends State height: 150, decoration: BoxDecoration( shape: BoxShape.circle, - color: _isSuccess ? Colors.green : Colors.red, + color: _isSuccess ? ThemeConfig.greenColor() : ThemeConfig.redColor(context), ), child: Icon( _isSuccess ? Icons.check_circle : Icons.error, @@ -168,7 +167,7 @@ class _FinishOrderScreenState extends State style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: _isSuccess ? Colors.green : Colors.red, + color: _isSuccess ? ThemeConfig.greenColor() : ThemeConfig.redColor(context), ), textAlign: TextAlign.center, ), diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/BlueprintEditorPage.dart index c8c27fae..e29f2a66 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/BlueprintEditorPage.dart @@ -35,10 +35,8 @@ class BlueprintEditorPage extends StatefulWidget { class _BlueprintEditorPageState extends State { BlueprintModel? blueprint; - List? currentBoxes; BlueprintGroupModel? currentGroup; - List allBoxes = []; selectionMode currentSelectionMode = selectionMode.none; @@ -107,6 +105,7 @@ class _BlueprintEditorPageState extends State { child: blueprint == null ? const Center(child: CircularProgressIndicator()) : SeatLayoutWidget( + isEditorMode: true, controller: seatLayoutController, onSeatTap: handleSeatTap, stateModel: SeatLayoutStateModel( @@ -426,9 +425,7 @@ class _BlueprintEditorPageState extends State { }, ), const SizedBox(width: 12), - IconButton( - icon: const Icon(Icons.grid_on), - tooltip: "Background of the blueprint", + TextButton( onPressed: () async { var file = await DialogHelper.dropFilesHere( context, @@ -444,6 +441,14 @@ class _BlueprintEditorPageState extends State { ToastHelper.Show(context, "SVG pozadí bylo úspěšně nahráno."); } }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text("Import background"), + const SizedBox(width: 8), // Optional spacing between text and icon + const Icon(Icons.grid_on), + ], + ), ), ], ); @@ -630,10 +635,6 @@ class _BlueprintEditorPageState extends State { Future loadData() async { blueprint = await DbEshop.getBlueprintForEdit(widget.id!); - - setState(() { - currentBoxes = blueprint!.objects; - }); } } diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 313b0845..9fd3d1fa 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -1,9 +1,11 @@ +import 'dart:async'; import 'dart:convert'; import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:fstapp/components/seatReservation/model/SeatModel.dart'; import 'package:fstapp/dataModels/FormFields.dart'; import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataModels/FormOptionModel.dart'; @@ -21,6 +23,7 @@ import 'package:fstapp/widgets/ButtonsHelper.dart'; import 'package:flutter/services.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:fstapp/widgets/HtmlView.dart'; +import 'package:fstapp/widgets/SeatReservationWidget.dart'; @RoutePage() class FormPage extends StatefulWidget { @@ -40,6 +43,7 @@ class _FormPageState extends State { Map? formResult; FormHolder? formHolder; FormModel? form; + List selectedSeats = []; final _formKey = GlobalKey(); final ScrollController _scrollController = ScrollController(); @@ -54,6 +58,58 @@ class _FormPageState extends State { super.didChangeDependencies(); } + bool _isSeatReservationVisible = false; + Completer?>? _seatReservationCompleter; + + /// Simulates the showDialog functionality for SeatReservation + Future?> _showSeatReservation(List seats) { + selectedSeats = seats; + setState(() { + _isSeatReservationVisible = true; + }); + + _seatReservationCompleter = Completer?>(); + + return _seatReservationCompleter!.future; + } + + /// Hides the SeatReservationWidget and resolves the completer + void _hideSeatReservation(List? seats) { + setState(() { + _isSeatReservationVisible = false; + }); + + _seatReservationCompleter!.complete(seats); + _seatReservationCompleter = null; + } + + Widget _buildSeatReservationOverlay() { + if (!_isSeatReservationVisible) return const SizedBox.shrink(); + + return Positioned.fill( + child: Container( + color: ThemeConfig.dddBackground, // Dim background + child: Center( + child: SeatReservationWidget( + secret: formHolder!.controller!.secret!, + formDataKey: formHolder!.controller!.formKey!, + blueprintId: formHolder!.controller!.blueprintId!, + selectedSeats: selectedSeats, + onSelectionChanged: (sts) { + _totalTickets = sts.length; + _totalPrice = 0; + for(var s in sts){ + _totalPrice += s.objectModel?.product?.price ?? 0; + } + setState(() {}); + }, + onCloseSeatReservation: formHolder!.controller!.onCloseSeatReservation!, + ), + ), + ), + ); + } + void _updateTotalPrice() { _totalPrice = 0.0; _totalTickets = 0; @@ -67,7 +123,22 @@ class _FormPageState extends State { } if (field is TicketHolder) { - _totalTickets = field.ticketKeys.length; + if(_isSeatReservationVisible){ + _totalTickets = selectedSeats.length; + } else { + _totalTickets = field.tickets.length; + } + + if(_isSeatReservationVisible){ + for (var s in selectedSeats) { + _totalPrice += s.objectModel!.product!.price!; + } + } else { + for (var s in field.tickets) { + _totalPrice += s.seat.objectModel!.product!.price!; + } + } + var ticketDataList = FormHelper.getFieldData(_formKey, field) ?? []; for (var ticketData in ticketDataList) { @@ -75,7 +146,7 @@ class _FormPageState extends State { if (ticketValue is FormOptionModel) { _totalPrice += ticketValue.price; } else if (ticketValue is BlueprintObjectModel) { - _totalPrice += ticketValue.product?.price ?? 0; + //_totalPrice += ticketValue.product?.price ?? 0; } } } @@ -94,7 +165,7 @@ class _FormPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), decoration: BoxDecoration( - color: Theme.of(context).primaryColor, // Primary color background + color: Theme.of(context).primaryColor.withOpacity(0.7), // Primary color background borderRadius: BorderRadius.circular(8.0), boxShadow: [BoxShadow(blurRadius: 5, color: Colors.black26)], ), @@ -132,7 +203,7 @@ class _FormPageState extends State { void _showOrderPreview() { TextInput.finishAutofillContext(); - //if (FormHelper.saveAndValidate(formHolder!)) + if (FormHelper.saveAndValidate(formHolder!)) showModalBottomSheet( context: context, isScrollControlled: true, @@ -211,7 +282,7 @@ class _FormPageState extends State { child: AutofillGroup( child: Column( children: [ - if (form!.footer != null) + if (form!.header != null) Column( children: [ HtmlView(html: form!.header!, isSelectable: true), @@ -227,6 +298,7 @@ class _FormPageState extends State { isLoading: _isLoading, height: 50.0, width: 250.0, + isEnabled: _totalPrice > 0 ), const SizedBox(height: 32), ], @@ -237,6 +309,7 @@ class _FormPageState extends State { ), ), ), + _buildSeatReservationOverlay(), _buildPriceAndTicketInfo(), ], ), @@ -320,6 +393,8 @@ class _FormPageState extends State { globalKey: _formKey, formKey: form!.formKey!, updateTotalPrice: _updateTotalPrice, + showSeatReservation: _showSeatReservation, + onCloseSeatReservation: _hideSeatReservation ); form!.data![FormHelper.metaFields] = updatedFields; diff --git a/lib/pages/OrderPreviewScreen.dart b/lib/pages/OrderPreviewScreen.dart index 937fdd35..3bf321c4 100644 --- a/lib/pages/OrderPreviewScreen.dart +++ b/lib/pages/OrderPreviewScreen.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:fstapp/dataModels/FormFields.dart'; import 'package:fstapp/services/FormHelper.dart'; import 'package:fstapp/services/Utilities.dart'; @@ -10,7 +9,7 @@ import 'package:fstapp/widgets/ButtonsHelper.dart'; class OrderPreviewScreen extends StatelessWidget { final FormHolder formHolder; - final double totalPrice; // Injected total price + final double totalPrice; final VoidCallback onSendPressed; static const double fontSizeFactor = 1.2; // Reused font size factor @@ -71,14 +70,13 @@ class OrderPreviewScreen extends StatelessWidget { // Submit Button Center( - child: ButtonsHelper.bigButton( - context: context, - onPressed: onSendPressed, - label: "Odeslat objednávku".tr(), - height: 50.0, - width: 250.0, - ), - ), + child: ButtonsHelper.primaryButton( + context: context, + onPressed: onSendPressed, + label: "Odeslat objednávku", + height: 50.0, + width: 250.0 + )), ], ), ), @@ -133,18 +131,20 @@ class OrderPreviewScreen extends StatelessWidget { final ticketHolder = formHolder.fields .firstWhere((field) => field.fieldType == FormHelper.fieldTypeTicket) as TicketHolder; - return Column( - children: ticketHolder.ticketValues.asMap().entries.map((entry) { - final index = entry.key; // Index for ticket values - final ticketFields = entry.value; - final ticketKey = ticketHolder.ticketKeys[index]; // Get the corresponding key for this ticket + final ticketWidgets = ticketHolder.tickets.asMap().entries.map((entry) { + final ticketIndex = entry.key; + final ticket = entry.value; - return _buildTicketCard(context, ticketFields, ticketKey, index + 1); // Pass ticket key and index - }).toList(), + // Build a card for the ticket with the index (1-based) + return _buildTicketCard(context, ticket, ticketIndex + 1); + }).toList(); + + return Column( + children: ticketWidgets, ); } - Widget _buildTicketCard(BuildContext context, List fieldData, GlobalKey ticketKey, int index) { + Widget _buildTicketCard(BuildContext context, FormTicketModel ticket, int index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Container( @@ -170,13 +170,14 @@ class OrderPreviewScreen extends StatelessWidget { ), const SizedBox(height: 8), + _buildInfoRow("Spot".tr(), ticket.seat.objectModel.toString()), // Display ticket details - ...fieldData.where((entry) { + ...ticket.ticketValues.where((entry) { // Filter out rows with null values - final value = entry.getValue(ticketKey); + final value = entry.getValue(ticket.ticketKey); return value != null && value.toString().isNotEmpty; }).map((entry) { - return _buildInfoRow(entry.label!, entry.getValue(ticketKey).toString()); + return _buildInfoRow(entry.label!, entry.getValue(ticket.ticketKey).toString()); }), ], ), diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index 78647107..c441d9ba 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -9,7 +9,7 @@ import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; -import 'package:fstapp/widgets/SeatReservationWidget.dart'; +import 'package:fstapp/widgets/ButtonsHelper.dart'; class FormHelper { // Field Type Constants @@ -65,8 +65,8 @@ class FormHelper { static bool saveAndValidate(FormHolder formHolder){ bool toReturn = formHolder.controller!.globalKey.currentState?.saveAndValidate() ?? false; - for(var k in formHolder.getTicket()!.ticketKeys){ - if(!(k.currentState?.saveAndValidate() ?? false)){ + for(var k in formHolder.getTicket()!.tickets){ + if(!(k.ticketKey.currentState?.saveAndValidate() ?? false)){ toReturn = false; } } @@ -110,8 +110,8 @@ class FormHelper { List> tickets = []; var ticket = fieldHolder as TicketHolder; - for (int i = 0; i < ticket.ticketKeys.length; i++) { - final ticketKey = ticket.ticketKeys[i]; + for (int i = 0; i < ticket.tickets.length; i++) { + final ticketKey = ticket.tickets[i].ticketKey; if (ticketKey.currentState == null) continue; Map ticketData = {}; @@ -119,6 +119,7 @@ class FormHelper { for(var subFieldHolder in ticket.fields){ ticketData[subFieldHolder.getFieldTypeValue()] = getFieldData(ticketKey, subFieldHolder); } + ticketData[fieldTypeSpot] = ticket.tickets[i].seat.objectModel; tickets.add(ticketData); } @@ -184,37 +185,37 @@ class FormHelper { ) { final maxTickets = ticket.maxTickets; - if (ticket.ticketValues.isEmpty) { - ticket.ticketValues.add(ticket.fields); - ticket.ticketKeys.add(GlobalKey()); - } - return StatefulBuilder( builder: (context, setState) { - void addTicket() { - if (ticket.ticketValues.length < maxTickets) { - setState(() { - ticket.ticketValues.add(ticket.fields); - ticket.ticketKeys.add(GlobalKey()); - }); - } - formHolder.controller!.updateTotalPrice?.call(); - } - void removeTicket(int index) { - if (ticket.ticketValues.length > 1) { - setState(() { - ticket.ticketValues.removeAt(index); - ticket.ticketKeys.removeAt(index); - }); - } + setState(() { + ticket.tickets.removeAt(index); + }); formHolder.controller!.updateTotalPrice?.call(); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - for (int i = 0; i < ticket.ticketValues.length; i++) + const SizedBox(height: 12), + Center( + child: ButtonsHelper.primaryButton( + context: context, + onPressed: () async { + var seats = await formHolder.controller!.showSeatReservation!(ticket.tickets.map((t)=>t.seat).toList()); + if(seats != null){ + ticket.updateTickets(seats); + } + formHolder.controller!.updateTotalPrice?.call(); + }, + label: "Výběr místa", + height: 50.0, + width: 250.0, + suffixIcon: Icon(Icons.event_seat) + ), + ), + const SizedBox(height: 12), + for (int i = 0; i < ticket.tickets.length; i++) Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Align( @@ -244,22 +245,28 @@ class FormHelper { ), ), ), - if (i > 0) - Align( - alignment: Alignment.centerRight, // Align the delete button to the right - child: IconButton( - onPressed: () => removeTicket(i), - icon: Icon(Icons.delete), - tooltip: "Delete".tr(), - ), + Align( + alignment: Alignment.centerRight, // Align the delete button to the right + child: IconButton( + onPressed: () => removeTicket(i), + icon: Icon(Icons.delete), + tooltip: "Delete".tr(), ), + ), ], ), + Text("${ticket.tickets[i].seat.objectModel}", + style: TextStyle( + fontSize: 14 * fontSizeFactor, + ),), + Divider( + color: Colors.black + ), FormBuilder( - key: ticket.ticketKeys[i], // Assign the corresponding key + key: ticket.tickets[i].ticketKey, // Assign the corresponding key onChanged: formHolder.controller!.updateTotalPrice, // Trigger price update on change child: Column( - children: getFormFields(context, ticket.ticketKeys[i], formHolder, ticket.ticketValues[i]), + children: getFormFields(context, ticket.tickets[i].ticketKey, formHolder, ticket.tickets[i].ticketValues), ), ), ], @@ -267,15 +274,6 @@ class FormHelper { ), ), ), - if (ticket.ticketValues.length < maxTickets) - Align( - alignment: Alignment.center, - child: ElevatedButton.icon( - onPressed: addTicket, - icon: Icon(Icons.add, color: ThemeConfig.whiteColor(context)), - label: const Text("Add another ticket").tr(), - ), - ), ], ); }, @@ -307,28 +305,7 @@ class FormHelper { errorText: field.errorText, ), onTap: () async { - SeatModel? selectedSeat = await showGeneralDialog( - context: context, - barrierColor: Colors.black12.withOpacity(0.6), - barrierDismissible: false, - barrierLabel: 'Dialog', - transitionDuration: const Duration(milliseconds: 300), - pageBuilder: (context, __, ___) { - return SeatReservationWidget( - secret: formHolder.controller!.secret!, - formDataKey: formHolder.controller!.formKey!, - blueprintId: formHolder.controller!.blueprintId!, - selectedSeat: field.value, - ); - }, - ); - - if (selectedSeat != null) { - formHolder.controller!.updateTotalPrice?.call(); - field.didChange(selectedSeat); - textController.text = selectedSeat.objectModel?.toString() ?? metaEmpty; - field.validate(); - } + await formHolder.controller!.showSeatReservation!(seat == null ? []:[seat]); }, ); }, diff --git a/lib/themeConfig.dart b/lib/themeConfig.dart index 8b90c10a..7e45da74 100644 --- a/lib/themeConfig.dart +++ b/lib/themeConfig.dart @@ -58,6 +58,7 @@ class ThemeConfig { static Color dddBackgroundDarker = const Color(0xFF191a1e); + static Color greenColor() => Colors.green.changeColorLightness(0.3).changeColorSaturation(0.5); static Color redColor(BuildContext context) => isDarkMode(context) ? Color(0xFFff5252) : Color(0xFFd32f2f); static Color darkColor(BuildContext context) => isDarkMode(context) ? dddText : seed1; static Color blackColor(BuildContext context) => isDarkMode(context) ? dddText : Colors.black; diff --git a/lib/widgets/ButtonsHelper.dart b/lib/widgets/ButtonsHelper.dart index 9bc0b075..11aeba37 100644 --- a/lib/widgets/ButtonsHelper.dart +++ b/lib/widgets/ButtonsHelper.dart @@ -133,20 +133,25 @@ class ButtonsHelper { double? width, EdgeInsetsGeometry? padding, double borderRadius = 8.0, + Widget? prefixIcon, + Widget? suffixIcon, + bool? isEnabled, }) { return SizedBox( height: height, width: width ?? double.infinity, child: ElevatedButton( - onPressed: isLoading ? null : onPressed, + onPressed: (isEnabled ?? true) && !isLoading ? onPressed : null, style: ElevatedButton.styleFrom( padding: padding ?? EdgeInsets.symmetric(horizontal: 16.0), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(borderRadius), ), - backgroundColor: Theme.of(context).primaryColor, + backgroundColor: (isEnabled ?? true) + ? Theme.of(context).primaryColor + : Theme.of(context).disabledColor, foregroundColor: Theme.of(context).colorScheme.onPrimary, - elevation: isLoading ? 0 : 4.0, + elevation: (isEnabled ?? true) && !isLoading ? 4.0 : 0, shadowColor: Colors.black26, ), child: isLoading @@ -155,12 +160,25 @@ class ButtonsHelper { Theme.of(context).colorScheme.onPrimary, ), ) - : Text( - label.tr(), - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (prefixIcon != null) ...[ + prefixIcon, + const SizedBox(width: 8.0), + ], + Text( + label.tr(), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + if (suffixIcon != null) ...[ + const SizedBox(width: 8.0), + suffixIcon, + ], + ], ), ), ); diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index aefd96e5..ac87a746 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -1,11 +1,10 @@ import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:fstapp/components/seatReservation/widgets/SeatWidget.dart'; -import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/styles/StylesConfig.dart'; +import 'package:fstapp/widgets/ButtonsHelper.dart'; import '../components/seatReservation/model/SeatLayoutStateModel.dart'; import '../components/seatReservation/model/SeatModel.dart'; @@ -18,9 +17,19 @@ class SeatReservationWidget extends StatefulWidget { final int blueprintId; final String secret; final String formDataKey; - SeatModel? selectedSeat; + final void Function(List)? onSelectionChanged; + final void Function(List?)? onCloseSeatReservation; + List selectedSeats; - SeatReservationWidget({Key? key, required this.blueprintId, required this.secret, required this.formDataKey, this.selectedSeat}) : super(key: key); + SeatReservationWidget({ + Key? key, + required this.blueprintId, + required this.secret, + required this.formDataKey, + required this.selectedSeats, + this.onSelectionChanged, + this.onCloseSeatReservation, + }) : super(key: key); @override State createState() => _SeatReservationWidgetState(); @@ -28,10 +37,7 @@ class SeatReservationWidget extends StatefulWidget { class _SeatReservationWidgetState extends State { List allObjects = []; - - BlueprintModel? blueprint; - _SeatReservationWidgetState(); @override void didChangeDependencies() { @@ -39,151 +45,110 @@ class _SeatReservationWidgetState extends State { loadData(); } - BlueprintGroupModel? currentBoxGroup; - @override Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: Center( - child: Container( - constraints: const BoxConstraints(maxWidth: StylesConfig.appMaxWidth), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 16), - Text(blueprint?.title ?? "", style: StylesConfig.textStyleBig), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SeatWidgetHelper.buildSeat( - state: SeatState.ordered, - size: 15.0, - ), - const SizedBox(width: 8), - const Text("obsazené"), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SeatWidgetHelper.buildSeat( - state: SeatState.available, - size: SeatReservationWidget.boxSize.toDouble(), - ), - const SizedBox(width: 8), - const Text("dostupné"), - ], + return SafeArea( + child: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: StylesConfig.appMaxWidth), + child: Stack( + children: [ + // Main Content + Column( + children: [ + Flexible( + child: blueprint == null + ? const Center(child: CircularProgressIndicator()) + : Padding( + padding: const EdgeInsets.fromLTRB(12, 24, 12, 0), + child: SeatLayoutWidget( + onSeatTap: (model) async { + await _handleSeatTap(model); + }, + stateModel: SeatLayoutStateModel( + rows: blueprint!.configuration!.height!, + cols: blueprint!.configuration!.width!, + seatSize: SeatReservationWidget.boxSize, + currentObjects: blueprint!.objects!, + allBoxes: allObjects, + backgroundSvg: blueprint!.backgroundSvg, + ), ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SeatWidgetHelper.buildSeat( - state: SeatState.selected_by_me, - size: SeatReservationWidget.boxSize.toDouble(), - ), - const SizedBox(width: 8), - const Text("vybrané"), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SeatWidgetHelper.buildSeat( - state: SeatState.black, - size: 15.0, - ), - const SizedBox(width: 8), - const Text("stůl"), - ], - ), - ], - ), - ), - const SizedBox(height: 12), - Flexible( - child: - blueprint == null - ? const Center(child: CircularProgressIndicator()) : - SeatLayoutWidget( - onSeatTap: (model) async { - if (model.seatState == SeatState.selected_by_me_focused) { - if(await DbEshop.selectSpot(context, widget.formDataKey, widget.secret, widget.selectedSeat!.objectModel!.id!, false)){ - model.seatState = SeatState.available; - widget.selectedSeat = null; - } - } else if (model.seatState == SeatState.available) { - if (widget.selectedSeat != null) { - if(await DbEshop.selectSpot(context, widget.formDataKey, widget.secret, widget.selectedSeat!.objectModel!.id!, false)){ - widget.selectedSeat!.seatState = SeatState.available; - } - } - model.seatState = SeatState.selected_by_me_focused; - widget.selectedSeat = model; - if(!await DbEshop.selectSpot(context, widget.formDataKey, widget.secret, widget.selectedSeat!.objectModel!.id!, true)){ - widget.selectedSeat = null; - model.seatState = SeatState.available; - } - } - setState(() {}); - }, - stateModel: SeatLayoutStateModel( - rows: blueprint!.configuration!.height!, - cols: blueprint!.configuration!.width!, - seatSize: SeatReservationWidget.boxSize, - currentObjects: blueprint!.objects!, - allBoxes: allObjects, - backgroundSvg: blueprint!.backgroundSvg ), ), - ), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Storno").tr(), - ), - const SizedBox(width: 24), - ElevatedButton( - onPressed: () { - // Save changes logic - Navigator.pop(context, widget.selectedSeat); - }, - child: const Text("Save").tr(), + // Bottom Buttons + Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: ButtonsHelper.primaryButton( + context: context, + onPressed: () { + widget.onCloseSeatReservation?.call(widget.selectedSeats); + }, + label: "Continue", + width: 250 + ), ), - ], - ), - const SizedBox(height: 12), - ], - ), + ), + ], + ), + ], ), ), ), ); } - void loadData() async { - blueprint = await DbEshop.getBlueprint(widget.secret, widget.formDataKey, widget.blueprintId); - if (blueprint == null) { - return; + /// Handles Seat Tap Logic + Future _handleSeatTap(SeatModel model) async { + if (model.seatState == SeatState.selected_by_me) { + model.seatState = SeatState.available; + setState(() {}); + if (await DbEshop.selectSpot( + context, + widget.formDataKey, + widget.secret, + model.objectModel!.id!, + false, + )) { + model.seatState = SeatState.available; + model.objectModel!.stateEnum = SeatState.available; + widget.selectedSeats.remove(model); + } else { + model.seatState = SeatState.selected_by_me; + } + } else if (model.seatState == SeatState.available) { + model.seatState = SeatState.selected_by_me; + setState(() {}); + if (await DbEshop.selectSpot( + context, + widget.formDataKey, + widget.secret, + model.objectModel!.id!, + true, + )) { + widget.selectedSeats.add(model); + model.seatState = SeatState.selected_by_me; + model.objectModel!.stateEnum = SeatState.selected_by_me; + } else { + model.seatState = SeatState.available; + } } + widget.onSelectionChanged?.call(widget.selectedSeats); + } - if (widget.selectedSeat != null) { - blueprint?.objects - ?.firstWhereOrNull((object) => object.id == widget.selectedSeat?.objectModel?.id) - ?.stateEnum = SeatState.selected_by_me_focused; - } + /// Loads Blueprint Data + void loadData() async { + blueprint = await DbEshop.getBlueprint( + widget.secret, + widget.formDataKey, + widget.blueprintId, + ); + if (blueprint == null) return; + blueprint?.objects + ?.firstWhereOrNull((object) => widget.selectedSeats.any((s) => s.objectModel!.id! == object.id)) + ?.stateEnum = SeatState.selected_by_me; setState(() {}); } From 877efaed6d774040f4a12cac9978d46002389cae Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 8 Dec 2024 18:10:30 +0100 Subject: [PATCH 114/159] reformat --- lib/dataModels/FormFields.dart | 56 +++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/lib/dataModels/FormFields.dart b/lib/dataModels/FormFields.dart index 042a408d..9c1712e5 100644 --- a/lib/dataModels/FormFields.dart +++ b/lib/dataModels/FormFields.dart @@ -13,19 +13,19 @@ class FieldHolder { final bool isRequired; final String fieldType; dynamic defaultValue; + dynamic getValue(GlobalKey formKey) => - FormHelper.getFieldData(formKey, this); + FormHelper.getFieldData(formKey, this); String getFieldTypeValue() => FormHelper.getFieldTypeValue(this); String? label; - FieldHolder({ - required this.fieldType, - required this.isRequired, - this.defaultValue, - this.label - }); + FieldHolder( + {required this.fieldType, + required this.isRequired, + this.defaultValue, + this.label}); factory FieldHolder.fromJson(Map json) { return FieldHolder( @@ -61,14 +61,19 @@ class OptionsFieldHolder extends FieldHolder { required this.options, required this.optionsType, required label, - }) : super(fieldType: fieldType, defaultValue: value, isRequired: true, label: label); + }) : super( + fieldType: fieldType, + defaultValue: value, + isRequired: true, + label: label); factory OptionsFieldHolder.fromJson(Map json) { return OptionsFieldHolder( fieldType: json[FieldHolder.metaType], value: json[FieldHolder.metaValue], label: json[metaLabel], - options: List.from(json[metaOptions].map((o) => FormOptionModel.fromJson(o))), + options: List.from( + json[metaOptions].map((o) => FormOptionModel.fromJson(o))), optionsType: json[metaOptionsType], ); } @@ -109,8 +114,9 @@ class TicketHolder extends FieldHolder { maxTickets: json[metaMaxTickets] ?? 1, fieldType: json[FieldHolder.metaType], fields: (json[metaFields] as List>?) - ?.map((e) => FormHolder.determineFieldType(e)) - .toList() ?? [], + ?.map((e) => FormHolder.determineFieldType(e)) + .toList() ?? + [], ); } @@ -123,8 +129,7 @@ class TicketHolder extends FieldHolder { } @override - String toString() => - 'TicketHolder(fields: $fields)'; + String toString() => 'TicketHolder(fields: $fields)'; /// Updates the tickets based on the provided list of [seats]. void updateTickets(List seats) { @@ -157,7 +162,7 @@ class TicketHolder extends FieldHolder { } } -class FormHolderController{ +class FormHolderController { final String? secret; final int? blueprintId; final GlobalKey globalKey; @@ -166,7 +171,14 @@ class FormHolderController{ Future?> Function(List)? showSeatReservation; void Function(List?)? onCloseSeatReservation; - FormHolderController({this.secret, this.blueprintId, required this.globalKey, this.formKey, this.updateTotalPrice, this.showSeatReservation, this.onCloseSeatReservation}); + FormHolderController( + {this.secret, + this.blueprintId, + required this.globalKey, + this.formKey, + this.updateTotalPrice, + this.showSeatReservation, + this.onCloseSeatReservation}); } class FormHolder { @@ -175,15 +187,16 @@ class FormHolder { FormHolderController? controller; final List fields; - TicketHolder? getTicket() => fields.firstWhereOrNull((f)=>f is TicketHolder) as TicketHolder; + TicketHolder? getTicket() => + fields.firstWhereOrNull((f) => f is TicketHolder) as TicketHolder; FormHolder({required this.fields}); factory FormHolder.fromJson(Map json) { return FormHolder( fields: (json[metaFields] as List>?) - ?.map((e) => determineFieldType(e)) - .toList() ?? + ?.map((e) => determineFieldType(e)) + .toList() ?? [], ); } @@ -210,7 +223,6 @@ class FormHolder { } class FormTicketModel { - final SeatModel seat; final List ticketValues; final GlobalKey ticketKey; @@ -220,9 +232,9 @@ class FormTicketModel { @override bool operator ==(Object other) => identical(this, other) || - other is FormTicketModel && - runtimeType == other.runtimeType && - seat.objectModel!.id! == other.seat.objectModel!.id!; + other is FormTicketModel && + runtimeType == other.runtimeType && + seat.objectModel!.id! == other.seat.objectModel!.id!; @override int get hashCode => seat.objectModel!.id!.hashCode; From ae0f42847f1b917bbdb265fb02e381b4090b9b33 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 8 Dec 2024 18:49:09 +0100 Subject: [PATCH 115/159] fixes --- lib/pages/FormPage.dart | 2 ++ lib/pages/OrderPreviewScreen.dart | 14 +++++++++----- lib/services/FormHelper.dart | 7 ++++--- lib/widgets/SeatReservationWidget.dart | 7 +++++++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 9fd3d1fa..f23e2a9a 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -91,10 +91,12 @@ class _FormPageState extends State { color: ThemeConfig.dddBackground, // Dim background child: Center( child: SeatReservationWidget( + secret: formHolder!.controller!.secret!, formDataKey: formHolder!.controller!.formKey!, blueprintId: formHolder!.controller!.blueprintId!, selectedSeats: selectedSeats, + maxTickets: formHolder!.getTicket()?.maxTickets, onSelectionChanged: (sts) { _totalTickets = sts.length; _totalPrice = 0; diff --git a/lib/pages/OrderPreviewScreen.dart b/lib/pages/OrderPreviewScreen.dart index 3bf321c4..a2f7d38e 100644 --- a/lib/pages/OrderPreviewScreen.dart +++ b/lib/pages/OrderPreviewScreen.dart @@ -12,7 +12,7 @@ class OrderPreviewScreen extends StatelessWidget { final double totalPrice; final VoidCallback onSendPressed; - static const double fontSizeFactor = 1.2; // Reused font size factor + static const double fontSizeFactor = 1.2; const OrderPreviewScreen({ super.key, @@ -119,10 +119,14 @@ class OrderPreviewScreen extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: personalInfoFields.map((field) { - return _buildInfoRow( - field.label!, - field.getValue(formHolder.controller!.globalKey).toString(), - ); + var value = field.getValue(formHolder.controller!.globalKey); + if(value != null){ + return _buildInfoRow( + field.label!, + value.toString(), + ); + } + return SizedBox.shrink(); }).toList(), ); } diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index c441d9ba..bc17de81 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; +import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; import 'package:fstapp/widgets/ButtonsHelper.dart'; @@ -183,11 +184,11 @@ class FormHelper { FormHolder formHolder, TicketHolder ticket ) { - final maxTickets = ticket.maxTickets; return StatefulBuilder( builder: (context, setState) { - void removeTicket(int index) { + Future removeTicket(int index) async { + await DbEshop.selectSpot(context, formHolder.controller!.formKey!, formHolder.controller!.secret!, ticket.tickets[index].seat.objectModel!.id!, false); setState(() { ticket.tickets.removeAt(index); }); @@ -246,7 +247,7 @@ class FormHelper { ), ), Align( - alignment: Alignment.centerRight, // Align the delete button to the right + alignment: Alignment.centerRight, child: IconButton( onPressed: () => removeTicket(i), icon: Icon(Icons.delete), diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index ac87a746..31b29161 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/widgets/ButtonsHelper.dart'; @@ -20,6 +21,7 @@ class SeatReservationWidget extends StatefulWidget { final void Function(List)? onSelectionChanged; final void Function(List?)? onCloseSeatReservation; List selectedSeats; + final int? maxTickets; SeatReservationWidget({ Key? key, @@ -27,6 +29,7 @@ class SeatReservationWidget extends StatefulWidget { required this.secret, required this.formDataKey, required this.selectedSeats, + this.maxTickets, this.onSelectionChanged, this.onCloseSeatReservation, }) : super(key: key); @@ -118,6 +121,10 @@ class _SeatReservationWidgetState extends State { model.seatState = SeatState.selected_by_me; } } else if (model.seatState == SeatState.available) { + if(widget.maxTickets != null && widget.selectedSeats.length >= widget.maxTickets!){ + ToastHelper.Show(context, "Více vstupenek není možné vybrat.".tr()); + return; + } model.seatState = SeatState.selected_by_me; setState(() {}); if (await DbEshop.selectSpot( From 18bbea390bdc924cfd60dd93d3cc63362f96d8ea Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 8 Dec 2024 22:54:12 +0100 Subject: [PATCH 116/159] form edit page --- lib/AppRouter.dart | 4 + lib/AppRouter.gr.dart | 474 +++++++++++++++++++--------------- lib/dataModels/FormModel.dart | 20 ++ lib/dataModels/Tb.dart | 1 + lib/dataServices/DbEshop.dart | 27 ++ lib/pages/FormEditPage.dart | 153 +++++++++++ lib/pages/FormPage.dart | 15 ++ 7 files changed, 484 insertions(+), 210 deletions(-) create mode 100644 lib/pages/FormEditPage.dart diff --git a/lib/AppRouter.dart b/lib/AppRouter.dart index 2f1f73e5..1a391006 100644 --- a/lib/AppRouter.dart +++ b/lib/AppRouter.dart @@ -6,6 +6,7 @@ import 'package:fstapp/pages/BlueprintEditorPage.dart'; import 'package:fstapp/pages/CheckPage.dart'; import 'package:fstapp/pages/EventEditPage.dart'; import 'package:fstapp/pages/EventPage.dart'; +import 'package:fstapp/pages/FormEditPage.dart'; import 'package:fstapp/pages/FormPage.dart'; import 'package:fstapp/pages/HtmlEditorPage.dart'; import 'package:fstapp/pages/InfoPage.dart'; @@ -46,6 +47,9 @@ class AppRouter extends RootStackRouter { AutoRoute(page: AdminDashboardRoute.page, path: sl(AdminDashboardPage.ROUTE)), AutoRoute(page: AdminDashboardRoute.page, path: sl(AdminDashboardPage.ROUTE)), AutoRoute(page: FormRoute.page, path: "/${FormPage.ROUTE}/:id"), + AutoRoute(page: FormEditRoute.page, path: "/${FormEditPage.ROUTE}", children: [ + AutoRoute(path: ':formKey', page: FormEditRoute.page,), + ]), AutoRoute(page: CheckRoute.page, path: "/:{$LINK}/${CheckPage.ROUTE}/:id"), AutoRoute(page: NewsFormRoute.page, path: "/:{$LINK}/${NewsFormPage.ROUTE}"), AutoRoute(page: HtmlEditorRoute.page, path: "/:{$LINK}/${HtmlEditorPage.ROUTE}"), diff --git a/lib/AppRouter.gr.dart b/lib/AppRouter.gr.dart index 0be52e5d..fc03b865 100644 --- a/lib/AppRouter.gr.dart +++ b/lib/AppRouter.gr.dart @@ -8,10 +8,10 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i27; -import 'package:flutter/foundation.dart' as _i29; -import 'package:flutter/material.dart' as _i28; -import 'package:fstapp/dataModels/PlaceModel.dart' as _i30; +import 'package:auto_route/auto_route.dart' as _i28; +import 'package:flutter/foundation.dart' as _i30; +import 'package:flutter/material.dart' as _i29; +import 'package:fstapp/dataModels/PlaceModel.dart' as _i31; import 'package:fstapp/pages/AdminDashboardPage.dart' deferred as _i1; import 'package:fstapp/pages/AdministrationOccasion/AdminPage.dart' deferred as _i2; @@ -20,30 +20,31 @@ import 'package:fstapp/pages/CheckPage.dart' deferred as _i4; import 'package:fstapp/pages/EventEditPage.dart' deferred as _i5; import 'package:fstapp/pages/EventPage.dart' deferred as _i6; import 'package:fstapp/pages/ForgotPasswordPage.dart' deferred as _i7; -import 'package:fstapp/pages/FormPage.dart' deferred as _i8; -import 'package:fstapp/pages/GamePage.dart' deferred as _i9; -import 'package:fstapp/pages/HtmlEditorPage.dart' deferred as _i10; -import 'package:fstapp/pages/InfoPage.dart' deferred as _i11; -import 'package:fstapp/pages/InstallPage.dart' deferred as _i12; -import 'package:fstapp/pages/LoginPage.dart' deferred as _i13; -import 'package:fstapp/pages/MapPage.dart' deferred as _i14; -import 'package:fstapp/pages/MySchedulePage.dart' deferred as _i15; -import 'package:fstapp/pages/NewsFormPage.dart' deferred as _i16; -import 'package:fstapp/pages/NewsPage.dart' deferred as _i17; -import 'package:fstapp/pages/OccasionHomePage.dart' deferred as _i18; -import 'package:fstapp/pages/ScheduleNavigationScreen.dart' deferred as _i20; -import 'package:fstapp/pages/SchedulePage.dart' deferred as _i21; -import 'package:fstapp/pages/SettingsPage.dart' deferred as _i22; -import 'package:fstapp/pages/SignupPage.dart' deferred as _i23; -import 'package:fstapp/pages/SignupPasswordPage.dart' deferred as _i19; -import 'package:fstapp/pages/SongPage.dart' deferred as _i24; -import 'package:fstapp/pages/TimetablePage.dart' deferred as _i25; -import 'package:fstapp/pages/UserPage.dart' deferred as _i26; +import 'package:fstapp/pages/FormEditPage.dart' deferred as _i8; +import 'package:fstapp/pages/FormPage.dart' deferred as _i9; +import 'package:fstapp/pages/GamePage.dart' deferred as _i10; +import 'package:fstapp/pages/HtmlEditorPage.dart' deferred as _i11; +import 'package:fstapp/pages/InfoPage.dart' deferred as _i12; +import 'package:fstapp/pages/InstallPage.dart' deferred as _i13; +import 'package:fstapp/pages/LoginPage.dart' deferred as _i14; +import 'package:fstapp/pages/MapPage.dart' deferred as _i15; +import 'package:fstapp/pages/MySchedulePage.dart' deferred as _i16; +import 'package:fstapp/pages/NewsFormPage.dart' deferred as _i17; +import 'package:fstapp/pages/NewsPage.dart' deferred as _i18; +import 'package:fstapp/pages/OccasionHomePage.dart' deferred as _i19; +import 'package:fstapp/pages/ScheduleNavigationScreen.dart' deferred as _i21; +import 'package:fstapp/pages/SchedulePage.dart' deferred as _i22; +import 'package:fstapp/pages/SettingsPage.dart' deferred as _i23; +import 'package:fstapp/pages/SignupPage.dart' deferred as _i24; +import 'package:fstapp/pages/SignupPasswordPage.dart' deferred as _i20; +import 'package:fstapp/pages/SongPage.dart' deferred as _i25; +import 'package:fstapp/pages/TimetablePage.dart' deferred as _i26; +import 'package:fstapp/pages/UserPage.dart' deferred as _i27; /// generated route for /// [_i1.AdminDashboardPage] -class AdminDashboardRoute extends _i27.PageRouteInfo { - const AdminDashboardRoute({List<_i27.PageRouteInfo>? children}) +class AdminDashboardRoute extends _i28.PageRouteInfo { + const AdminDashboardRoute({List<_i28.PageRouteInfo>? children}) : super( AdminDashboardRoute.name, initialChildren: children, @@ -51,10 +52,10 @@ class AdminDashboardRoute extends _i27.PageRouteInfo { static const String name = 'AdminDashboardRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( + return _i28.DeferredWidget( _i1.loadLibrary, () => _i1.AdminDashboardPage(), ); @@ -64,8 +65,8 @@ class AdminDashboardRoute extends _i27.PageRouteInfo { /// generated route for /// [_i2.AdminPage] -class AdminRoute extends _i27.PageRouteInfo { - const AdminRoute({List<_i27.PageRouteInfo>? children}) +class AdminRoute extends _i28.PageRouteInfo { + const AdminRoute({List<_i28.PageRouteInfo>? children}) : super( AdminRoute.name, initialChildren: children, @@ -73,10 +74,10 @@ class AdminRoute extends _i27.PageRouteInfo { static const String name = 'AdminRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( + return _i28.DeferredWidget( _i2.loadLibrary, () => _i2.AdminPage(), ); @@ -87,11 +88,11 @@ class AdminRoute extends _i27.PageRouteInfo { /// generated route for /// [_i3.BlueprintEditorPage] class BlueprintEditorRoute - extends _i27.PageRouteInfo { + extends _i28.PageRouteInfo { BlueprintEditorRoute({ - _i28.Key? key, + _i29.Key? key, int? id, - List<_i27.PageRouteInfo>? children, + List<_i28.PageRouteInfo>? children, }) : super( BlueprintEditorRoute.name, args: BlueprintEditorRouteArgs( @@ -104,13 +105,13 @@ class BlueprintEditorRoute static const String name = 'BlueprintEditorRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => BlueprintEditorRouteArgs(id: pathParams.optInt('id'))); - return _i27.DeferredWidget( + return _i28.DeferredWidget( _i3.loadLibrary, () => _i3.BlueprintEditorPage( key: args.key, @@ -127,7 +128,7 @@ class BlueprintEditorRouteArgs { this.id, }); - final _i28.Key? key; + final _i29.Key? key; final int? id; @@ -139,11 +140,11 @@ class BlueprintEditorRouteArgs { /// generated route for /// [_i4.CheckPage] -class CheckRoute extends _i27.PageRouteInfo { +class CheckRoute extends _i28.PageRouteInfo { CheckRoute({ required int id, - _i28.Key? key, - List<_i27.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( CheckRoute.name, args: CheckRouteArgs( @@ -156,13 +157,13 @@ class CheckRoute extends _i27.PageRouteInfo { static const String name = 'CheckRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => CheckRouteArgs(id: pathParams.getInt('id'))); - return _i27.DeferredWidget( + return _i28.DeferredWidget( _i4.loadLibrary, () => _i4.CheckPage( id: args.id, @@ -181,7 +182,7 @@ class CheckRouteArgs { final int id; - final _i28.Key? key; + final _i29.Key? key; @override String toString() { @@ -191,11 +192,11 @@ class CheckRouteArgs { /// generated route for /// [_i5.EventEditPage] -class EventEditRoute extends _i27.PageRouteInfo { +class EventEditRoute extends _i28.PageRouteInfo { EventEditRoute({ - _i28.Key? key, + _i29.Key? key, int? id, - List<_i27.PageRouteInfo>? children, + List<_i28.PageRouteInfo>? children, }) : super( EventEditRoute.name, args: EventEditRouteArgs( @@ -208,13 +209,13 @@ class EventEditRoute extends _i27.PageRouteInfo { static const String name = 'EventEditRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => EventEditRouteArgs(id: pathParams.optInt('id'))); - return _i27.DeferredWidget( + return _i28.DeferredWidget( _i5.loadLibrary, () => _i5.EventEditPage( key: args.key, @@ -231,7 +232,7 @@ class EventEditRouteArgs { this.id, }); - final _i28.Key? key; + final _i29.Key? key; final int? id; @@ -243,11 +244,11 @@ class EventEditRouteArgs { /// generated route for /// [_i6.EventPage] -class EventRoute extends _i27.PageRouteInfo { +class EventRoute extends _i28.PageRouteInfo { EventRoute({ int? id, - _i28.Key? key, - List<_i27.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( EventRoute.name, args: EventRouteArgs( @@ -260,13 +261,13 @@ class EventRoute extends _i27.PageRouteInfo { static const String name = 'EventRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => EventRouteArgs(id: pathParams.optInt('id'))); - return _i27.DeferredWidget( + return _i28.DeferredWidget( _i6.loadLibrary, () => _i6.EventPage( id: args.id, @@ -285,7 +286,7 @@ class EventRouteArgs { final int? id; - final _i28.Key? key; + final _i29.Key? key; @override String toString() { @@ -295,8 +296,8 @@ class EventRouteArgs { /// generated route for /// [_i7.ForgotPasswordPage] -class ForgotPasswordRoute extends _i27.PageRouteInfo { - const ForgotPasswordRoute({List<_i27.PageRouteInfo>? children}) +class ForgotPasswordRoute extends _i28.PageRouteInfo { + const ForgotPasswordRoute({List<_i28.PageRouteInfo>? children}) : super( ForgotPasswordRoute.name, initialChildren: children, @@ -304,10 +305,10 @@ class ForgotPasswordRoute extends _i27.PageRouteInfo { static const String name = 'ForgotPasswordRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( + return _i28.DeferredWidget( _i7.loadLibrary, () => _i7.ForgotPasswordPage(), ); @@ -316,11 +317,64 @@ class ForgotPasswordRoute extends _i27.PageRouteInfo { } /// generated route for -/// [_i8.FormPage] -class FormRoute extends _i27.PageRouteInfo { +/// [_i8.FormEditPage] +class FormEditRoute extends _i28.PageRouteInfo { + FormEditRoute({ + _i29.Key? key, + String? formKey, + List<_i28.PageRouteInfo>? children, + }) : super( + FormEditRoute.name, + args: FormEditRouteArgs( + key: key, + formKey: formKey, + ), + rawPathParams: {'formKey': formKey}, + initialChildren: children, + ); + + static const String name = 'FormEditRoute'; + + static _i28.PageInfo page = _i28.PageInfo( + name, + builder: (data) { + final pathParams = data.inheritedPathParams; + final args = data.argsAs( + orElse: () => + FormEditRouteArgs(formKey: pathParams.optString('formKey'))); + return _i28.DeferredWidget( + _i8.loadLibrary, + () => _i8.FormEditPage( + key: args.key, + formKey: args.formKey, + ), + ); + }, + ); +} + +class FormEditRouteArgs { + const FormEditRouteArgs({ + this.key, + this.formKey, + }); + + final _i29.Key? key; + + final String? formKey; + + @override + String toString() { + return 'FormEditRouteArgs{key: $key, formKey: $formKey}'; + } +} + +/// generated route for +/// [_i9.FormPage] +class FormRoute extends _i28.PageRouteInfo { FormRoute({ - _i28.Key? key, - List<_i27.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( FormRoute.name, args: FormRouteArgs(key: key), @@ -329,14 +383,14 @@ class FormRoute extends _i27.PageRouteInfo { static const String name = 'FormRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const FormRouteArgs()); - return _i27.DeferredWidget( - _i8.loadLibrary, - () => _i8.FormPage(key: args.key), + return _i28.DeferredWidget( + _i9.loadLibrary, + () => _i9.FormPage(key: args.key), ); }, ); @@ -345,7 +399,7 @@ class FormRoute extends _i27.PageRouteInfo { class FormRouteArgs { const FormRouteArgs({this.key}); - final _i28.Key? key; + final _i29.Key? key; @override String toString() { @@ -354,11 +408,11 @@ class FormRouteArgs { } /// generated route for -/// [_i9.GamePage] -class GameRoute extends _i27.PageRouteInfo { +/// [_i10.GamePage] +class GameRoute extends _i28.PageRouteInfo { GameRoute({ - _i28.Key? key, - List<_i27.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( GameRoute.name, args: GameRouteArgs(key: key), @@ -367,14 +421,14 @@ class GameRoute extends _i27.PageRouteInfo { static const String name = 'GameRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const GameRouteArgs()); - return _i27.DeferredWidget( - _i9.loadLibrary, - () => _i9.GamePage(key: args.key), + return _i28.DeferredWidget( + _i10.loadLibrary, + () => _i10.GamePage(key: args.key), ); }, ); @@ -383,7 +437,7 @@ class GameRoute extends _i27.PageRouteInfo { class GameRouteArgs { const GameRouteArgs({this.key}); - final _i28.Key? key; + final _i29.Key? key; @override String toString() { @@ -392,12 +446,12 @@ class GameRouteArgs { } /// generated route for -/// [_i10.HtmlEditorPage] -class HtmlEditorRoute extends _i27.PageRouteInfo { +/// [_i11.HtmlEditorPage] +class HtmlEditorRoute extends _i28.PageRouteInfo { HtmlEditorRoute({ Map? content, - _i28.Key? key, - List<_i27.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( HtmlEditorRoute.name, args: HtmlEditorRouteArgs( @@ -409,14 +463,14 @@ class HtmlEditorRoute extends _i27.PageRouteInfo { static const String name = 'HtmlEditorRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const HtmlEditorRouteArgs()); - return _i27.DeferredWidget( - _i10.loadLibrary, - () => _i10.HtmlEditorPage( + return _i28.DeferredWidget( + _i11.loadLibrary, + () => _i11.HtmlEditorPage( content: args.content, key: args.key, ), @@ -433,7 +487,7 @@ class HtmlEditorRouteArgs { final Map? content; - final _i28.Key? key; + final _i29.Key? key; @override String toString() { @@ -442,12 +496,12 @@ class HtmlEditorRouteArgs { } /// generated route for -/// [_i11.InfoPage] -class InfoRoute extends _i27.PageRouteInfo { +/// [_i12.InfoPage] +class InfoRoute extends _i28.PageRouteInfo { InfoRoute({ int? id, - _i29.Key? key, - List<_i27.PageRouteInfo>? children, + _i30.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( InfoRoute.name, args: InfoRouteArgs( @@ -460,15 +514,15 @@ class InfoRoute extends _i27.PageRouteInfo { static const String name = 'InfoRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => InfoRouteArgs(id: pathParams.optInt('id'))); - return _i27.DeferredWidget( - _i11.loadLibrary, - () => _i11.InfoPage( + return _i28.DeferredWidget( + _i12.loadLibrary, + () => _i12.InfoPage( id: args.id, key: args.key, ), @@ -485,7 +539,7 @@ class InfoRouteArgs { final int? id; - final _i29.Key? key; + final _i30.Key? key; @override String toString() { @@ -494,9 +548,9 @@ class InfoRouteArgs { } /// generated route for -/// [_i12.InstallPage] -class InstallRoute extends _i27.PageRouteInfo { - const InstallRoute({List<_i27.PageRouteInfo>? children}) +/// [_i13.InstallPage] +class InstallRoute extends _i28.PageRouteInfo { + const InstallRoute({List<_i28.PageRouteInfo>? children}) : super( InstallRoute.name, initialChildren: children, @@ -504,21 +558,21 @@ class InstallRoute extends _i27.PageRouteInfo { static const String name = 'InstallRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i12.loadLibrary, - () => _i12.InstallPage(), + return _i28.DeferredWidget( + _i13.loadLibrary, + () => _i13.InstallPage(), ); }, ); } /// generated route for -/// [_i13.LoginPage] -class LoginRoute extends _i27.PageRouteInfo { - const LoginRoute({List<_i27.PageRouteInfo>? children}) +/// [_i14.LoginPage] +class LoginRoute extends _i28.PageRouteInfo { + const LoginRoute({List<_i28.PageRouteInfo>? children}) : super( LoginRoute.name, initialChildren: children, @@ -526,25 +580,25 @@ class LoginRoute extends _i27.PageRouteInfo { static const String name = 'LoginRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i13.loadLibrary, - () => _i13.LoginPage(), + return _i28.DeferredWidget( + _i14.loadLibrary, + () => _i14.LoginPage(), ); }, ); } /// generated route for -/// [_i14.MapPage] -class MapRoute extends _i27.PageRouteInfo { +/// [_i15.MapPage] +class MapRoute extends _i28.PageRouteInfo { MapRoute({ int? id, - _i30.PlaceModel? place, - _i28.Key? key, - List<_i27.PageRouteInfo>? children, + _i31.PlaceModel? place, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( MapRoute.name, args: MapRouteArgs( @@ -558,15 +612,15 @@ class MapRoute extends _i27.PageRouteInfo { static const String name = 'MapRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => MapRouteArgs(id: pathParams.optInt('id'))); - return _i27.DeferredWidget( - _i14.loadLibrary, - () => _i14.MapPage( + return _i28.DeferredWidget( + _i15.loadLibrary, + () => _i15.MapPage( id: args.id, place: args.place, key: args.key, @@ -585,9 +639,9 @@ class MapRouteArgs { final int? id; - final _i30.PlaceModel? place; + final _i31.PlaceModel? place; - final _i28.Key? key; + final _i29.Key? key; @override String toString() { @@ -596,9 +650,9 @@ class MapRouteArgs { } /// generated route for -/// [_i15.MySchedulePage] -class MyScheduleRoute extends _i27.PageRouteInfo { - const MyScheduleRoute({List<_i27.PageRouteInfo>? children}) +/// [_i16.MySchedulePage] +class MyScheduleRoute extends _i28.PageRouteInfo { + const MyScheduleRoute({List<_i28.PageRouteInfo>? children}) : super( MyScheduleRoute.name, initialChildren: children, @@ -606,21 +660,21 @@ class MyScheduleRoute extends _i27.PageRouteInfo { static const String name = 'MyScheduleRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i15.loadLibrary, - () => _i15.MySchedulePage(), + return _i28.DeferredWidget( + _i16.loadLibrary, + () => _i16.MySchedulePage(), ); }, ); } /// generated route for -/// [_i16.NewsFormPage] -class NewsFormRoute extends _i27.PageRouteInfo { - const NewsFormRoute({List<_i27.PageRouteInfo>? children}) +/// [_i17.NewsFormPage] +class NewsFormRoute extends _i28.PageRouteInfo { + const NewsFormRoute({List<_i28.PageRouteInfo>? children}) : super( NewsFormRoute.name, initialChildren: children, @@ -628,24 +682,24 @@ class NewsFormRoute extends _i27.PageRouteInfo { static const String name = 'NewsFormRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i16.loadLibrary, - () => _i16.NewsFormPage(), + return _i28.DeferredWidget( + _i17.loadLibrary, + () => _i17.NewsFormPage(), ); }, ); } /// generated route for -/// [_i17.NewsPage] -class NewsRoute extends _i27.PageRouteInfo { +/// [_i18.NewsPage] +class NewsRoute extends _i28.PageRouteInfo { NewsRoute({ - _i28.Key? key, - _i28.VoidCallback? onSetAsRead, - List<_i27.PageRouteInfo>? children, + _i29.Key? key, + _i29.VoidCallback? onSetAsRead, + List<_i28.PageRouteInfo>? children, }) : super( NewsRoute.name, args: NewsRouteArgs( @@ -657,14 +711,14 @@ class NewsRoute extends _i27.PageRouteInfo { static const String name = 'NewsRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const NewsRouteArgs()); - return _i27.DeferredWidget( - _i17.loadLibrary, - () => _i17.NewsPage( + return _i28.DeferredWidget( + _i18.loadLibrary, + () => _i18.NewsPage( key: args.key, onSetAsRead: args.onSetAsRead, ), @@ -679,9 +733,9 @@ class NewsRouteArgs { this.onSetAsRead, }); - final _i28.Key? key; + final _i29.Key? key; - final _i28.VoidCallback? onSetAsRead; + final _i29.VoidCallback? onSetAsRead; @override String toString() { @@ -690,9 +744,9 @@ class NewsRouteArgs { } /// generated route for -/// [_i18.OccasionHomePage] -class OccasionHomeRoute extends _i27.PageRouteInfo { - const OccasionHomeRoute({List<_i27.PageRouteInfo>? children}) +/// [_i19.OccasionHomePage] +class OccasionHomeRoute extends _i28.PageRouteInfo { + const OccasionHomeRoute({List<_i28.PageRouteInfo>? children}) : super( OccasionHomeRoute.name, initialChildren: children, @@ -700,21 +754,21 @@ class OccasionHomeRoute extends _i27.PageRouteInfo { static const String name = 'OccasionHomeRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i18.loadLibrary, - () => _i18.OccasionHomePage(), + return _i28.DeferredWidget( + _i19.loadLibrary, + () => _i19.OccasionHomePage(), ); }, ); } /// generated route for -/// [_i19.ResetPasswordPage] -class ResetPasswordRoute extends _i27.PageRouteInfo { - const ResetPasswordRoute({List<_i27.PageRouteInfo>? children}) +/// [_i20.ResetPasswordPage] +class ResetPasswordRoute extends _i28.PageRouteInfo { + const ResetPasswordRoute({List<_i28.PageRouteInfo>? children}) : super( ResetPasswordRoute.name, initialChildren: children, @@ -722,21 +776,21 @@ class ResetPasswordRoute extends _i27.PageRouteInfo { static const String name = 'ResetPasswordRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i19.loadLibrary, - () => _i19.ResetPasswordPage(), + return _i28.DeferredWidget( + _i20.loadLibrary, + () => _i20.ResetPasswordPage(), ); }, ); } /// generated route for -/// [_i20.ScheduleNavigationPage] -class ScheduleNavigationRoute extends _i27.PageRouteInfo { - const ScheduleNavigationRoute({List<_i27.PageRouteInfo>? children}) +/// [_i21.ScheduleNavigationPage] +class ScheduleNavigationRoute extends _i28.PageRouteInfo { + const ScheduleNavigationRoute({List<_i28.PageRouteInfo>? children}) : super( ScheduleNavigationRoute.name, initialChildren: children, @@ -744,21 +798,21 @@ class ScheduleNavigationRoute extends _i27.PageRouteInfo { static const String name = 'ScheduleNavigationRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i20.loadLibrary, - () => _i20.ScheduleNavigationPage(), + return _i28.DeferredWidget( + _i21.loadLibrary, + () => _i21.ScheduleNavigationPage(), ); }, ); } /// generated route for -/// [_i21.SchedulePage] -class ScheduleRoute extends _i27.PageRouteInfo { - const ScheduleRoute({List<_i27.PageRouteInfo>? children}) +/// [_i22.SchedulePage] +class ScheduleRoute extends _i28.PageRouteInfo { + const ScheduleRoute({List<_i28.PageRouteInfo>? children}) : super( ScheduleRoute.name, initialChildren: children, @@ -766,21 +820,21 @@ class ScheduleRoute extends _i27.PageRouteInfo { static const String name = 'ScheduleRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i21.loadLibrary, - () => _i21.SchedulePage(), + return _i28.DeferredWidget( + _i22.loadLibrary, + () => _i22.SchedulePage(), ); }, ); } /// generated route for -/// [_i22.SettingsPage] -class SettingsRoute extends _i27.PageRouteInfo { - const SettingsRoute({List<_i27.PageRouteInfo>? children}) +/// [_i23.SettingsPage] +class SettingsRoute extends _i28.PageRouteInfo { + const SettingsRoute({List<_i28.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -788,21 +842,21 @@ class SettingsRoute extends _i27.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i22.loadLibrary, - () => _i22.SettingsPage(), + return _i28.DeferredWidget( + _i23.loadLibrary, + () => _i23.SettingsPage(), ); }, ); } /// generated route for -/// [_i23.SignupPage] -class SignupRoute extends _i27.PageRouteInfo { - const SignupRoute({List<_i27.PageRouteInfo>? children}) +/// [_i24.SignupPage] +class SignupRoute extends _i28.PageRouteInfo { + const SignupRoute({List<_i28.PageRouteInfo>? children}) : super( SignupRoute.name, initialChildren: children, @@ -810,23 +864,23 @@ class SignupRoute extends _i27.PageRouteInfo { static const String name = 'SignupRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i23.loadLibrary, - () => _i23.SignupPage(), + return _i28.DeferredWidget( + _i24.loadLibrary, + () => _i24.SignupPage(), ); }, ); } /// generated route for -/// [_i24.SongbookPage] -class SongbookRoute extends _i27.PageRouteInfo { +/// [_i25.SongbookPage] +class SongbookRoute extends _i28.PageRouteInfo { SongbookRoute({ - _i28.Key? key, - List<_i27.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( SongbookRoute.name, args: SongbookRouteArgs(key: key), @@ -835,14 +889,14 @@ class SongbookRoute extends _i27.PageRouteInfo { static const String name = 'SongbookRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const SongbookRouteArgs()); - return _i27.DeferredWidget( - _i24.loadLibrary, - () => _i24.SongbookPage(key: args.key), + return _i28.DeferredWidget( + _i25.loadLibrary, + () => _i25.SongbookPage(key: args.key), ); }, ); @@ -851,7 +905,7 @@ class SongbookRoute extends _i27.PageRouteInfo { class SongbookRouteArgs { const SongbookRouteArgs({this.key}); - final _i28.Key? key; + final _i29.Key? key; @override String toString() { @@ -860,9 +914,9 @@ class SongbookRouteArgs { } /// generated route for -/// [_i25.TimetablePage] -class TimetableRoute extends _i27.PageRouteInfo { - const TimetableRoute({List<_i27.PageRouteInfo>? children}) +/// [_i26.TimetablePage] +class TimetableRoute extends _i28.PageRouteInfo { + const TimetableRoute({List<_i28.PageRouteInfo>? children}) : super( TimetableRoute.name, initialChildren: children, @@ -870,21 +924,21 @@ class TimetableRoute extends _i27.PageRouteInfo { static const String name = 'TimetableRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i25.loadLibrary, - () => _i25.TimetablePage(), + return _i28.DeferredWidget( + _i26.loadLibrary, + () => _i26.TimetablePage(), ); }, ); } /// generated route for -/// [_i26.UserPage] -class UserRoute extends _i27.PageRouteInfo { - const UserRoute({List<_i27.PageRouteInfo>? children}) +/// [_i27.UserPage] +class UserRoute extends _i28.PageRouteInfo { + const UserRoute({List<_i28.PageRouteInfo>? children}) : super( UserRoute.name, initialChildren: children, @@ -892,12 +946,12 @@ class UserRoute extends _i27.PageRouteInfo { static const String name = 'UserRoute'; - static _i27.PageInfo page = _i27.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i27.DeferredWidget( - _i26.loadLibrary, - () => _i26.UserPage(), + return _i28.DeferredWidget( + _i27.loadLibrary, + () => _i27.UserPage(), ); }, ); diff --git a/lib/dataModels/FormModel.dart b/lib/dataModels/FormModel.dart index 13d55d76..4a7bb7ee 100644 --- a/lib/dataModels/FormModel.dart +++ b/lib/dataModels/FormModel.dart @@ -15,6 +15,7 @@ class FormModel { String? secret; String? header; String? footer; + String? link; FormModel({ this.id, @@ -31,6 +32,7 @@ class FormModel { this.secret, this.header, this.footer, + this.link, }); factory FormModel.fromJson(Map json) { @@ -51,6 +53,7 @@ class FormModel { secret: json['secret'], header: json[Tb.forms.header], footer: json[Tb.forms.footer], + link: json[Tb.forms.link], ); } @@ -69,5 +72,22 @@ class FormModel { 'secret': secret, Tb.forms.header: header, Tb.forms.footer: footer, + Tb.forms.link: link, + }; + + Map toEditedJson() => { + Tb.forms.id: id, + Tb.forms.created_at: createdAt?.toIso8601String(), + Tb.forms.data: data, + Tb.forms.key: formKey, + Tb.forms.occasion: occasion, + Tb.forms.blueprint: blueprint, + Tb.forms.type: type, + Tb.forms.bank_account: bankAccount, + Tb.forms.deadline_duration_seconds: deadlineDuration, + Tb.forms.is_open: isOpen, + Tb.forms.header: header, + Tb.forms.footer: footer, + Tb.forms.link: link, }; } diff --git a/lib/dataModels/Tb.dart b/lib/dataModels/Tb.dart index 118b2d83..4875ae39 100644 --- a/lib/dataModels/Tb.dart +++ b/lib/dataModels/Tb.dart @@ -328,4 +328,5 @@ class FormsTb { String get is_open => "is_open"; String get header => "header"; String get footer => "footer"; + String get link => "link"; } \ No newline at end of file diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 6d2b16cd..d0323369 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -1,10 +1,12 @@ import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; import 'package:fstapp/dataModels/FormModel.dart'; +import 'package:fstapp/dataModels/Tb.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; import 'package:fstapp/dataModelsEshop/ProductModel.dart'; import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/services/Utilities.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -54,6 +56,31 @@ class DbEshop { return null; } + static Future getFormForEdit(String formKey) async { + var data = await _supabase + .from(Tb.forms.table) + .select() + .eq(Tb.forms.key, formKey) + .maybeSingle(); + if(data==null) + { + return null; + } + return FormModel.fromJson(data); + } + + static Future updateForm(FormModel form) async { + var upsertObj = form.toEditedJson(); + if(form.id!=null) { + await _supabase.from(Tb.forms.table).update(upsertObj).eq(Tb.forms.id, form.id!); + } + else { + upsertObj.remove(Tb.forms.id); + upsertObj.addAll({Tb.forms.occasion: RightsService.currentOccasion!}); + await _supabase.from(Tb.forms.table).insert(upsertObj); + } + } + static Future getBlueprint(String mySecret, String formKey, int blueprintId) async { final response = await _supabase.rpc( 'get_blueprint', diff --git a/lib/pages/FormEditPage.dart b/lib/pages/FormEditPage.dart new file mode 100644 index 00000000..c2838917 --- /dev/null +++ b/lib/pages/FormEditPage.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:fstapp/AppRouter.gr.dart'; +import 'package:fstapp/RouterService.dart'; +import 'package:fstapp/dataModels/FormModel.dart'; +import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/styles/StylesConfig.dart'; +import 'package:fstapp/themeConfig.dart'; +import 'package:fstapp/widgets/HtmlView.dart'; +import 'package:fstapp/pages/HtmlEditorPage.dart'; +import 'package:fstapp/services/DialogHelper.dart'; +import 'package:fstapp/services/ToastHelper.dart'; +import 'package:easy_localization/easy_localization.dart'; + +@RoutePage() +class FormEditPage extends StatefulWidget { + static const ROUTE = "formEdit"; + String? formKey; + + FormEditPage({super.key, @pathParam this.formKey}); + + @override + _FormEditPageState createState() => _FormEditPageState(); +} + +class _FormEditPageState extends State { + final _formKey = GlobalKey(); + FormModel? form; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (widget.formKey == null && context.routeData.hasPendingChildren) { + widget.formKey = context.routeData.pendingChildren[0].pathParams.getString("formKey"); + widget.formKey = "7f4e3892-a544-4385-b933-61117e9755c3"; + } + loadData(); + } + + Future loadData() async { + form = await DbEshop.getFormForEdit(widget.formKey!); + setState(() {}); + } + + Future saveChanges() async { + await DbEshop.updateForm(form!); + ToastHelper.Show(context, "${"Saved".tr()}: ${form?.formKey}"); + Navigator.of(context).pop(); + } + + Future deleteForm() async { + final confirmation = await DialogHelper.showConfirmationDialogAsync( + context, + "Confirm removal".tr(), + "Are you sure you want to delete this form?".tr(), + ); + if (confirmation) { + ToastHelper.Show(context, "${"Not supported yet".tr()}: ${form?.formKey}"); + // Uncomment when delete logic is ready + // await DbEshop.deleteForm(form!); + // ToastHelper.Show(context, "${"Deleted".tr()}: ${form?.formKey}"); + // Navigator.of(context).pop(); + } + } + + void cancelEdit() { + Navigator.of(context).pop(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Edit Form").tr(), + actions: [ + if (form != null) + IconButton( + icon: const Icon(Icons.delete), + tooltip: "Delete".tr(), + onPressed: deleteForm, + ), + ], + ), + body: form == null + ? const Center(child: CircularProgressIndicator()) + : Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: StylesConfig.appMaxWidth), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: FormBuilder( + key: _formKey, + child: ListView( + children: [ + if (form?.header != null) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + HtmlView(html: form!.header!, isSelectable: true), + const SizedBox(height: 8), + Center( + child: ElevatedButton( + onPressed: () async { + RouterService.navigatePageInfo( + context, + HtmlEditorRoute(content: {HtmlEditorPage.parContent: form!.header!}), + ).then((value) async { + if (value != null) { + setState(() { + form!.header = value as String; + }); + await DbEshop.updateForm(form!); + } + }); + }, + child: Text("Edit content").tr(), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + bottomNavigationBar: Container( + color: ThemeConfig.appBarColor(), + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: cancelEdit, + style: TextButton.styleFrom( + foregroundColor: Colors.white, + ), + child: Text("Storno").tr(), + ), + const SizedBox(width: 16), + ElevatedButton( + onPressed: saveChanges, + child: Text("Save").tr(), + ), + ], + ), + ), + ); + } +} + diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index f23e2a9a..95dbae24 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -5,6 +5,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:fstapp/RouterService.dart'; import 'package:fstapp/components/seatReservation/model/SeatModel.dart'; import 'package:fstapp/dataModels/FormFields.dart'; import 'package:fstapp/dataModels/FormModel.dart'; @@ -12,7 +13,9 @@ import 'package:fstapp/dataModels/FormOptionModel.dart'; import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/pages/AdministrationOccasion/OrderFinishScreen.dart'; +import 'package:fstapp/pages/FormEditPage.dart'; import 'package:fstapp/pages/OrderPreviewScreen.dart'; import 'package:fstapp/services/FormHelper.dart'; import 'package:fstapp/services/Utilities.dart'; @@ -315,6 +318,18 @@ class _FormPageState extends State { _buildPriceAndTicketInfo(), ], ), + floatingActionButton: Visibility( + visible: RightsService.isEditor(), + child: FloatingActionButton( + onPressed: () { + RouterService.navigate( + context, + "${FormEditPage.ROUTE}/${form!.formKey}") + .then((value) => loadData()); + }, + child: const Icon(Icons.edit), + ), + ), ); } From 3553ba807147a8034e83279692d05918cef0f971 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Sun, 8 Dec 2024 23:16:53 +0100 Subject: [PATCH 117/159] form edit address --- lib/AppRouter.dart | 6 ++---- lib/pages/FormEditPage.dart | 1 - lib/pages/FormPage.dart | 8 ++++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/AppRouter.dart b/lib/AppRouter.dart index 1a391006..89f648d2 100644 --- a/lib/AppRouter.dart +++ b/lib/AppRouter.dart @@ -47,9 +47,7 @@ class AppRouter extends RootStackRouter { AutoRoute(page: AdminDashboardRoute.page, path: sl(AdminDashboardPage.ROUTE)), AutoRoute(page: AdminDashboardRoute.page, path: sl(AdminDashboardPage.ROUTE)), AutoRoute(page: FormRoute.page, path: "/${FormPage.ROUTE}/:id"), - AutoRoute(page: FormEditRoute.page, path: "/${FormEditPage.ROUTE}", children: [ - AutoRoute(path: ':formKey', page: FormEditRoute.page,), - ]), + AutoRoute(page: FormEditRoute.page, path: "/${FormPage.ROUTE}/:formKey/edit"), AutoRoute(page: CheckRoute.page, path: "/:{$LINK}/${CheckPage.ROUTE}/:id"), AutoRoute(page: NewsFormRoute.page, path: "/:{$LINK}/${NewsFormPage.ROUTE}"), AutoRoute(page: HtmlEditorRoute.page, path: "/:{$LINK}/${HtmlEditorPage.ROUTE}"), @@ -65,7 +63,7 @@ class AppRouter extends RootStackRouter { AutoRoute(path: ':id', page: BlueprintEditorRoute.page,), ]), AutoRoute(page: OccasionHomeRoute.page, path: "/:{$LINK}", children: [ - AutoRoute(page: UserRoute.page, path: "${UserPage.ROUTE}"), + AutoRoute(page: UserRoute.page, path: UserPage.ROUTE), AutoRoute(page: ScheduleNavigationRoute.page, path: EventPage.ROUTE, children: [ AutoRoute(page: ScheduleRoute.page, path: "", initial: true), AutoRoute(page: EventRoute.page, path: ":id") diff --git a/lib/pages/FormEditPage.dart b/lib/pages/FormEditPage.dart index c2838917..989bdf7d 100644 --- a/lib/pages/FormEditPage.dart +++ b/lib/pages/FormEditPage.dart @@ -33,7 +33,6 @@ class _FormEditPageState extends State { super.didChangeDependencies(); if (widget.formKey == null && context.routeData.hasPendingChildren) { widget.formKey = context.routeData.pendingChildren[0].pathParams.getString("formKey"); - widget.formKey = "7f4e3892-a544-4385-b933-61117e9755c3"; } loadData(); } diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index 95dbae24..dc9cf476 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -32,7 +32,7 @@ import 'package:fstapp/widgets/SeatReservationWidget.dart'; class FormPage extends StatefulWidget { static const ROUTE = "form"; - String? id; + String? formKey; FormPage({super.key}); @override @@ -53,8 +53,8 @@ class _FormPageState extends State { @override Future didChangeDependencies() async { - if (widget.id == null && context.routeData.hasPendingChildren) { - widget.id = context.routeData.pendingChildren[0].pathParams.getString("id"); + if (widget.formKey == null && context.routeData.hasPendingChildren) { + widget.formKey = context.routeData.pendingChildren[0].pathParams.getString("formKey"); } await loadData(); @@ -324,7 +324,7 @@ class _FormPageState extends State { onPressed: () { RouterService.navigate( context, - "${FormEditPage.ROUTE}/${form!.formKey}") + "${FormPage.ROUTE}/${form!.formKey}/edit") .then((value) => loadData()); }, child: const Icon(Icons.edit), From 3c36a740840130cf3a9734d633589a76bb98cef0 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 02:56:13 +0100 Subject: [PATCH 118/159] form edit screen, refact --- lib/AppRouter.gr.dart | 448 +++++++++--------- lib/components/dataGrid/AdminPageHelper.dart | 88 ++++ .../AdministrationOccasion/AdminPage.dart | 74 +-- lib/pages/AdministrationOccasion/FormTab.dart | 134 ++++++ lib/pages/FormEditPage.dart | 146 +----- lib/pages/FormPage.dart | 2 - 6 files changed, 486 insertions(+), 406 deletions(-) create mode 100644 lib/components/dataGrid/AdminPageHelper.dart create mode 100644 lib/pages/AdministrationOccasion/FormTab.dart diff --git a/lib/AppRouter.gr.dart b/lib/AppRouter.gr.dart index fc03b865..0358a476 100644 --- a/lib/AppRouter.gr.dart +++ b/lib/AppRouter.gr.dart @@ -8,13 +8,15 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i28; -import 'package:flutter/foundation.dart' as _i30; -import 'package:flutter/material.dart' as _i29; -import 'package:fstapp/dataModels/PlaceModel.dart' as _i31; +import 'package:auto_route/auto_route.dart' as _i29; +import 'package:flutter/foundation.dart' as _i31; +import 'package:flutter/material.dart' as _i30; +import 'package:fstapp/dataModels/PlaceModel.dart' as _i32; import 'package:fstapp/pages/AdminDashboardPage.dart' deferred as _i1; import 'package:fstapp/pages/AdministrationOccasion/AdminPage.dart' deferred as _i2; +import 'package:fstapp/pages/AdministrationOccasion/FormTab.dart' + deferred as _i10; import 'package:fstapp/pages/BlueprintEditorPage.dart' deferred as _i3; import 'package:fstapp/pages/CheckPage.dart' deferred as _i4; import 'package:fstapp/pages/EventEditPage.dart' deferred as _i5; @@ -22,29 +24,29 @@ import 'package:fstapp/pages/EventPage.dart' deferred as _i6; import 'package:fstapp/pages/ForgotPasswordPage.dart' deferred as _i7; import 'package:fstapp/pages/FormEditPage.dart' deferred as _i8; import 'package:fstapp/pages/FormPage.dart' deferred as _i9; -import 'package:fstapp/pages/GamePage.dart' deferred as _i10; -import 'package:fstapp/pages/HtmlEditorPage.dart' deferred as _i11; -import 'package:fstapp/pages/InfoPage.dart' deferred as _i12; -import 'package:fstapp/pages/InstallPage.dart' deferred as _i13; -import 'package:fstapp/pages/LoginPage.dart' deferred as _i14; -import 'package:fstapp/pages/MapPage.dart' deferred as _i15; -import 'package:fstapp/pages/MySchedulePage.dart' deferred as _i16; -import 'package:fstapp/pages/NewsFormPage.dart' deferred as _i17; -import 'package:fstapp/pages/NewsPage.dart' deferred as _i18; -import 'package:fstapp/pages/OccasionHomePage.dart' deferred as _i19; -import 'package:fstapp/pages/ScheduleNavigationScreen.dart' deferred as _i21; -import 'package:fstapp/pages/SchedulePage.dart' deferred as _i22; -import 'package:fstapp/pages/SettingsPage.dart' deferred as _i23; -import 'package:fstapp/pages/SignupPage.dart' deferred as _i24; -import 'package:fstapp/pages/SignupPasswordPage.dart' deferred as _i20; -import 'package:fstapp/pages/SongPage.dart' deferred as _i25; -import 'package:fstapp/pages/TimetablePage.dart' deferred as _i26; -import 'package:fstapp/pages/UserPage.dart' deferred as _i27; +import 'package:fstapp/pages/GamePage.dart' deferred as _i11; +import 'package:fstapp/pages/HtmlEditorPage.dart' deferred as _i12; +import 'package:fstapp/pages/InfoPage.dart' deferred as _i13; +import 'package:fstapp/pages/InstallPage.dart' deferred as _i14; +import 'package:fstapp/pages/LoginPage.dart' deferred as _i15; +import 'package:fstapp/pages/MapPage.dart' deferred as _i16; +import 'package:fstapp/pages/MySchedulePage.dart' deferred as _i17; +import 'package:fstapp/pages/NewsFormPage.dart' deferred as _i18; +import 'package:fstapp/pages/NewsPage.dart' deferred as _i19; +import 'package:fstapp/pages/OccasionHomePage.dart' deferred as _i20; +import 'package:fstapp/pages/ScheduleNavigationScreen.dart' deferred as _i22; +import 'package:fstapp/pages/SchedulePage.dart' deferred as _i23; +import 'package:fstapp/pages/SettingsPage.dart' deferred as _i24; +import 'package:fstapp/pages/SignupPage.dart' deferred as _i25; +import 'package:fstapp/pages/SignupPasswordPage.dart' deferred as _i21; +import 'package:fstapp/pages/SongPage.dart' deferred as _i26; +import 'package:fstapp/pages/TimetablePage.dart' deferred as _i27; +import 'package:fstapp/pages/UserPage.dart' deferred as _i28; /// generated route for /// [_i1.AdminDashboardPage] -class AdminDashboardRoute extends _i28.PageRouteInfo { - const AdminDashboardRoute({List<_i28.PageRouteInfo>? children}) +class AdminDashboardRoute extends _i29.PageRouteInfo { + const AdminDashboardRoute({List<_i29.PageRouteInfo>? children}) : super( AdminDashboardRoute.name, initialChildren: children, @@ -52,10 +54,10 @@ class AdminDashboardRoute extends _i28.PageRouteInfo { static const String name = 'AdminDashboardRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( + return _i29.DeferredWidget( _i1.loadLibrary, () => _i1.AdminDashboardPage(), ); @@ -65,8 +67,8 @@ class AdminDashboardRoute extends _i28.PageRouteInfo { /// generated route for /// [_i2.AdminPage] -class AdminRoute extends _i28.PageRouteInfo { - const AdminRoute({List<_i28.PageRouteInfo>? children}) +class AdminRoute extends _i29.PageRouteInfo { + const AdminRoute({List<_i29.PageRouteInfo>? children}) : super( AdminRoute.name, initialChildren: children, @@ -74,10 +76,10 @@ class AdminRoute extends _i28.PageRouteInfo { static const String name = 'AdminRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( + return _i29.DeferredWidget( _i2.loadLibrary, () => _i2.AdminPage(), ); @@ -88,11 +90,11 @@ class AdminRoute extends _i28.PageRouteInfo { /// generated route for /// [_i3.BlueprintEditorPage] class BlueprintEditorRoute - extends _i28.PageRouteInfo { + extends _i29.PageRouteInfo { BlueprintEditorRoute({ - _i29.Key? key, + _i30.Key? key, int? id, - List<_i28.PageRouteInfo>? children, + List<_i29.PageRouteInfo>? children, }) : super( BlueprintEditorRoute.name, args: BlueprintEditorRouteArgs( @@ -105,13 +107,13 @@ class BlueprintEditorRoute static const String name = 'BlueprintEditorRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => BlueprintEditorRouteArgs(id: pathParams.optInt('id'))); - return _i28.DeferredWidget( + return _i29.DeferredWidget( _i3.loadLibrary, () => _i3.BlueprintEditorPage( key: args.key, @@ -128,7 +130,7 @@ class BlueprintEditorRouteArgs { this.id, }); - final _i29.Key? key; + final _i30.Key? key; final int? id; @@ -140,11 +142,11 @@ class BlueprintEditorRouteArgs { /// generated route for /// [_i4.CheckPage] -class CheckRoute extends _i28.PageRouteInfo { +class CheckRoute extends _i29.PageRouteInfo { CheckRoute({ required int id, - _i29.Key? key, - List<_i28.PageRouteInfo>? children, + _i30.Key? key, + List<_i29.PageRouteInfo>? children, }) : super( CheckRoute.name, args: CheckRouteArgs( @@ -157,13 +159,13 @@ class CheckRoute extends _i28.PageRouteInfo { static const String name = 'CheckRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => CheckRouteArgs(id: pathParams.getInt('id'))); - return _i28.DeferredWidget( + return _i29.DeferredWidget( _i4.loadLibrary, () => _i4.CheckPage( id: args.id, @@ -182,7 +184,7 @@ class CheckRouteArgs { final int id; - final _i29.Key? key; + final _i30.Key? key; @override String toString() { @@ -192,11 +194,11 @@ class CheckRouteArgs { /// generated route for /// [_i5.EventEditPage] -class EventEditRoute extends _i28.PageRouteInfo { +class EventEditRoute extends _i29.PageRouteInfo { EventEditRoute({ - _i29.Key? key, + _i30.Key? key, int? id, - List<_i28.PageRouteInfo>? children, + List<_i29.PageRouteInfo>? children, }) : super( EventEditRoute.name, args: EventEditRouteArgs( @@ -209,13 +211,13 @@ class EventEditRoute extends _i28.PageRouteInfo { static const String name = 'EventEditRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => EventEditRouteArgs(id: pathParams.optInt('id'))); - return _i28.DeferredWidget( + return _i29.DeferredWidget( _i5.loadLibrary, () => _i5.EventEditPage( key: args.key, @@ -232,7 +234,7 @@ class EventEditRouteArgs { this.id, }); - final _i29.Key? key; + final _i30.Key? key; final int? id; @@ -244,11 +246,11 @@ class EventEditRouteArgs { /// generated route for /// [_i6.EventPage] -class EventRoute extends _i28.PageRouteInfo { +class EventRoute extends _i29.PageRouteInfo { EventRoute({ int? id, - _i29.Key? key, - List<_i28.PageRouteInfo>? children, + _i30.Key? key, + List<_i29.PageRouteInfo>? children, }) : super( EventRoute.name, args: EventRouteArgs( @@ -261,13 +263,13 @@ class EventRoute extends _i28.PageRouteInfo { static const String name = 'EventRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => EventRouteArgs(id: pathParams.optInt('id'))); - return _i28.DeferredWidget( + return _i29.DeferredWidget( _i6.loadLibrary, () => _i6.EventPage( id: args.id, @@ -286,7 +288,7 @@ class EventRouteArgs { final int? id; - final _i29.Key? key; + final _i30.Key? key; @override String toString() { @@ -296,8 +298,8 @@ class EventRouteArgs { /// generated route for /// [_i7.ForgotPasswordPage] -class ForgotPasswordRoute extends _i28.PageRouteInfo { - const ForgotPasswordRoute({List<_i28.PageRouteInfo>? children}) +class ForgotPasswordRoute extends _i29.PageRouteInfo { + const ForgotPasswordRoute({List<_i29.PageRouteInfo>? children}) : super( ForgotPasswordRoute.name, initialChildren: children, @@ -305,10 +307,10 @@ class ForgotPasswordRoute extends _i28.PageRouteInfo { static const String name = 'ForgotPasswordRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( + return _i29.DeferredWidget( _i7.loadLibrary, () => _i7.ForgotPasswordPage(), ); @@ -318,11 +320,11 @@ class ForgotPasswordRoute extends _i28.PageRouteInfo { /// generated route for /// [_i8.FormEditPage] -class FormEditRoute extends _i28.PageRouteInfo { +class FormEditRoute extends _i29.PageRouteInfo { FormEditRoute({ - _i29.Key? key, + _i30.Key? key, String? formKey, - List<_i28.PageRouteInfo>? children, + List<_i29.PageRouteInfo>? children, }) : super( FormEditRoute.name, args: FormEditRouteArgs( @@ -335,14 +337,14 @@ class FormEditRoute extends _i28.PageRouteInfo { static const String name = 'FormEditRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => FormEditRouteArgs(formKey: pathParams.optString('formKey'))); - return _i28.DeferredWidget( + return _i29.DeferredWidget( _i8.loadLibrary, () => _i8.FormEditPage( key: args.key, @@ -359,7 +361,7 @@ class FormEditRouteArgs { this.formKey, }); - final _i29.Key? key; + final _i30.Key? key; final String? formKey; @@ -371,10 +373,10 @@ class FormEditRouteArgs { /// generated route for /// [_i9.FormPage] -class FormRoute extends _i28.PageRouteInfo { +class FormRoute extends _i29.PageRouteInfo { FormRoute({ - _i29.Key? key, - List<_i28.PageRouteInfo>? children, + _i30.Key? key, + List<_i29.PageRouteInfo>? children, }) : super( FormRoute.name, args: FormRouteArgs(key: key), @@ -383,12 +385,12 @@ class FormRoute extends _i28.PageRouteInfo { static const String name = 'FormRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const FormRouteArgs()); - return _i28.DeferredWidget( + return _i29.DeferredWidget( _i9.loadLibrary, () => _i9.FormPage(key: args.key), ); @@ -399,7 +401,7 @@ class FormRoute extends _i28.PageRouteInfo { class FormRouteArgs { const FormRouteArgs({this.key}); - final _i29.Key? key; + final _i30.Key? key; @override String toString() { @@ -408,11 +410,33 @@ class FormRouteArgs { } /// generated route for -/// [_i10.GamePage] -class GameRoute extends _i28.PageRouteInfo { +/// [_i10.FormTab] +class FormTab extends _i29.PageRouteInfo { + const FormTab({List<_i29.PageRouteInfo>? children}) + : super( + FormTab.name, + initialChildren: children, + ); + + static const String name = 'FormTab'; + + static _i29.PageInfo page = _i29.PageInfo( + name, + builder: (data) { + return _i29.DeferredWidget( + _i10.loadLibrary, + () => _i10.FormTab(), + ); + }, + ); +} + +/// generated route for +/// [_i11.GamePage] +class GameRoute extends _i29.PageRouteInfo { GameRoute({ - _i29.Key? key, - List<_i28.PageRouteInfo>? children, + _i30.Key? key, + List<_i29.PageRouteInfo>? children, }) : super( GameRoute.name, args: GameRouteArgs(key: key), @@ -421,14 +445,14 @@ class GameRoute extends _i28.PageRouteInfo { static const String name = 'GameRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const GameRouteArgs()); - return _i28.DeferredWidget( - _i10.loadLibrary, - () => _i10.GamePage(key: args.key), + return _i29.DeferredWidget( + _i11.loadLibrary, + () => _i11.GamePage(key: args.key), ); }, ); @@ -437,7 +461,7 @@ class GameRoute extends _i28.PageRouteInfo { class GameRouteArgs { const GameRouteArgs({this.key}); - final _i29.Key? key; + final _i30.Key? key; @override String toString() { @@ -446,12 +470,12 @@ class GameRouteArgs { } /// generated route for -/// [_i11.HtmlEditorPage] -class HtmlEditorRoute extends _i28.PageRouteInfo { +/// [_i12.HtmlEditorPage] +class HtmlEditorRoute extends _i29.PageRouteInfo { HtmlEditorRoute({ Map? content, - _i29.Key? key, - List<_i28.PageRouteInfo>? children, + _i30.Key? key, + List<_i29.PageRouteInfo>? children, }) : super( HtmlEditorRoute.name, args: HtmlEditorRouteArgs( @@ -463,14 +487,14 @@ class HtmlEditorRoute extends _i28.PageRouteInfo { static const String name = 'HtmlEditorRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const HtmlEditorRouteArgs()); - return _i28.DeferredWidget( - _i11.loadLibrary, - () => _i11.HtmlEditorPage( + return _i29.DeferredWidget( + _i12.loadLibrary, + () => _i12.HtmlEditorPage( content: args.content, key: args.key, ), @@ -487,7 +511,7 @@ class HtmlEditorRouteArgs { final Map? content; - final _i29.Key? key; + final _i30.Key? key; @override String toString() { @@ -496,12 +520,12 @@ class HtmlEditorRouteArgs { } /// generated route for -/// [_i12.InfoPage] -class InfoRoute extends _i28.PageRouteInfo { +/// [_i13.InfoPage] +class InfoRoute extends _i29.PageRouteInfo { InfoRoute({ int? id, - _i30.Key? key, - List<_i28.PageRouteInfo>? children, + _i31.Key? key, + List<_i29.PageRouteInfo>? children, }) : super( InfoRoute.name, args: InfoRouteArgs( @@ -514,15 +538,15 @@ class InfoRoute extends _i28.PageRouteInfo { static const String name = 'InfoRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => InfoRouteArgs(id: pathParams.optInt('id'))); - return _i28.DeferredWidget( - _i12.loadLibrary, - () => _i12.InfoPage( + return _i29.DeferredWidget( + _i13.loadLibrary, + () => _i13.InfoPage( id: args.id, key: args.key, ), @@ -539,7 +563,7 @@ class InfoRouteArgs { final int? id; - final _i30.Key? key; + final _i31.Key? key; @override String toString() { @@ -548,9 +572,9 @@ class InfoRouteArgs { } /// generated route for -/// [_i13.InstallPage] -class InstallRoute extends _i28.PageRouteInfo { - const InstallRoute({List<_i28.PageRouteInfo>? children}) +/// [_i14.InstallPage] +class InstallRoute extends _i29.PageRouteInfo { + const InstallRoute({List<_i29.PageRouteInfo>? children}) : super( InstallRoute.name, initialChildren: children, @@ -558,21 +582,21 @@ class InstallRoute extends _i28.PageRouteInfo { static const String name = 'InstallRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i13.loadLibrary, - () => _i13.InstallPage(), + return _i29.DeferredWidget( + _i14.loadLibrary, + () => _i14.InstallPage(), ); }, ); } /// generated route for -/// [_i14.LoginPage] -class LoginRoute extends _i28.PageRouteInfo { - const LoginRoute({List<_i28.PageRouteInfo>? children}) +/// [_i15.LoginPage] +class LoginRoute extends _i29.PageRouteInfo { + const LoginRoute({List<_i29.PageRouteInfo>? children}) : super( LoginRoute.name, initialChildren: children, @@ -580,25 +604,25 @@ class LoginRoute extends _i28.PageRouteInfo { static const String name = 'LoginRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i14.loadLibrary, - () => _i14.LoginPage(), + return _i29.DeferredWidget( + _i15.loadLibrary, + () => _i15.LoginPage(), ); }, ); } /// generated route for -/// [_i15.MapPage] -class MapRoute extends _i28.PageRouteInfo { +/// [_i16.MapPage] +class MapRoute extends _i29.PageRouteInfo { MapRoute({ int? id, - _i31.PlaceModel? place, - _i29.Key? key, - List<_i28.PageRouteInfo>? children, + _i32.PlaceModel? place, + _i30.Key? key, + List<_i29.PageRouteInfo>? children, }) : super( MapRoute.name, args: MapRouteArgs( @@ -612,15 +636,15 @@ class MapRoute extends _i28.PageRouteInfo { static const String name = 'MapRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => MapRouteArgs(id: pathParams.optInt('id'))); - return _i28.DeferredWidget( - _i15.loadLibrary, - () => _i15.MapPage( + return _i29.DeferredWidget( + _i16.loadLibrary, + () => _i16.MapPage( id: args.id, place: args.place, key: args.key, @@ -639,9 +663,9 @@ class MapRouteArgs { final int? id; - final _i31.PlaceModel? place; + final _i32.PlaceModel? place; - final _i29.Key? key; + final _i30.Key? key; @override String toString() { @@ -650,9 +674,9 @@ class MapRouteArgs { } /// generated route for -/// [_i16.MySchedulePage] -class MyScheduleRoute extends _i28.PageRouteInfo { - const MyScheduleRoute({List<_i28.PageRouteInfo>? children}) +/// [_i17.MySchedulePage] +class MyScheduleRoute extends _i29.PageRouteInfo { + const MyScheduleRoute({List<_i29.PageRouteInfo>? children}) : super( MyScheduleRoute.name, initialChildren: children, @@ -660,21 +684,21 @@ class MyScheduleRoute extends _i28.PageRouteInfo { static const String name = 'MyScheduleRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i16.loadLibrary, - () => _i16.MySchedulePage(), + return _i29.DeferredWidget( + _i17.loadLibrary, + () => _i17.MySchedulePage(), ); }, ); } /// generated route for -/// [_i17.NewsFormPage] -class NewsFormRoute extends _i28.PageRouteInfo { - const NewsFormRoute({List<_i28.PageRouteInfo>? children}) +/// [_i18.NewsFormPage] +class NewsFormRoute extends _i29.PageRouteInfo { + const NewsFormRoute({List<_i29.PageRouteInfo>? children}) : super( NewsFormRoute.name, initialChildren: children, @@ -682,24 +706,24 @@ class NewsFormRoute extends _i28.PageRouteInfo { static const String name = 'NewsFormRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i17.loadLibrary, - () => _i17.NewsFormPage(), + return _i29.DeferredWidget( + _i18.loadLibrary, + () => _i18.NewsFormPage(), ); }, ); } /// generated route for -/// [_i18.NewsPage] -class NewsRoute extends _i28.PageRouteInfo { +/// [_i19.NewsPage] +class NewsRoute extends _i29.PageRouteInfo { NewsRoute({ - _i29.Key? key, - _i29.VoidCallback? onSetAsRead, - List<_i28.PageRouteInfo>? children, + _i30.Key? key, + _i30.VoidCallback? onSetAsRead, + List<_i29.PageRouteInfo>? children, }) : super( NewsRoute.name, args: NewsRouteArgs( @@ -711,14 +735,14 @@ class NewsRoute extends _i28.PageRouteInfo { static const String name = 'NewsRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const NewsRouteArgs()); - return _i28.DeferredWidget( - _i18.loadLibrary, - () => _i18.NewsPage( + return _i29.DeferredWidget( + _i19.loadLibrary, + () => _i19.NewsPage( key: args.key, onSetAsRead: args.onSetAsRead, ), @@ -733,9 +757,9 @@ class NewsRouteArgs { this.onSetAsRead, }); - final _i29.Key? key; + final _i30.Key? key; - final _i29.VoidCallback? onSetAsRead; + final _i30.VoidCallback? onSetAsRead; @override String toString() { @@ -744,9 +768,9 @@ class NewsRouteArgs { } /// generated route for -/// [_i19.OccasionHomePage] -class OccasionHomeRoute extends _i28.PageRouteInfo { - const OccasionHomeRoute({List<_i28.PageRouteInfo>? children}) +/// [_i20.OccasionHomePage] +class OccasionHomeRoute extends _i29.PageRouteInfo { + const OccasionHomeRoute({List<_i29.PageRouteInfo>? children}) : super( OccasionHomeRoute.name, initialChildren: children, @@ -754,21 +778,21 @@ class OccasionHomeRoute extends _i28.PageRouteInfo { static const String name = 'OccasionHomeRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i19.loadLibrary, - () => _i19.OccasionHomePage(), + return _i29.DeferredWidget( + _i20.loadLibrary, + () => _i20.OccasionHomePage(), ); }, ); } /// generated route for -/// [_i20.ResetPasswordPage] -class ResetPasswordRoute extends _i28.PageRouteInfo { - const ResetPasswordRoute({List<_i28.PageRouteInfo>? children}) +/// [_i21.ResetPasswordPage] +class ResetPasswordRoute extends _i29.PageRouteInfo { + const ResetPasswordRoute({List<_i29.PageRouteInfo>? children}) : super( ResetPasswordRoute.name, initialChildren: children, @@ -776,21 +800,21 @@ class ResetPasswordRoute extends _i28.PageRouteInfo { static const String name = 'ResetPasswordRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i20.loadLibrary, - () => _i20.ResetPasswordPage(), + return _i29.DeferredWidget( + _i21.loadLibrary, + () => _i21.ResetPasswordPage(), ); }, ); } /// generated route for -/// [_i21.ScheduleNavigationPage] -class ScheduleNavigationRoute extends _i28.PageRouteInfo { - const ScheduleNavigationRoute({List<_i28.PageRouteInfo>? children}) +/// [_i22.ScheduleNavigationPage] +class ScheduleNavigationRoute extends _i29.PageRouteInfo { + const ScheduleNavigationRoute({List<_i29.PageRouteInfo>? children}) : super( ScheduleNavigationRoute.name, initialChildren: children, @@ -798,21 +822,21 @@ class ScheduleNavigationRoute extends _i28.PageRouteInfo { static const String name = 'ScheduleNavigationRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i21.loadLibrary, - () => _i21.ScheduleNavigationPage(), + return _i29.DeferredWidget( + _i22.loadLibrary, + () => _i22.ScheduleNavigationPage(), ); }, ); } /// generated route for -/// [_i22.SchedulePage] -class ScheduleRoute extends _i28.PageRouteInfo { - const ScheduleRoute({List<_i28.PageRouteInfo>? children}) +/// [_i23.SchedulePage] +class ScheduleRoute extends _i29.PageRouteInfo { + const ScheduleRoute({List<_i29.PageRouteInfo>? children}) : super( ScheduleRoute.name, initialChildren: children, @@ -820,21 +844,21 @@ class ScheduleRoute extends _i28.PageRouteInfo { static const String name = 'ScheduleRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i22.loadLibrary, - () => _i22.SchedulePage(), + return _i29.DeferredWidget( + _i23.loadLibrary, + () => _i23.SchedulePage(), ); }, ); } /// generated route for -/// [_i23.SettingsPage] -class SettingsRoute extends _i28.PageRouteInfo { - const SettingsRoute({List<_i28.PageRouteInfo>? children}) +/// [_i24.SettingsPage] +class SettingsRoute extends _i29.PageRouteInfo { + const SettingsRoute({List<_i29.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -842,21 +866,21 @@ class SettingsRoute extends _i28.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i23.loadLibrary, - () => _i23.SettingsPage(), + return _i29.DeferredWidget( + _i24.loadLibrary, + () => _i24.SettingsPage(), ); }, ); } /// generated route for -/// [_i24.SignupPage] -class SignupRoute extends _i28.PageRouteInfo { - const SignupRoute({List<_i28.PageRouteInfo>? children}) +/// [_i25.SignupPage] +class SignupRoute extends _i29.PageRouteInfo { + const SignupRoute({List<_i29.PageRouteInfo>? children}) : super( SignupRoute.name, initialChildren: children, @@ -864,23 +888,23 @@ class SignupRoute extends _i28.PageRouteInfo { static const String name = 'SignupRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i24.loadLibrary, - () => _i24.SignupPage(), + return _i29.DeferredWidget( + _i25.loadLibrary, + () => _i25.SignupPage(), ); }, ); } /// generated route for -/// [_i25.SongbookPage] -class SongbookRoute extends _i28.PageRouteInfo { +/// [_i26.SongbookPage] +class SongbookRoute extends _i29.PageRouteInfo { SongbookRoute({ - _i29.Key? key, - List<_i28.PageRouteInfo>? children, + _i30.Key? key, + List<_i29.PageRouteInfo>? children, }) : super( SongbookRoute.name, args: SongbookRouteArgs(key: key), @@ -889,14 +913,14 @@ class SongbookRoute extends _i28.PageRouteInfo { static const String name = 'SongbookRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const SongbookRouteArgs()); - return _i28.DeferredWidget( - _i25.loadLibrary, - () => _i25.SongbookPage(key: args.key), + return _i29.DeferredWidget( + _i26.loadLibrary, + () => _i26.SongbookPage(key: args.key), ); }, ); @@ -905,7 +929,7 @@ class SongbookRoute extends _i28.PageRouteInfo { class SongbookRouteArgs { const SongbookRouteArgs({this.key}); - final _i29.Key? key; + final _i30.Key? key; @override String toString() { @@ -914,9 +938,9 @@ class SongbookRouteArgs { } /// generated route for -/// [_i26.TimetablePage] -class TimetableRoute extends _i28.PageRouteInfo { - const TimetableRoute({List<_i28.PageRouteInfo>? children}) +/// [_i27.TimetablePage] +class TimetableRoute extends _i29.PageRouteInfo { + const TimetableRoute({List<_i29.PageRouteInfo>? children}) : super( TimetableRoute.name, initialChildren: children, @@ -924,21 +948,21 @@ class TimetableRoute extends _i28.PageRouteInfo { static const String name = 'TimetableRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i26.loadLibrary, - () => _i26.TimetablePage(), + return _i29.DeferredWidget( + _i27.loadLibrary, + () => _i27.TimetablePage(), ); }, ); } /// generated route for -/// [_i27.UserPage] -class UserRoute extends _i28.PageRouteInfo { - const UserRoute({List<_i28.PageRouteInfo>? children}) +/// [_i28.UserPage] +class UserRoute extends _i29.PageRouteInfo { + const UserRoute({List<_i29.PageRouteInfo>? children}) : super( UserRoute.name, initialChildren: children, @@ -946,12 +970,12 @@ class UserRoute extends _i28.PageRouteInfo { static const String name = 'UserRoute'; - static _i28.PageInfo page = _i28.PageInfo( + static _i29.PageInfo page = _i29.PageInfo( name, builder: (data) { - return _i28.DeferredWidget( - _i27.loadLibrary, - () => _i27.UserPage(), + return _i29.DeferredWidget( + _i28.loadLibrary, + () => _i28.UserPage(), ); }, ); diff --git a/lib/components/dataGrid/AdminPageHelper.dart b/lib/components/dataGrid/AdminPageHelper.dart new file mode 100644 index 00000000..b01ce2da --- /dev/null +++ b/lib/components/dataGrid/AdminPageHelper.dart @@ -0,0 +1,88 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:fstapp/RouterService.dart'; +import 'package:fstapp/pages/AdministrationOccasion/FormTab.dart'; +import 'package:fstapp/pages/AdministrationOccasion/GameTab.dart'; +import 'package:fstapp/pages/AdministrationOccasion/InformationTab.dart'; +import 'package:fstapp/pages/AdministrationOccasion/PlacesTab.dart'; +import 'package:fstapp/pages/AdministrationOccasion/ScheduleTab.dart'; +import 'package:fstapp/pages/AdministrationOccasion/ServiceTab.dart'; +import 'package:fstapp/pages/AdministrationOccasion/UserGroupsTab.dart'; +import 'package:fstapp/pages/AdministrationOccasion/UsersTab.dart'; + +class AdminPageHelper { + static PreferredSizeWidget buildAdminAppBar( + BuildContext context, + List activeTabs, + TabController tabController, + String title + ) { + return AppBar( + title: Text(title), + leading: BackButton( + onPressed: () => RouterService.goBack(context), + ), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(40), + child: Align( + alignment: Alignment.centerLeft, + child: TabBar( + controller: tabController, + isScrollable: true, + tabs: activeTabs.map((tab) { + return Row( + children: [ + Icon(tab.icon), + Padding( + padding: const EdgeInsets.all(12), + child: Text(tab.label), + ), + ], + ); + }).toList(), + ), + ), + ), + ); + } +} + +class AdminTabDefinition { + final String label; + final IconData icon; + final Widget widget; + + AdminTabDefinition({required this.label, required this.icon, required this.widget}); + + // Tab labels as static const strings + static const String info = "Info"; + static const String events = "Events"; + static const String places = "Places"; + static const String groups = "Groups"; + static const String service = "Service"; + static const String users = "Users"; + static const String game = "Game"; + + static const String form = "Form"; + static const String blueprint = "Blueprint"; + static const String tickets = "Tickets"; + static const String orders = "Orders"; + + + // Available tabs defined in a dictionary + static Map get availableTabs => { + info: AdminTabDefinition(label: "Info".tr(), icon: Icons.info, widget: InformationTab()), + events: AdminTabDefinition(label: "Schedule".tr(), icon: Icons.calendar_month, widget: ScheduleTab()), + places: AdminTabDefinition(label: "Places".tr(), icon: Icons.pin_drop, widget: PlacesTab()), + groups: AdminTabDefinition(label: "Groups".tr(), icon: Icons.groups, widget: UserGroupsTab()), + service: AdminTabDefinition(label: "Service".tr(), icon: Icons.food_bank, widget: ServiceTab()), + users: AdminTabDefinition(label: "Users".tr(), icon: Icons.people, widget: UsersTab()), + game: AdminTabDefinition(label: "Game".tr(), icon: Icons.gamepad, widget: GameTab()), + + form: AdminTabDefinition(label: "Form".tr(), icon: Icons.list, widget: FormTab()), + blueprint: AdminTabDefinition(label: "Blueprint".tr(), icon: Icons.grid_on, widget: GameTab()), + tickets: AdminTabDefinition(label: "Tickets".tr(), icon: Icons.local_activity, widget: GameTab()), + orders: AdminTabDefinition(label: "Orders".tr(), icon: Icons.shopping_cart, widget: GameTab()), + + }; +} diff --git a/lib/pages/AdministrationOccasion/AdminPage.dart b/lib/pages/AdministrationOccasion/AdminPage.dart index a3314541..f884eacd 100644 --- a/lib/pages/AdministrationOccasion/AdminPage.dart +++ b/lib/pages/AdministrationOccasion/AdminPage.dart @@ -1,19 +1,13 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:fstapp/RouterService.dart'; -import 'package:fstapp/pages/AdministrationOccasion/GameTab.dart'; -import 'package:fstapp/pages/AdministrationOccasion/InformationTab.dart'; -import 'package:fstapp/pages/AdministrationOccasion/PlacesTab.dart'; -import 'package:fstapp/pages/AdministrationOccasion/ScheduleTab.dart'; -import 'package:fstapp/pages/AdministrationOccasion/ServiceTab.dart'; -import 'package:fstapp/pages/AdministrationOccasion/UserGroupsTab.dart'; -import 'package:fstapp/pages/AdministrationOccasion/UsersTab.dart'; +import 'package:fstapp/components/dataGrid/AdminPageHelper.dart'; + @RoutePage() class AdminPage extends StatefulWidget { static const ROUTE = "admin"; - const AdminPage({Key? key}) : super(key: key); + const AdminPage({super.key}); @override _AdminPageState createState() => _AdminPageState(); @@ -30,7 +24,8 @@ class _AdminPageState extends State with SingleTickerProviderStateMix AdminTabDefinition.groups, AdminTabDefinition.game, AdminTabDefinition.service, - AdminTabDefinition.users + AdminTabDefinition.users, + AdminTabDefinition.form ]; @override @@ -46,33 +41,7 @@ class _AdminPageState extends State with SingleTickerProviderStateMix return DefaultTabController( length: _tabController.length, child: Scaffold( - appBar: AppBar( - title: const Text("Admin").tr(), - leading: BackButton( - onPressed: () => RouterService.popOrHome(context), - ), - bottom: PreferredSize( - preferredSize: const Size.fromHeight(40), - child: Align( - alignment: Alignment.centerLeft, - child: TabBar( - controller: _tabController, - isScrollable: true, - tabs: activeTabs.map((tab) { - return Row( - children: [ - Icon(tab.icon), - Padding( - padding: const EdgeInsets.all(12), - child: Text(tab.label), - ), - ], - ); - }).toList(), - ), - ), - ), - ), + appBar: AdminPageHelper.buildAdminAppBar(context, activeTabs, _tabController, "Admin".tr()), body: TabBarView( controller: _tabController, physics: const NeverScrollableScrollPhysics(), @@ -87,33 +56,4 @@ class _AdminPageState extends State with SingleTickerProviderStateMix _tabController.dispose(); super.dispose(); } -} - -// Definition for each tab -class AdminTabDefinition { - final String label; - final IconData icon; - final Widget widget; - - AdminTabDefinition({required this.label, required this.icon, required this.widget}); - - // Tab labels as static const strings - static const String info = "Info"; - static const String events = "Events"; - static const String places = "Places"; - static const String groups = "Groups"; - static const String service = "Service"; - static const String users = "Users"; - static const String game = "Game"; - - // Available tabs defined in a dictionary - static Map get availableTabs => { - info: AdminTabDefinition(label: "Info".tr(), icon: Icons.info, widget: InformationTab()), - events: AdminTabDefinition(label: "Schedule".tr(), icon: Icons.calendar_month, widget: ScheduleTab()), - places: AdminTabDefinition(label: "Places".tr(), icon: Icons.pin_drop, widget: PlacesTab()), - groups: AdminTabDefinition(label: "Groups".tr(), icon: Icons.groups, widget: UserGroupsTab()), - service: AdminTabDefinition(label: "Service".tr(), icon: Icons.food_bank, widget: ServiceTab()), - users: AdminTabDefinition(label: "Users".tr(), icon: Icons.people, widget: UsersTab()), - game: AdminTabDefinition(label: "Game".tr(), icon: Icons.gamepad, widget: GameTab()), - }; -} +} \ No newline at end of file diff --git a/lib/pages/AdministrationOccasion/FormTab.dart b/lib/pages/AdministrationOccasion/FormTab.dart new file mode 100644 index 00000000..16f76b8d --- /dev/null +++ b/lib/pages/AdministrationOccasion/FormTab.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:fstapp/AppRouter.gr.dart'; +import 'package:fstapp/RouterService.dart'; +import 'package:fstapp/dataModels/FormModel.dart'; +import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/styles/StylesConfig.dart'; +import 'package:fstapp/themeConfig.dart'; +import 'package:fstapp/widgets/HtmlView.dart'; +import 'package:fstapp/pages/HtmlEditorPage.dart'; +import 'package:fstapp/services/DialogHelper.dart'; +import 'package:fstapp/services/ToastHelper.dart'; +import 'package:easy_localization/easy_localization.dart'; + +@RoutePage() +class FormTab extends StatefulWidget { + const FormTab({super.key}); + + @override + _FormTabState createState() => _FormTabState(); +} + +class _FormTabState extends State { + FormModel? form; + String? formKey; + @override + void didChangeDependencies() { + super.didChangeDependencies(); + ToastHelper.Show(context, context.routeData.pathParams.getString("formKey")); + if (formKey == null && context.routeData.pathParams.isNotEmpty) { + formKey = context.routeData.pathParams.getString("formKey"); + } + loadData(); + } + + Future loadData() async { + form = await DbEshop.getFormForEdit(formKey!); + setState(() {}); + } + + Future saveChanges() async { + await DbEshop.updateForm(form!); + ToastHelper.Show(context, "${"Saved".tr()}: ${form?.formKey}"); + Navigator.of(context).pop(); + } + + Future deleteForm() async { + final confirmation = await DialogHelper.showConfirmationDialogAsync( + context, + "Confirm removal".tr(), + "Are you sure you want to delete this form?".tr(), + ); + if (confirmation) { + ToastHelper.Show(context, "${"Not supported yet".tr()}: ${form?.formKey}"); + // Uncomment when delete logic is ready + // await DbEshop.deleteForm(form!); + // ToastHelper.Show(context, "${"Deleted".tr()}: ${form?.formKey}"); + // Navigator.of(context).pop(); + } + } + + void cancelEdit() { + Navigator.of(context).pop(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: form == null + ? const Center(child: CircularProgressIndicator()) + : Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: StylesConfig.appMaxWidth), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: ListView( + children: [ + if (form?.header != null) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + HtmlView(html: form!.header!, isSelectable: true), + const SizedBox(height: 8), + Center( + child: ElevatedButton( + onPressed: () async { + RouterService.navigatePageInfo( + context, + HtmlEditorRoute(content: {HtmlEditorPage.parContent: form!.header!}), + ).then((value) async { + if (value != null) { + setState(() { + form!.header = value as String; + }); + await DbEshop.updateForm(form!); + } + }); + }, + child: Text("Edit content").tr(), + ), + ), + ], + ), + ], + ), + ), + ), + ), + bottomNavigationBar: Container( + color: ThemeConfig.appBarColor(), + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: cancelEdit, + style: TextButton.styleFrom( + foregroundColor: Colors.white, + ), + child: Text("Storno").tr(), + ), + const SizedBox(width: 16), + ElevatedButton( + onPressed: saveChanges, + child: Text("Save").tr(), + ), + ], + ), + ), + ); + } +} + diff --git a/lib/pages/FormEditPage.dart b/lib/pages/FormEditPage.dart index 989bdf7d..b845f7a0 100644 --- a/lib/pages/FormEditPage.dart +++ b/lib/pages/FormEditPage.dart @@ -1,16 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:auto_route/auto_route.dart'; -import 'package:fstapp/AppRouter.gr.dart'; -import 'package:fstapp/RouterService.dart'; -import 'package:fstapp/dataModels/FormModel.dart'; -import 'package:fstapp/dataServices/DbEshop.dart'; -import 'package:fstapp/styles/StylesConfig.dart'; -import 'package:fstapp/themeConfig.dart'; -import 'package:fstapp/widgets/HtmlView.dart'; -import 'package:fstapp/pages/HtmlEditorPage.dart'; -import 'package:fstapp/services/DialogHelper.dart'; -import 'package:fstapp/services/ToastHelper.dart'; +import 'package:fstapp/components/dataGrid/AdminPageHelper.dart'; import 'package:easy_localization/easy_localization.dart'; @RoutePage() @@ -24,126 +14,32 @@ class FormEditPage extends StatefulWidget { _FormEditPageState createState() => _FormEditPageState(); } -class _FormEditPageState extends State { - final _formKey = GlobalKey(); - FormModel? form; +class _FormEditPageState extends State with SingleTickerProviderStateMixin { + late TabController _tabController; - @override - void didChangeDependencies() { - super.didChangeDependencies(); - if (widget.formKey == null && context.routeData.hasPendingChildren) { - widget.formKey = context.routeData.pendingChildren[0].pathParams.getString("formKey"); - } - loadData(); - } - - Future loadData() async { - form = await DbEshop.getFormForEdit(widget.formKey!); - setState(() {}); - } + // List of active tabs by name + final List activeTabNames = [ + AdminTabDefinition.form + ]; - Future saveChanges() async { - await DbEshop.updateForm(form!); - ToastHelper.Show(context, "${"Saved".tr()}: ${form?.formKey}"); - Navigator.of(context).pop(); - } - - Future deleteForm() async { - final confirmation = await DialogHelper.showConfirmationDialogAsync( - context, - "Confirm removal".tr(), - "Are you sure you want to delete this form?".tr(), - ); - if (confirmation) { - ToastHelper.Show(context, "${"Not supported yet".tr()}: ${form?.formKey}"); - // Uncomment when delete logic is ready - // await DbEshop.deleteForm(form!); - // ToastHelper.Show(context, "${"Deleted".tr()}: ${form?.formKey}"); - // Navigator.of(context).pop(); - } - } - - void cancelEdit() { - Navigator.of(context).pop(); + @override + void initState() { + super.initState(); + _tabController = TabController(vsync: this, length: activeTabNames.length); } @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Edit Form").tr(), - actions: [ - if (form != null) - IconButton( - icon: const Icon(Icons.delete), - tooltip: "Delete".tr(), - onPressed: deleteForm, - ), - ], - ), - body: form == null - ? const Center(child: CircularProgressIndicator()) - : Align( - alignment: Alignment.topCenter, - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: StylesConfig.appMaxWidth), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: FormBuilder( - key: _formKey, - child: ListView( - children: [ - if (form?.header != null) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - HtmlView(html: form!.header!, isSelectable: true), - const SizedBox(height: 8), - Center( - child: ElevatedButton( - onPressed: () async { - RouterService.navigatePageInfo( - context, - HtmlEditorRoute(content: {HtmlEditorPage.parContent: form!.header!}), - ).then((value) async { - if (value != null) { - setState(() { - form!.header = value as String; - }); - await DbEshop.updateForm(form!); - } - }); - }, - child: Text("Edit content").tr(), - ), - ), - ], - ), - ], - ), - ), - ), - ), - ), - bottomNavigationBar: Container( - color: ThemeConfig.appBarColor(), - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: cancelEdit, - style: TextButton.styleFrom( - foregroundColor: Colors.white, - ), - child: Text("Storno").tr(), - ), - const SizedBox(width: 16), - ElevatedButton( - onPressed: saveChanges, - child: Text("Save").tr(), - ), - ], + final activeTabs = activeTabNames.map((name) => AdminTabDefinition.availableTabs[name]!).toList(); + + return DefaultTabController( + length: _tabController.length, + child: Scaffold( + appBar: AdminPageHelper.buildAdminAppBar(context, activeTabs, _tabController, "Admin".tr()), + body: TabBarView( + controller: _tabController, + physics: const NeverScrollableScrollPhysics(), + children: activeTabs.map((tab) => tab.widget).toList(), ), ), ); diff --git a/lib/pages/FormPage.dart b/lib/pages/FormPage.dart index dc9cf476..52b9cd36 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/FormPage.dart @@ -150,8 +150,6 @@ class _FormPageState extends State { for (var ticketValue in ticketData.values) { if (ticketValue is FormOptionModel) { _totalPrice += ticketValue.price; - } else if (ticketValue is BlueprintObjectModel) { - //_totalPrice += ticketValue.product?.price ?? 0; } } } From 8b5f0a97926ef7289c3e734bd5630a2312d9f1d5 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:17:54 +0100 Subject: [PATCH 119/159] blueprint editor --- lib/AppRouter.dart | 5 +- lib/AppRouter.gr.dart | 554 ++++++++---------- lib/components/dataGrid/AdminPageHelper.dart | 7 +- lib/dataServices/DbEshop.dart | 4 +- .../BlueprintEditorTab.dart} | 29 +- .../{FormTab.dart => FormEditorTab.dart} | 9 +- lib/pages/FormEditPage.dart | 3 +- .../database/eshop/get_blueprint_editor.sql | 14 +- 8 files changed, 289 insertions(+), 336 deletions(-) rename lib/pages/{BlueprintEditorPage.dart => AdminDashboard/BlueprintEditorTab.dart} (96%) rename lib/pages/AdministrationOccasion/{FormTab.dart => FormEditorTab.dart} (94%) diff --git a/lib/AppRouter.dart b/lib/AppRouter.dart index 89f648d2..4881e249 100644 --- a/lib/AppRouter.dart +++ b/lib/AppRouter.dart @@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/pages/AdminDashboardPage.dart'; -import 'package:fstapp/pages/BlueprintEditorPage.dart'; +import 'package:fstapp/pages/AdminDashboard/BlueprintEditorTab.dart'; import 'package:fstapp/pages/CheckPage.dart'; import 'package:fstapp/pages/EventEditPage.dart'; import 'package:fstapp/pages/EventPage.dart'; @@ -59,9 +59,6 @@ class AppRouter extends RootStackRouter { AutoRoute(page: EventEditRoute.page, path: "/:{$LINK}/${EventEditPage.ROUTE}", children: [ AutoRoute(path: ':id', page: EventEditRoute.page,), ]), - AutoRoute(page: BlueprintEditorRoute.page, path: "/:{$LINK}/${BlueprintEditorPage.ROUTE}", children: [ - AutoRoute(path: ':id', page: BlueprintEditorRoute.page,), - ]), AutoRoute(page: OccasionHomeRoute.page, path: "/:{$LINK}", children: [ AutoRoute(page: UserRoute.page, path: UserPage.ROUTE), AutoRoute(page: ScheduleNavigationRoute.page, path: EventPage.ROUTE, children: [ diff --git a/lib/AppRouter.gr.dart b/lib/AppRouter.gr.dart index 0358a476..bf88c984 100644 --- a/lib/AppRouter.gr.dart +++ b/lib/AppRouter.gr.dart @@ -8,45 +8,44 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i29; -import 'package:flutter/foundation.dart' as _i31; -import 'package:flutter/material.dart' as _i30; -import 'package:fstapp/dataModels/PlaceModel.dart' as _i32; +import 'package:auto_route/auto_route.dart' as _i28; +import 'package:flutter/foundation.dart' as _i30; +import 'package:flutter/material.dart' as _i29; +import 'package:fstapp/dataModels/PlaceModel.dart' as _i31; import 'package:fstapp/pages/AdminDashboardPage.dart' deferred as _i1; import 'package:fstapp/pages/AdministrationOccasion/AdminPage.dart' deferred as _i2; -import 'package:fstapp/pages/AdministrationOccasion/FormTab.dart' - deferred as _i10; -import 'package:fstapp/pages/BlueprintEditorPage.dart' deferred as _i3; -import 'package:fstapp/pages/CheckPage.dart' deferred as _i4; -import 'package:fstapp/pages/EventEditPage.dart' deferred as _i5; -import 'package:fstapp/pages/EventPage.dart' deferred as _i6; -import 'package:fstapp/pages/ForgotPasswordPage.dart' deferred as _i7; -import 'package:fstapp/pages/FormEditPage.dart' deferred as _i8; +import 'package:fstapp/pages/AdministrationOccasion/FormEditorTab.dart' + deferred as _i8; +import 'package:fstapp/pages/CheckPage.dart' deferred as _i3; +import 'package:fstapp/pages/EventEditPage.dart' deferred as _i4; +import 'package:fstapp/pages/EventPage.dart' deferred as _i5; +import 'package:fstapp/pages/ForgotPasswordPage.dart' deferred as _i6; +import 'package:fstapp/pages/FormEditPage.dart' deferred as _i7; import 'package:fstapp/pages/FormPage.dart' deferred as _i9; -import 'package:fstapp/pages/GamePage.dart' deferred as _i11; -import 'package:fstapp/pages/HtmlEditorPage.dart' deferred as _i12; -import 'package:fstapp/pages/InfoPage.dart' deferred as _i13; -import 'package:fstapp/pages/InstallPage.dart' deferred as _i14; -import 'package:fstapp/pages/LoginPage.dart' deferred as _i15; -import 'package:fstapp/pages/MapPage.dart' deferred as _i16; -import 'package:fstapp/pages/MySchedulePage.dart' deferred as _i17; -import 'package:fstapp/pages/NewsFormPage.dart' deferred as _i18; -import 'package:fstapp/pages/NewsPage.dart' deferred as _i19; -import 'package:fstapp/pages/OccasionHomePage.dart' deferred as _i20; -import 'package:fstapp/pages/ScheduleNavigationScreen.dart' deferred as _i22; -import 'package:fstapp/pages/SchedulePage.dart' deferred as _i23; -import 'package:fstapp/pages/SettingsPage.dart' deferred as _i24; -import 'package:fstapp/pages/SignupPage.dart' deferred as _i25; -import 'package:fstapp/pages/SignupPasswordPage.dart' deferred as _i21; -import 'package:fstapp/pages/SongPage.dart' deferred as _i26; -import 'package:fstapp/pages/TimetablePage.dart' deferred as _i27; -import 'package:fstapp/pages/UserPage.dart' deferred as _i28; +import 'package:fstapp/pages/GamePage.dart' deferred as _i10; +import 'package:fstapp/pages/HtmlEditorPage.dart' deferred as _i11; +import 'package:fstapp/pages/InfoPage.dart' deferred as _i12; +import 'package:fstapp/pages/InstallPage.dart' deferred as _i13; +import 'package:fstapp/pages/LoginPage.dart' deferred as _i14; +import 'package:fstapp/pages/MapPage.dart' deferred as _i15; +import 'package:fstapp/pages/MySchedulePage.dart' deferred as _i16; +import 'package:fstapp/pages/NewsFormPage.dart' deferred as _i17; +import 'package:fstapp/pages/NewsPage.dart' deferred as _i18; +import 'package:fstapp/pages/OccasionHomePage.dart' deferred as _i19; +import 'package:fstapp/pages/ScheduleNavigationScreen.dart' deferred as _i21; +import 'package:fstapp/pages/SchedulePage.dart' deferred as _i22; +import 'package:fstapp/pages/SettingsPage.dart' deferred as _i23; +import 'package:fstapp/pages/SignupPage.dart' deferred as _i24; +import 'package:fstapp/pages/SignupPasswordPage.dart' deferred as _i20; +import 'package:fstapp/pages/SongPage.dart' deferred as _i25; +import 'package:fstapp/pages/TimetablePage.dart' deferred as _i26; +import 'package:fstapp/pages/UserPage.dart' deferred as _i27; /// generated route for /// [_i1.AdminDashboardPage] -class AdminDashboardRoute extends _i29.PageRouteInfo { - const AdminDashboardRoute({List<_i29.PageRouteInfo>? children}) +class AdminDashboardRoute extends _i28.PageRouteInfo { + const AdminDashboardRoute({List<_i28.PageRouteInfo>? children}) : super( AdminDashboardRoute.name, initialChildren: children, @@ -54,10 +53,10 @@ class AdminDashboardRoute extends _i29.PageRouteInfo { static const String name = 'AdminDashboardRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( + return _i28.DeferredWidget( _i1.loadLibrary, () => _i1.AdminDashboardPage(), ); @@ -67,8 +66,8 @@ class AdminDashboardRoute extends _i29.PageRouteInfo { /// generated route for /// [_i2.AdminPage] -class AdminRoute extends _i29.PageRouteInfo { - const AdminRoute({List<_i29.PageRouteInfo>? children}) +class AdminRoute extends _i28.PageRouteInfo { + const AdminRoute({List<_i28.PageRouteInfo>? children}) : super( AdminRoute.name, initialChildren: children, @@ -76,10 +75,10 @@ class AdminRoute extends _i29.PageRouteInfo { static const String name = 'AdminRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( + return _i28.DeferredWidget( _i2.loadLibrary, () => _i2.AdminPage(), ); @@ -88,65 +87,12 @@ class AdminRoute extends _i29.PageRouteInfo { } /// generated route for -/// [_i3.BlueprintEditorPage] -class BlueprintEditorRoute - extends _i29.PageRouteInfo { - BlueprintEditorRoute({ - _i30.Key? key, - int? id, - List<_i29.PageRouteInfo>? children, - }) : super( - BlueprintEditorRoute.name, - args: BlueprintEditorRouteArgs( - key: key, - id: id, - ), - rawPathParams: {'id': id}, - initialChildren: children, - ); - - static const String name = 'BlueprintEditorRoute'; - - static _i29.PageInfo page = _i29.PageInfo( - name, - builder: (data) { - final pathParams = data.inheritedPathParams; - final args = data.argsAs( - orElse: () => BlueprintEditorRouteArgs(id: pathParams.optInt('id'))); - return _i29.DeferredWidget( - _i3.loadLibrary, - () => _i3.BlueprintEditorPage( - key: args.key, - id: args.id, - ), - ); - }, - ); -} - -class BlueprintEditorRouteArgs { - const BlueprintEditorRouteArgs({ - this.key, - this.id, - }); - - final _i30.Key? key; - - final int? id; - - @override - String toString() { - return 'BlueprintEditorRouteArgs{key: $key, id: $id}'; - } -} - -/// generated route for -/// [_i4.CheckPage] -class CheckRoute extends _i29.PageRouteInfo { +/// [_i3.CheckPage] +class CheckRoute extends _i28.PageRouteInfo { CheckRoute({ required int id, - _i30.Key? key, - List<_i29.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( CheckRoute.name, args: CheckRouteArgs( @@ -159,15 +105,15 @@ class CheckRoute extends _i29.PageRouteInfo { static const String name = 'CheckRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => CheckRouteArgs(id: pathParams.getInt('id'))); - return _i29.DeferredWidget( - _i4.loadLibrary, - () => _i4.CheckPage( + return _i28.DeferredWidget( + _i3.loadLibrary, + () => _i3.CheckPage( id: args.id, key: args.key, ), @@ -184,7 +130,7 @@ class CheckRouteArgs { final int id; - final _i30.Key? key; + final _i29.Key? key; @override String toString() { @@ -193,12 +139,12 @@ class CheckRouteArgs { } /// generated route for -/// [_i5.EventEditPage] -class EventEditRoute extends _i29.PageRouteInfo { +/// [_i4.EventEditPage] +class EventEditRoute extends _i28.PageRouteInfo { EventEditRoute({ - _i30.Key? key, + _i29.Key? key, int? id, - List<_i29.PageRouteInfo>? children, + List<_i28.PageRouteInfo>? children, }) : super( EventEditRoute.name, args: EventEditRouteArgs( @@ -211,15 +157,15 @@ class EventEditRoute extends _i29.PageRouteInfo { static const String name = 'EventEditRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => EventEditRouteArgs(id: pathParams.optInt('id'))); - return _i29.DeferredWidget( - _i5.loadLibrary, - () => _i5.EventEditPage( + return _i28.DeferredWidget( + _i4.loadLibrary, + () => _i4.EventEditPage( key: args.key, id: args.id, ), @@ -234,7 +180,7 @@ class EventEditRouteArgs { this.id, }); - final _i30.Key? key; + final _i29.Key? key; final int? id; @@ -245,12 +191,12 @@ class EventEditRouteArgs { } /// generated route for -/// [_i6.EventPage] -class EventRoute extends _i29.PageRouteInfo { +/// [_i5.EventPage] +class EventRoute extends _i28.PageRouteInfo { EventRoute({ int? id, - _i30.Key? key, - List<_i29.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( EventRoute.name, args: EventRouteArgs( @@ -263,15 +209,15 @@ class EventRoute extends _i29.PageRouteInfo { static const String name = 'EventRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => EventRouteArgs(id: pathParams.optInt('id'))); - return _i29.DeferredWidget( - _i6.loadLibrary, - () => _i6.EventPage( + return _i28.DeferredWidget( + _i5.loadLibrary, + () => _i5.EventPage( id: args.id, key: args.key, ), @@ -288,7 +234,7 @@ class EventRouteArgs { final int? id; - final _i30.Key? key; + final _i29.Key? key; @override String toString() { @@ -297,9 +243,9 @@ class EventRouteArgs { } /// generated route for -/// [_i7.ForgotPasswordPage] -class ForgotPasswordRoute extends _i29.PageRouteInfo { - const ForgotPasswordRoute({List<_i29.PageRouteInfo>? children}) +/// [_i6.ForgotPasswordPage] +class ForgotPasswordRoute extends _i28.PageRouteInfo { + const ForgotPasswordRoute({List<_i28.PageRouteInfo>? children}) : super( ForgotPasswordRoute.name, initialChildren: children, @@ -307,24 +253,24 @@ class ForgotPasswordRoute extends _i29.PageRouteInfo { static const String name = 'ForgotPasswordRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i7.loadLibrary, - () => _i7.ForgotPasswordPage(), + return _i28.DeferredWidget( + _i6.loadLibrary, + () => _i6.ForgotPasswordPage(), ); }, ); } /// generated route for -/// [_i8.FormEditPage] -class FormEditRoute extends _i29.PageRouteInfo { +/// [_i7.FormEditPage] +class FormEditRoute extends _i28.PageRouteInfo { FormEditRoute({ - _i30.Key? key, + _i29.Key? key, String? formKey, - List<_i29.PageRouteInfo>? children, + List<_i28.PageRouteInfo>? children, }) : super( FormEditRoute.name, args: FormEditRouteArgs( @@ -337,16 +283,16 @@ class FormEditRoute extends _i29.PageRouteInfo { static const String name = 'FormEditRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => FormEditRouteArgs(formKey: pathParams.optString('formKey'))); - return _i29.DeferredWidget( - _i8.loadLibrary, - () => _i8.FormEditPage( + return _i28.DeferredWidget( + _i7.loadLibrary, + () => _i7.FormEditPage( key: args.key, formKey: args.formKey, ), @@ -361,7 +307,7 @@ class FormEditRouteArgs { this.formKey, }); - final _i30.Key? key; + final _i29.Key? key; final String? formKey; @@ -371,12 +317,34 @@ class FormEditRouteArgs { } } +/// generated route for +/// [_i8.FormEditorTab] +class FormEditorTab extends _i28.PageRouteInfo { + const FormEditorTab({List<_i28.PageRouteInfo>? children}) + : super( + FormEditorTab.name, + initialChildren: children, + ); + + static const String name = 'FormEditorTab'; + + static _i28.PageInfo page = _i28.PageInfo( + name, + builder: (data) { + return _i28.DeferredWidget( + _i8.loadLibrary, + () => _i8.FormEditorTab(), + ); + }, + ); +} + /// generated route for /// [_i9.FormPage] -class FormRoute extends _i29.PageRouteInfo { +class FormRoute extends _i28.PageRouteInfo { FormRoute({ - _i30.Key? key, - List<_i29.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( FormRoute.name, args: FormRouteArgs(key: key), @@ -385,12 +353,12 @@ class FormRoute extends _i29.PageRouteInfo { static const String name = 'FormRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const FormRouteArgs()); - return _i29.DeferredWidget( + return _i28.DeferredWidget( _i9.loadLibrary, () => _i9.FormPage(key: args.key), ); @@ -401,7 +369,7 @@ class FormRoute extends _i29.PageRouteInfo { class FormRouteArgs { const FormRouteArgs({this.key}); - final _i30.Key? key; + final _i29.Key? key; @override String toString() { @@ -410,33 +378,11 @@ class FormRouteArgs { } /// generated route for -/// [_i10.FormTab] -class FormTab extends _i29.PageRouteInfo { - const FormTab({List<_i29.PageRouteInfo>? children}) - : super( - FormTab.name, - initialChildren: children, - ); - - static const String name = 'FormTab'; - - static _i29.PageInfo page = _i29.PageInfo( - name, - builder: (data) { - return _i29.DeferredWidget( - _i10.loadLibrary, - () => _i10.FormTab(), - ); - }, - ); -} - -/// generated route for -/// [_i11.GamePage] -class GameRoute extends _i29.PageRouteInfo { +/// [_i10.GamePage] +class GameRoute extends _i28.PageRouteInfo { GameRoute({ - _i30.Key? key, - List<_i29.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( GameRoute.name, args: GameRouteArgs(key: key), @@ -445,14 +391,14 @@ class GameRoute extends _i29.PageRouteInfo { static const String name = 'GameRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const GameRouteArgs()); - return _i29.DeferredWidget( - _i11.loadLibrary, - () => _i11.GamePage(key: args.key), + return _i28.DeferredWidget( + _i10.loadLibrary, + () => _i10.GamePage(key: args.key), ); }, ); @@ -461,7 +407,7 @@ class GameRoute extends _i29.PageRouteInfo { class GameRouteArgs { const GameRouteArgs({this.key}); - final _i30.Key? key; + final _i29.Key? key; @override String toString() { @@ -470,12 +416,12 @@ class GameRouteArgs { } /// generated route for -/// [_i12.HtmlEditorPage] -class HtmlEditorRoute extends _i29.PageRouteInfo { +/// [_i11.HtmlEditorPage] +class HtmlEditorRoute extends _i28.PageRouteInfo { HtmlEditorRoute({ Map? content, - _i30.Key? key, - List<_i29.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( HtmlEditorRoute.name, args: HtmlEditorRouteArgs( @@ -487,14 +433,14 @@ class HtmlEditorRoute extends _i29.PageRouteInfo { static const String name = 'HtmlEditorRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const HtmlEditorRouteArgs()); - return _i29.DeferredWidget( - _i12.loadLibrary, - () => _i12.HtmlEditorPage( + return _i28.DeferredWidget( + _i11.loadLibrary, + () => _i11.HtmlEditorPage( content: args.content, key: args.key, ), @@ -511,7 +457,7 @@ class HtmlEditorRouteArgs { final Map? content; - final _i30.Key? key; + final _i29.Key? key; @override String toString() { @@ -520,12 +466,12 @@ class HtmlEditorRouteArgs { } /// generated route for -/// [_i13.InfoPage] -class InfoRoute extends _i29.PageRouteInfo { +/// [_i12.InfoPage] +class InfoRoute extends _i28.PageRouteInfo { InfoRoute({ int? id, - _i31.Key? key, - List<_i29.PageRouteInfo>? children, + _i30.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( InfoRoute.name, args: InfoRouteArgs( @@ -538,15 +484,15 @@ class InfoRoute extends _i29.PageRouteInfo { static const String name = 'InfoRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => InfoRouteArgs(id: pathParams.optInt('id'))); - return _i29.DeferredWidget( - _i13.loadLibrary, - () => _i13.InfoPage( + return _i28.DeferredWidget( + _i12.loadLibrary, + () => _i12.InfoPage( id: args.id, key: args.key, ), @@ -563,7 +509,7 @@ class InfoRouteArgs { final int? id; - final _i31.Key? key; + final _i30.Key? key; @override String toString() { @@ -572,9 +518,9 @@ class InfoRouteArgs { } /// generated route for -/// [_i14.InstallPage] -class InstallRoute extends _i29.PageRouteInfo { - const InstallRoute({List<_i29.PageRouteInfo>? children}) +/// [_i13.InstallPage] +class InstallRoute extends _i28.PageRouteInfo { + const InstallRoute({List<_i28.PageRouteInfo>? children}) : super( InstallRoute.name, initialChildren: children, @@ -582,21 +528,21 @@ class InstallRoute extends _i29.PageRouteInfo { static const String name = 'InstallRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i14.loadLibrary, - () => _i14.InstallPage(), + return _i28.DeferredWidget( + _i13.loadLibrary, + () => _i13.InstallPage(), ); }, ); } /// generated route for -/// [_i15.LoginPage] -class LoginRoute extends _i29.PageRouteInfo { - const LoginRoute({List<_i29.PageRouteInfo>? children}) +/// [_i14.LoginPage] +class LoginRoute extends _i28.PageRouteInfo { + const LoginRoute({List<_i28.PageRouteInfo>? children}) : super( LoginRoute.name, initialChildren: children, @@ -604,25 +550,25 @@ class LoginRoute extends _i29.PageRouteInfo { static const String name = 'LoginRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i15.loadLibrary, - () => _i15.LoginPage(), + return _i28.DeferredWidget( + _i14.loadLibrary, + () => _i14.LoginPage(), ); }, ); } /// generated route for -/// [_i16.MapPage] -class MapRoute extends _i29.PageRouteInfo { +/// [_i15.MapPage] +class MapRoute extends _i28.PageRouteInfo { MapRoute({ int? id, - _i32.PlaceModel? place, - _i30.Key? key, - List<_i29.PageRouteInfo>? children, + _i31.PlaceModel? place, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( MapRoute.name, args: MapRouteArgs( @@ -636,15 +582,15 @@ class MapRoute extends _i29.PageRouteInfo { static const String name = 'MapRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => MapRouteArgs(id: pathParams.optInt('id'))); - return _i29.DeferredWidget( - _i16.loadLibrary, - () => _i16.MapPage( + return _i28.DeferredWidget( + _i15.loadLibrary, + () => _i15.MapPage( id: args.id, place: args.place, key: args.key, @@ -663,9 +609,9 @@ class MapRouteArgs { final int? id; - final _i32.PlaceModel? place; + final _i31.PlaceModel? place; - final _i30.Key? key; + final _i29.Key? key; @override String toString() { @@ -674,9 +620,9 @@ class MapRouteArgs { } /// generated route for -/// [_i17.MySchedulePage] -class MyScheduleRoute extends _i29.PageRouteInfo { - const MyScheduleRoute({List<_i29.PageRouteInfo>? children}) +/// [_i16.MySchedulePage] +class MyScheduleRoute extends _i28.PageRouteInfo { + const MyScheduleRoute({List<_i28.PageRouteInfo>? children}) : super( MyScheduleRoute.name, initialChildren: children, @@ -684,21 +630,21 @@ class MyScheduleRoute extends _i29.PageRouteInfo { static const String name = 'MyScheduleRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i17.loadLibrary, - () => _i17.MySchedulePage(), + return _i28.DeferredWidget( + _i16.loadLibrary, + () => _i16.MySchedulePage(), ); }, ); } /// generated route for -/// [_i18.NewsFormPage] -class NewsFormRoute extends _i29.PageRouteInfo { - const NewsFormRoute({List<_i29.PageRouteInfo>? children}) +/// [_i17.NewsFormPage] +class NewsFormRoute extends _i28.PageRouteInfo { + const NewsFormRoute({List<_i28.PageRouteInfo>? children}) : super( NewsFormRoute.name, initialChildren: children, @@ -706,24 +652,24 @@ class NewsFormRoute extends _i29.PageRouteInfo { static const String name = 'NewsFormRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i18.loadLibrary, - () => _i18.NewsFormPage(), + return _i28.DeferredWidget( + _i17.loadLibrary, + () => _i17.NewsFormPage(), ); }, ); } /// generated route for -/// [_i19.NewsPage] -class NewsRoute extends _i29.PageRouteInfo { +/// [_i18.NewsPage] +class NewsRoute extends _i28.PageRouteInfo { NewsRoute({ - _i30.Key? key, - _i30.VoidCallback? onSetAsRead, - List<_i29.PageRouteInfo>? children, + _i29.Key? key, + _i29.VoidCallback? onSetAsRead, + List<_i28.PageRouteInfo>? children, }) : super( NewsRoute.name, args: NewsRouteArgs( @@ -735,14 +681,14 @@ class NewsRoute extends _i29.PageRouteInfo { static const String name = 'NewsRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const NewsRouteArgs()); - return _i29.DeferredWidget( - _i19.loadLibrary, - () => _i19.NewsPage( + return _i28.DeferredWidget( + _i18.loadLibrary, + () => _i18.NewsPage( key: args.key, onSetAsRead: args.onSetAsRead, ), @@ -757,9 +703,9 @@ class NewsRouteArgs { this.onSetAsRead, }); - final _i30.Key? key; + final _i29.Key? key; - final _i30.VoidCallback? onSetAsRead; + final _i29.VoidCallback? onSetAsRead; @override String toString() { @@ -768,9 +714,9 @@ class NewsRouteArgs { } /// generated route for -/// [_i20.OccasionHomePage] -class OccasionHomeRoute extends _i29.PageRouteInfo { - const OccasionHomeRoute({List<_i29.PageRouteInfo>? children}) +/// [_i19.OccasionHomePage] +class OccasionHomeRoute extends _i28.PageRouteInfo { + const OccasionHomeRoute({List<_i28.PageRouteInfo>? children}) : super( OccasionHomeRoute.name, initialChildren: children, @@ -778,21 +724,21 @@ class OccasionHomeRoute extends _i29.PageRouteInfo { static const String name = 'OccasionHomeRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i20.loadLibrary, - () => _i20.OccasionHomePage(), + return _i28.DeferredWidget( + _i19.loadLibrary, + () => _i19.OccasionHomePage(), ); }, ); } /// generated route for -/// [_i21.ResetPasswordPage] -class ResetPasswordRoute extends _i29.PageRouteInfo { - const ResetPasswordRoute({List<_i29.PageRouteInfo>? children}) +/// [_i20.ResetPasswordPage] +class ResetPasswordRoute extends _i28.PageRouteInfo { + const ResetPasswordRoute({List<_i28.PageRouteInfo>? children}) : super( ResetPasswordRoute.name, initialChildren: children, @@ -800,21 +746,21 @@ class ResetPasswordRoute extends _i29.PageRouteInfo { static const String name = 'ResetPasswordRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i21.loadLibrary, - () => _i21.ResetPasswordPage(), + return _i28.DeferredWidget( + _i20.loadLibrary, + () => _i20.ResetPasswordPage(), ); }, ); } /// generated route for -/// [_i22.ScheduleNavigationPage] -class ScheduleNavigationRoute extends _i29.PageRouteInfo { - const ScheduleNavigationRoute({List<_i29.PageRouteInfo>? children}) +/// [_i21.ScheduleNavigationPage] +class ScheduleNavigationRoute extends _i28.PageRouteInfo { + const ScheduleNavigationRoute({List<_i28.PageRouteInfo>? children}) : super( ScheduleNavigationRoute.name, initialChildren: children, @@ -822,21 +768,21 @@ class ScheduleNavigationRoute extends _i29.PageRouteInfo { static const String name = 'ScheduleNavigationRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i22.loadLibrary, - () => _i22.ScheduleNavigationPage(), + return _i28.DeferredWidget( + _i21.loadLibrary, + () => _i21.ScheduleNavigationPage(), ); }, ); } /// generated route for -/// [_i23.SchedulePage] -class ScheduleRoute extends _i29.PageRouteInfo { - const ScheduleRoute({List<_i29.PageRouteInfo>? children}) +/// [_i22.SchedulePage] +class ScheduleRoute extends _i28.PageRouteInfo { + const ScheduleRoute({List<_i28.PageRouteInfo>? children}) : super( ScheduleRoute.name, initialChildren: children, @@ -844,21 +790,21 @@ class ScheduleRoute extends _i29.PageRouteInfo { static const String name = 'ScheduleRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i23.loadLibrary, - () => _i23.SchedulePage(), + return _i28.DeferredWidget( + _i22.loadLibrary, + () => _i22.SchedulePage(), ); }, ); } /// generated route for -/// [_i24.SettingsPage] -class SettingsRoute extends _i29.PageRouteInfo { - const SettingsRoute({List<_i29.PageRouteInfo>? children}) +/// [_i23.SettingsPage] +class SettingsRoute extends _i28.PageRouteInfo { + const SettingsRoute({List<_i28.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -866,21 +812,21 @@ class SettingsRoute extends _i29.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i24.loadLibrary, - () => _i24.SettingsPage(), + return _i28.DeferredWidget( + _i23.loadLibrary, + () => _i23.SettingsPage(), ); }, ); } /// generated route for -/// [_i25.SignupPage] -class SignupRoute extends _i29.PageRouteInfo { - const SignupRoute({List<_i29.PageRouteInfo>? children}) +/// [_i24.SignupPage] +class SignupRoute extends _i28.PageRouteInfo { + const SignupRoute({List<_i28.PageRouteInfo>? children}) : super( SignupRoute.name, initialChildren: children, @@ -888,23 +834,23 @@ class SignupRoute extends _i29.PageRouteInfo { static const String name = 'SignupRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i25.loadLibrary, - () => _i25.SignupPage(), + return _i28.DeferredWidget( + _i24.loadLibrary, + () => _i24.SignupPage(), ); }, ); } /// generated route for -/// [_i26.SongbookPage] -class SongbookRoute extends _i29.PageRouteInfo { +/// [_i25.SongbookPage] +class SongbookRoute extends _i28.PageRouteInfo { SongbookRoute({ - _i30.Key? key, - List<_i29.PageRouteInfo>? children, + _i29.Key? key, + List<_i28.PageRouteInfo>? children, }) : super( SongbookRoute.name, args: SongbookRouteArgs(key: key), @@ -913,14 +859,14 @@ class SongbookRoute extends _i29.PageRouteInfo { static const String name = 'SongbookRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const SongbookRouteArgs()); - return _i29.DeferredWidget( - _i26.loadLibrary, - () => _i26.SongbookPage(key: args.key), + return _i28.DeferredWidget( + _i25.loadLibrary, + () => _i25.SongbookPage(key: args.key), ); }, ); @@ -929,7 +875,7 @@ class SongbookRoute extends _i29.PageRouteInfo { class SongbookRouteArgs { const SongbookRouteArgs({this.key}); - final _i30.Key? key; + final _i29.Key? key; @override String toString() { @@ -938,9 +884,9 @@ class SongbookRouteArgs { } /// generated route for -/// [_i27.TimetablePage] -class TimetableRoute extends _i29.PageRouteInfo { - const TimetableRoute({List<_i29.PageRouteInfo>? children}) +/// [_i26.TimetablePage] +class TimetableRoute extends _i28.PageRouteInfo { + const TimetableRoute({List<_i28.PageRouteInfo>? children}) : super( TimetableRoute.name, initialChildren: children, @@ -948,21 +894,21 @@ class TimetableRoute extends _i29.PageRouteInfo { static const String name = 'TimetableRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i27.loadLibrary, - () => _i27.TimetablePage(), + return _i28.DeferredWidget( + _i26.loadLibrary, + () => _i26.TimetablePage(), ); }, ); } /// generated route for -/// [_i28.UserPage] -class UserRoute extends _i29.PageRouteInfo { - const UserRoute({List<_i29.PageRouteInfo>? children}) +/// [_i27.UserPage] +class UserRoute extends _i28.PageRouteInfo { + const UserRoute({List<_i28.PageRouteInfo>? children}) : super( UserRoute.name, initialChildren: children, @@ -970,12 +916,12 @@ class UserRoute extends _i29.PageRouteInfo { static const String name = 'UserRoute'; - static _i29.PageInfo page = _i29.PageInfo( + static _i28.PageInfo page = _i28.PageInfo( name, builder: (data) { - return _i29.DeferredWidget( - _i28.loadLibrary, - () => _i28.UserPage(), + return _i28.DeferredWidget( + _i27.loadLibrary, + () => _i27.UserPage(), ); }, ); diff --git a/lib/components/dataGrid/AdminPageHelper.dart b/lib/components/dataGrid/AdminPageHelper.dart index b01ce2da..a5d89631 100644 --- a/lib/components/dataGrid/AdminPageHelper.dart +++ b/lib/components/dataGrid/AdminPageHelper.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fstapp/RouterService.dart'; -import 'package:fstapp/pages/AdministrationOccasion/FormTab.dart'; +import 'package:fstapp/pages/AdministrationOccasion/FormEditorTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/GameTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/InformationTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/PlacesTab.dart'; @@ -9,6 +9,7 @@ import 'package:fstapp/pages/AdministrationOccasion/ScheduleTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/ServiceTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/UserGroupsTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/UsersTab.dart'; +import 'package:fstapp/pages/AdminDashboard/BlueprintEditorTab.dart'; class AdminPageHelper { static PreferredSizeWidget buildAdminAppBar( @@ -79,8 +80,8 @@ class AdminTabDefinition { users: AdminTabDefinition(label: "Users".tr(), icon: Icons.people, widget: UsersTab()), game: AdminTabDefinition(label: "Game".tr(), icon: Icons.gamepad, widget: GameTab()), - form: AdminTabDefinition(label: "Form".tr(), icon: Icons.list, widget: FormTab()), - blueprint: AdminTabDefinition(label: "Blueprint".tr(), icon: Icons.grid_on, widget: GameTab()), + form: AdminTabDefinition(label: "Form".tr(), icon: Icons.list, widget: FormEditorTab()), + blueprint: AdminTabDefinition(label: "Blueprint".tr(), icon: Icons.grid_on, widget: BlueprintTab()), tickets: AdminTabDefinition(label: "Tickets".tr(), icon: Icons.local_activity, widget: GameTab()), orders: AdminTabDefinition(label: "Orders".tr(), icon: Icons.shopping_cart, widget: GameTab()), diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index d0323369..ebb69eab 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -100,12 +100,12 @@ class DbEshop { return b; } - static Future getBlueprintForEdit(int blueprintId) async { + static Future getBlueprintForEdit(String formKey) async { final response = await _supabase.rpc( 'get_blueprint_editor', params: { - 'blueprint_id': blueprintId, + 'form_key': formKey, }, ); diff --git a/lib/pages/BlueprintEditorPage.dart b/lib/pages/AdminDashboard/BlueprintEditorTab.dart similarity index 96% rename from lib/pages/BlueprintEditorPage.dart rename to lib/pages/AdminDashboard/BlueprintEditorTab.dart index e29f2a66..23a2fa7d 100644 --- a/lib/pages/BlueprintEditorPage.dart +++ b/lib/pages/AdminDashboard/BlueprintEditorTab.dart @@ -16,26 +16,23 @@ import 'package:fstapp/widgets/SeatReservationWidget.dart'; import 'package:collection/collection.dart'; -import '../components/seatReservation/model/SeatLayoutStateModel.dart'; -import '../components/seatReservation/model/SeatModel.dart'; -import '../components/seatReservation/utils/SeatState.dart'; -import '../components/seatReservation/widgets/SeatLayoutWidget.dart'; +import '../../components/seatReservation/model/SeatLayoutStateModel.dart'; +import '../../components/seatReservation/model/SeatModel.dart'; +import '../../components/seatReservation/utils/SeatState.dart'; +import '../../components/seatReservation/widgets/SeatLayoutWidget.dart'; -@RoutePage() -class BlueprintEditorPage extends StatefulWidget { - static const ROUTE = "blueprintEdit"; +class BlueprintTab extends StatefulWidget { - int? id; - - BlueprintEditorPage({super.key, @pathParam this.id}); + const BlueprintTab({super.key}); @override - State createState() => _BlueprintEditorPageState(); + State createState() => _BlueprintTabState(); } -class _BlueprintEditorPageState extends State { +class _BlueprintTabState extends State { BlueprintModel? blueprint; BlueprintGroupModel? currentGroup; + String? formKey; List allBoxes = []; @@ -47,9 +44,8 @@ class _BlueprintEditorPageState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - if (widget.id == null && context.routeData.hasPendingChildren) { - widget.id = - context.routeData.pendingChildren[0].pathParams.getInt("id"); + if (formKey == null && context.routeData.pathParams.isNotEmpty) { + formKey = context.routeData.pathParams.getString("formKey"); } loadData(); } @@ -634,7 +630,8 @@ class _BlueprintEditorPageState extends State { } Future loadData() async { - blueprint = await DbEshop.getBlueprintForEdit(widget.id!); + blueprint = await DbEshop.getBlueprintForEdit(formKey!); + setState(() {}); } } diff --git a/lib/pages/AdministrationOccasion/FormTab.dart b/lib/pages/AdministrationOccasion/FormEditorTab.dart similarity index 94% rename from lib/pages/AdministrationOccasion/FormTab.dart rename to lib/pages/AdministrationOccasion/FormEditorTab.dart index 16f76b8d..a8ec84c7 100644 --- a/lib/pages/AdministrationOccasion/FormTab.dart +++ b/lib/pages/AdministrationOccasion/FormEditorTab.dart @@ -13,20 +13,19 @@ import 'package:fstapp/services/ToastHelper.dart'; import 'package:easy_localization/easy_localization.dart'; @RoutePage() -class FormTab extends StatefulWidget { - const FormTab({super.key}); +class FormEditorTab extends StatefulWidget { + const FormEditorTab({super.key}); @override - _FormTabState createState() => _FormTabState(); + _FormEditorTabState createState() => _FormEditorTabState(); } -class _FormTabState extends State { +class _FormEditorTabState extends State { FormModel? form; String? formKey; @override void didChangeDependencies() { super.didChangeDependencies(); - ToastHelper.Show(context, context.routeData.pathParams.getString("formKey")); if (formKey == null && context.routeData.pathParams.isNotEmpty) { formKey = context.routeData.pathParams.getString("formKey"); } diff --git a/lib/pages/FormEditPage.dart b/lib/pages/FormEditPage.dart index b845f7a0..61b84d37 100644 --- a/lib/pages/FormEditPage.dart +++ b/lib/pages/FormEditPage.dart @@ -19,7 +19,8 @@ class _FormEditPageState extends State with SingleTickerProviderSt // List of active tabs by name final List activeTabNames = [ - AdminTabDefinition.form + AdminTabDefinition.form, + AdminTabDefinition.blueprint ]; @override diff --git a/scripts/database/eshop/get_blueprint_editor.sql b/scripts/database/eshop/get_blueprint_editor.sql index a9c1d6b1..1904a685 100644 --- a/scripts/database/eshop/get_blueprint_editor.sql +++ b/scripts/database/eshop/get_blueprint_editor.sql @@ -1,11 +1,12 @@ CREATE OR REPLACE FUNCTION get_blueprint_editor( - blueprint_id BIGINT + form_key UUID ) RETURNS JSONB LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE + blueprint_id BIGINT; blueprintData JSONB; spotsData JSONB; productsData JSONB; @@ -15,6 +16,17 @@ DECLARE valid_spots JSONB; occasion_id BIGINT; BEGIN + -- Resolve blueprint_id using form_key + SELECT blueprint + INTO blueprint_id + FROM public.forms + WHERE key = form_key; + + -- Check if form_key is valid + IF blueprint_id IS NULL THEN + RETURN jsonb_build_object('code', 404, 'message', 'Form key does not exist or is not linked to a blueprint'); + END IF; + -- Fetch occasion_id for the blueprint SELECT occasion INTO occasion_id From ad9dcb518f068fd5d4deb62d6684bfacb3b1317c Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:24:17 +0100 Subject: [PATCH 120/159] adjust --- lib/AppRouter.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/AppRouter.dart b/lib/AppRouter.dart index 4881e249..03183664 100644 --- a/lib/AppRouter.dart +++ b/lib/AppRouter.dart @@ -2,11 +2,9 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/pages/AdminDashboardPage.dart'; -import 'package:fstapp/pages/AdminDashboard/BlueprintEditorTab.dart'; import 'package:fstapp/pages/CheckPage.dart'; import 'package:fstapp/pages/EventEditPage.dart'; import 'package:fstapp/pages/EventPage.dart'; -import 'package:fstapp/pages/FormEditPage.dart'; import 'package:fstapp/pages/FormPage.dart'; import 'package:fstapp/pages/HtmlEditorPage.dart'; import 'package:fstapp/pages/InfoPage.dart'; @@ -18,7 +16,6 @@ import 'package:fstapp/pages/NewsPage.dart'; import 'package:fstapp/pages/MySchedulePage.dart'; import 'package:fstapp/pages/ResetPasswordPage.dart'; import 'package:fstapp/pages/ForgotPasswordPage.dart'; -import 'package:fstapp/pages/SchedulePage.dart'; import 'package:fstapp/pages/SettingsPage.dart'; import 'package:fstapp/pages/SignupPage.dart'; import 'package:fstapp/pages/SongPage.dart'; From 3d01f8a93a82c641383b6a13277fb8cc840ab5a8 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:26:28 +0100 Subject: [PATCH 121/159] fix empty weblink --- lib/widgets/HtmlView.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/HtmlView.dart b/lib/widgets/HtmlView.dart index af1075e0..a185eb58 100644 --- a/lib/widgets/HtmlView.dart +++ b/lib/widgets/HtmlView.dart @@ -16,7 +16,7 @@ class HtmlWithAppLinksWidget extends HtmlWidget { @override FutureOr Function(String p1)? get onTapUrl { return (String url) { - if (AppConfig.compatibleUrls().any((u) => url.startsWith(u)) || url.contains("localhost")) { + if (AppConfig.compatibleUrls().any((u) => url.isNotEmpty && (url.startsWith(u)) || url.contains("localhost"))) { var path = url.split('/#/').last; RouterService.navigate(context, path); return true; From 7cb8b9388c79c1f8fef8c37ccb43be71350d1467 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:36:34 +0100 Subject: [PATCH 122/159] adjust --- lib/pages/AdminDashboard/BlueprintEditorTab.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/AdminDashboard/BlueprintEditorTab.dart b/lib/pages/AdminDashboard/BlueprintEditorTab.dart index 23a2fa7d..8b9827df 100644 --- a/lib/pages/AdminDashboard/BlueprintEditorTab.dart +++ b/lib/pages/AdminDashboard/BlueprintEditorTab.dart @@ -54,6 +54,7 @@ class _BlueprintTabState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( + automaticallyImplyLeading: false, title: Text(blueprint?.title??"Edit".tr()), actions: [ IconButton( From 0b219e257ec7d8185c7a62965952121d0b0bb00c Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 04:30:32 +0100 Subject: [PATCH 123/159] base --- scripts/database/eshop/get_orders.sql | 123 ++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 scripts/database/eshop/get_orders.sql diff --git a/scripts/database/eshop/get_orders.sql b/scripts/database/eshop/get_orders.sql new file mode 100644 index 00000000..57101c9b --- /dev/null +++ b/scripts/database/eshop/get_orders.sql @@ -0,0 +1,123 @@ +CREATE OR REPLACE FUNCTION get_orders( + form_key UUID +) +RETURNS JSONB +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + blueprint_id BIGINT; + occasion_id BIGINT; + spotsData JSONB; + productsData JSONB; + ticketsData JSONB; + ordersData JSONB; + orderProductTicketsData JSONB; +BEGIN + -- Resolve blueprint_id using form_key + SELECT blueprint + INTO blueprint_id + FROM public.forms + WHERE key = form_key; + + -- Check if form_key is valid + IF blueprint_id IS NULL THEN + RETURN jsonb_build_object('code', 404, 'message', 'Form key does not exist or is not linked to a blueprint'); + END IF; + + -- Fetch occasion_id for the blueprint + SELECT occasion + INTO occasion_id + FROM eshop.blueprints + WHERE id = blueprint_id; + + -- Authorization check + IF (SELECT get_is_editor_on_occasion(occasion_id)) <> TRUE THEN + RETURN jsonb_build_object('code', 403, 'message', 'User is not authorized to access this occasion'); + END IF; + + -- Fetch spots where order_product_ticket is not NULL + SELECT jsonb_agg(jsonb_build_object( + 'id', s.id, + 'title', s.title, + 'product', s.product, + 'order_product_ticket', s.order_product_ticket, + 'state', CASE + WHEN s.order_product_ticket IS NOT NULL THEN 'ordered' + WHEN s.secret IS NOT NULL AND s.secret_expiration_time > now() THEN 'selected' + ELSE 'available' + END + )) + INTO spotsData + FROM eshop.spots s + WHERE s.occasion = occasion_id + AND s.order_product_ticket IS NOT NULL; + + -- Fetch all products linked to the occasion + SELECT jsonb_agg(jsonb_build_object( + 'id', p.id, + 'title', p.title, + 'price', p.price, + 'type', pt.type, + 'description', pt.description + )) + INTO productsData + FROM eshop.products p + JOIN eshop.product_types pt ON pt.id = p.product_type + WHERE pt.occasion = occasion_id; + + -- Fetch tickets linked to the occasion + SELECT jsonb_agg(jsonb_build_object( + 'id', t.id, + 'ticket_symbol', t.ticket_symbol, + 'state', t.state, + 'note', t.note + )) + INTO ticketsData + FROM eshop.tickets t + WHERE t.occasion = occasion_id; + + -- Fetch orders linked to the occasion + SELECT jsonb_agg(jsonb_build_object( + 'id', o.id, + 'created_at', o.created_at, + 'updated_at', o.updated_at, + 'price', o.price, + 'state', o.state, + 'currency_code', o.currency_code, + 'data', o.data + )) + INTO ordersData + FROM eshop.orders o + WHERE o.occasion = occasion_id; + + -- Extract order IDs from ordersData JSONB + WITH order_ids AS ( + SELECT (order_obj->>'id')::BIGINT AS id + FROM jsonb_array_elements(ordersData) order_obj + ) + -- Fetch relevant order-product-ticket data based on extracted order IDs + SELECT jsonb_agg(jsonb_build_object( + 'id', opt.id, + 'order', opt."order", + 'product', opt.product, + 'ticket', opt.ticket, + 'created_at', opt.created_at + )) + INTO orderProductTicketsData + FROM eshop.order_product_ticket opt + WHERE opt."order" IN (SELECT id FROM order_ids); + + -- Return combined data + RETURN jsonb_build_object( + 'code', 200, + 'data', jsonb_build_object( + 'spots', COALESCE(spotsData, '[]'::jsonb), + 'products', COALESCE(productsData, '[]'::jsonb), + 'tickets', COALESCE(ticketsData, '[]'::jsonb), + 'orders', COALESCE(ordersData, '[]'::jsonb), + 'order_product_ticket', COALESCE(orderProductTicketsData, '[]'::jsonb) + ) + ); +END; +$$; From d0cda3b34c587fd6268472cf18357369401bae61 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 05:00:39 +0100 Subject: [PATCH 124/159] payment ifno --- scripts/database/eshop/get_orders.sql | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/scripts/database/eshop/get_orders.sql b/scripts/database/eshop/get_orders.sql index 57101c9b..2b99f434 100644 --- a/scripts/database/eshop/get_orders.sql +++ b/scripts/database/eshop/get_orders.sql @@ -13,6 +13,7 @@ DECLARE ticketsData JSONB; ordersData JSONB; orderProductTicketsData JSONB; + paymentInfoData JSONB; BEGIN -- Resolve blueprint_id using form_key SELECT blueprint @@ -85,7 +86,8 @@ BEGIN 'price', o.price, 'state', o.state, 'currency_code', o.currency_code, - 'data', o.data + 'data', o.data, + 'payment_info', o.payment_info )) INTO ordersData FROM eshop.orders o @@ -108,6 +110,24 @@ BEGIN FROM eshop.order_product_ticket opt WHERE opt."order" IN (SELECT id FROM order_ids); + -- Fetch payment information linked to the orders + SELECT jsonb_agg(jsonb_build_object( + 'id', pi.id, + 'created_at', pi.created_at, + 'bank_account', pi.bank_account, + 'variable_symbol', pi.variable_symbol, + 'amount', pi.amount, + 'paid', pi.paid, + 'deadline', pi.deadline, + 'currency_code', pi.currency_code + )) + INTO paymentInfoData + FROM eshop.payment_info pi + WHERE pi.id IN ( + SELECT DISTINCT (order_obj->>'payment_info')::BIGINT + FROM jsonb_array_elements(ordersData) order_obj + ); + -- Return combined data RETURN jsonb_build_object( 'code', 200, @@ -116,7 +136,8 @@ BEGIN 'products', COALESCE(productsData, '[]'::jsonb), 'tickets', COALESCE(ticketsData, '[]'::jsonb), 'orders', COALESCE(ordersData, '[]'::jsonb), - 'order_product_ticket', COALESCE(orderProductTicketsData, '[]'::jsonb) + 'order_product_ticket', COALESCE(orderProductTicketsData, '[]'::jsonb), + 'payment_info', COALESCE(paymentInfoData, '[]'::jsonb) ) ); END; From e82778dd87c31cf19b320d009dbcd7a80e09c7dc Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:29:56 +0100 Subject: [PATCH 125/159] getting orders --- lib/dataModelsEshop/BlueprintHelper.dart | 125 +++++++++++++++++ lib/dataModelsEshop/BlueprintModel.dart | 129 ++---------------- lib/dataModelsEshop/OrderModel.dart | 26 +++- lib/dataModelsEshop/PaymentInfoModel.dart | 57 ++++++++ lib/dataModelsEshop/TbEshop.dart | 14 ++ lib/dataModelsEshop/TicketModel.dart | 21 ++- lib/dataServices/DbEshop.dart | 77 +++++++++++ .../AdministrationOccasion/AdminPage.dart | 1 - 8 files changed, 328 insertions(+), 122 deletions(-) create mode 100644 lib/dataModelsEshop/BlueprintHelper.dart create mode 100644 lib/dataModelsEshop/PaymentInfoModel.dart diff --git a/lib/dataModelsEshop/BlueprintHelper.dart b/lib/dataModelsEshop/BlueprintHelper.dart new file mode 100644 index 00000000..93585530 --- /dev/null +++ b/lib/dataModelsEshop/BlueprintHelper.dart @@ -0,0 +1,125 @@ +import 'package:fstapp/components/seatReservation/utils/SeatState.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; +import 'package:fstapp/dataModelsEshop/OrderModel.dart'; +import 'package:fstapp/dataModelsEshop/OrderProductTicketModel.dart'; +import 'package:fstapp/dataModelsEshop/PaymentInfoModel.dart'; +import 'package:fstapp/dataModelsEshop/ProductModel.dart'; +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/dataModelsEshop/TicketModel.dart'; +import 'package:collection/collection.dart'; + +class BlueprintHelper { + static List parseGroups(Map json) { + return json[TbEshop.blueprints.groups] != null + ? List.from( + json[TbEshop.blueprints.groups].map((g) => BlueprintGroupModel.fromJson(g))) + : []; + } + + static List? parseObjects(Map json) { + return json[TbEshop.blueprints.objects] != null + ? List.from( + json[TbEshop.blueprints.objects].map((obj) => BlueprintObjectModel.fromJson(obj))) + : null; + } + + static List? parseSpots(Map json) { + return json[BlueprintModel.metaSpots] != null + ? List.from( + json[BlueprintModel.metaSpots].map((spot) => BlueprintObjectModel.fromJson(spot))) + : null; + } + + static List? parseProducts(Map json) { + return json[TbEshop.products.table] != null + ? List.from( + json[TbEshop.products.table].map((p) => ProductModel.fromJson(p))) + : null; + } + + static List? parseTickets(Map json) { + return json[TbEshop.tickets.table] != null + ? List.from( + json[TbEshop.tickets.table].map((t) => TicketModel.fromJson(t))) + : null; + } + + static List? parseOrders(Map json) { + return json[TbEshop.orders.table] != null + ? List.from( + json[TbEshop.orders.table].map((o) => OrderModel.fromJson(o))) + : null; + } + + static List? parseOrderProductTickets(Map json) { + return json[TbEshop.order_product_ticket.table] != null + ? List.from( + json[TbEshop.order_product_ticket.table].map((o) => OrderProductTicketModel.fromJson(o))) + : null; + } + + static List? parsePaymentInfo(Map json) { + return json[TbEshop.payment_info.table] != null + ? List.from( + json[TbEshop.payment_info.table].map((g) => PaymentInfoModel.fromJson(g))) + : []; + } + + static List? enrichObjects( + List? rawObjects, + List? spots, + List? products, + ) { + if (rawObjects == null) return null; + + return rawObjects.map((object) { + if (object.type == BlueprintModel.metaSpotType) { + final matchingSpot = spots?.firstWhereOrNull((spot) => spot.id == object.id); + return BlueprintObjectModel( + id: object.id, + type: object.type, + spotProduct: matchingSpot?.spotProduct, + product: products?.firstWhereOrNull((p) => p.id == matchingSpot?.spotProduct), + orderProductTicket: matchingSpot?.orderProductTicket, + groupId: object.groupId, + title: matchingSpot?.title ?? object.title, + stateEnum: BlueprintObjectModel.States.entries + .firstWhereOrNull((entry) => entry.value == matchingSpot?.state) + ?.key ?? + SeatState.available, + x: object.x, + y: object.y, + ); + } else if (object.type == BlueprintModel.metaTableAreaType) { + return BlueprintObjectModel( + id: object.id, + type: object.type, + title: object.title, + stateEnum: SeatState.black, + x: object.x, + y: object.y, + ); + } + return null; // Skip unrecognized object types + }).whereType().toList(); // Filter out null values + } + + static void assignObjectsToGroups( + List? objects, + List? groups, + ) { + if (objects != null) { + for (var obj in objects) { + if (obj.groupId != null) { + final group = groups?.firstWhereOrNull((g) => g.id == obj.groupId); + obj.group = group; + if (group != null) { + group.objects.add(obj); + } + } + } + } + } +} diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index ed8b52d5..2d542912 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -1,5 +1,6 @@ import 'package:fstapp/components/seatReservation/utils/SeatState.dart'; import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintHelper.dart'; import 'package:fstapp/dataModelsEshop/OrderModel.dart'; import 'package:fstapp/dataModelsEshop/OrderProductTicketModel.dart'; import 'package:fstapp/dataModelsEshop/ProductModel.dart'; @@ -7,7 +8,6 @@ import 'package:fstapp/dataModelsEshop/TbEshop.dart'; import 'package:fstapp/dataModelsEshop/TicketModel.dart'; import 'BlueprintConfiguration.dart'; import 'BlueprintObjectModel.dart'; -import 'package:collection/collection.dart'; class BlueprintModel { static const String metaSpots = "spots"; @@ -32,17 +32,17 @@ class BlueprintModel { List? orderProductTickets; factory BlueprintModel.fromJson(Map json) { - final List groups = _parseGroups(json); - final List? rawObjects = _parseObjects(json); - final List? spots = _parseSpots(json); - final List? products = _parseProducts(json); - final List? tickets = _parseTickets(json); - final List? orders = _parseOrders(json); - final List? orderProductTickets = _parseOrderProductTickets(json); + final List groups = BlueprintHelper.parseGroups(json); + final List? rawObjects = BlueprintHelper.parseObjects(json); + final List? spots = BlueprintHelper.parseSpots(json); + final List? products = BlueprintHelper.parseProducts(json); + final List? tickets = BlueprintHelper.parseTickets(json); + final List? orders = BlueprintHelper.parseOrders(json); + final List? orderProductTickets = BlueprintHelper.parseOrderProductTickets(json); - final List? enrichedObjects = _enrichObjects(rawObjects, spots, products); + final List? enrichedObjects = BlueprintHelper.enrichObjects(rawObjects, spots, products); - _assignObjectsToGroups(enrichedObjects, groups); + BlueprintHelper.assignObjectsToGroups(enrichedObjects, groups); return BlueprintModel( id: json[TbEshop.blueprints.id], @@ -66,109 +66,6 @@ class BlueprintModel { ); } - // Helper Methods - static List _parseGroups(Map json) { - return json[TbEshop.blueprints.groups] != null - ? List.from( - json[TbEshop.blueprints.groups].map((g) => BlueprintGroupModel.fromJson(g))) - : []; - } - - static List? _parseObjects(Map json) { - return json[TbEshop.blueprints.objects] != null - ? List.from( - json[TbEshop.blueprints.objects].map((obj) => BlueprintObjectModel.fromJson(obj))) - : null; - } - - static List? _parseSpots(Map json) { - return json[BlueprintModel.metaSpots] != null - ? List.from( - json[BlueprintModel.metaSpots].map((spot) => BlueprintObjectModel.fromJson(spot))) - : null; - } - - static List? _parseProducts(Map json) { - return json[TbEshop.products.table] != null - ? List.from( - json[TbEshop.products.table].map((p) => ProductModel.fromJson(p))) - : null; - } - - static List? _parseTickets(Map json) { - return json[TbEshop.tickets.table] != null - ? List.from( - json[TbEshop.tickets.table].map((t) => TicketModel.fromJson(t))) - : null; - } - - static List? _parseOrders(Map json) { - return json[TbEshop.orders.table] != null - ? List.from( - json[TbEshop.orders.table].map((o) => OrderModel.fromJson(o))) - : null; - } - - static List? _parseOrderProductTickets(Map json) { - return json[TbEshop.order_product_ticket.table] != null - ? List.from( - json[TbEshop.order_product_ticket.table].map((o) => OrderProductTicketModel.fromJson(o))) - : null; - } - - static List? _enrichObjects( - List? rawObjects, - List? spots, - List? products) { - if (rawObjects == null) return null; - - return rawObjects.map((object) { - if (object.type == metaSpotType) { - final matchingSpot = spots?.firstWhereOrNull((spot) => spot.id == object.id); - return BlueprintObjectModel( - id: object.id, - type: object.type, - spotProduct: matchingSpot?.spotProduct, - product: products?.firstWhereOrNull((p) => p.id == matchingSpot?.spotProduct), - orderProductTicket: matchingSpot?.orderProductTicket, - groupId: object.groupId, - title: matchingSpot?.title ?? object.title, - stateEnum: BlueprintObjectModel.States.entries - .firstWhereOrNull((entry) => entry.value == matchingSpot?.state) - ?.key ?? - SeatState.available, - x: object.x, - y: object.y, - ); - } else if (object.type == metaTableAreaType) { - return BlueprintObjectModel( - id: object.id, - type: object.type, - title: object.title, - stateEnum: SeatState.black, - x: object.x, - y: object.y, - ); - } - return null; // Skip unrecognized object types - }).whereType().toList(); // Filter out null values - } - - static void _assignObjectsToGroups( - List? objects, List? groups) { - if (objects != null) { - for (var obj in objects) { - if (obj.groupId != null) { - final group = groups?.firstWhereOrNull((g) => g.id == obj.groupId); - obj.group = group; - if (group != null) { - group.objects.add(obj); - } - } - } - } - } - Map toJson() => { TbEshop.blueprints.id: id, TbEshop.blueprints.created_at: createdAt?.toIso8601String(), @@ -206,8 +103,8 @@ class BlueprintModel { this.orderProductTickets, }); - void assignAllSpotsWithBlueprint(){ - for(var o in objects??[]){ + void assignAllSpotsWithBlueprint() { + for (var o in objects ?? []) { o.blueprint = this; } } @@ -219,4 +116,4 @@ class BlueprintModel { } return id; } -} +} \ No newline at end of file diff --git a/lib/dataModelsEshop/OrderModel.dart b/lib/dataModelsEshop/OrderModel.dart index dfd0f3f3..c169229a 100644 --- a/lib/dataModelsEshop/OrderModel.dart +++ b/lib/dataModelsEshop/OrderModel.dart @@ -1,4 +1,8 @@ import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/dataModelsEshop/TicketModel.dart'; +import 'package:fstapp/dataModelsEshop/ProductModel.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; +import 'package:fstapp/dataModelsEshop/PaymentInfoModel.dart'; class OrderModel { int? id; @@ -12,6 +16,12 @@ class OrderModel { int? form; String? currencyCode; + // Relating tickets, spots, products, and payment info to the order + List? relatedTickets; + List? relatedSpots; + List? relatedProducts; + PaymentInfoModel? paymentInfoModel; + static const String pendingState = "pending"; static const String completedState = "completed"; static const String canceledState = "canceled"; @@ -19,9 +29,15 @@ class OrderModel { factory OrderModel.fromJson(Map json) { return OrderModel( id: json[TbEshop.orders.id], - createdAt: json[TbEshop.orders.created_at] != null ? DateTime.parse(json[TbEshop.orders.created_at]) : null, - updatedAt: json[TbEshop.orders.updated_at] != null ? DateTime.parse(json[TbEshop.orders.updated_at]) : null, - price: json[TbEshop.orders.price] != null ? double.tryParse(json[TbEshop.orders.price].toString()) : null, + createdAt: json[TbEshop.orders.created_at] != null + ? DateTime.parse(json[TbEshop.orders.created_at]) + : null, + updatedAt: json[TbEshop.orders.updated_at] != null + ? DateTime.parse(json[TbEshop.orders.updated_at]) + : null, + price: json[TbEshop.orders.price] != null + ? double.tryParse(json[TbEshop.orders.price].toString()) + : null, state: json[TbEshop.orders.state], data: json[TbEshop.orders.data], occasion: json[TbEshop.orders.occasion], @@ -57,5 +73,9 @@ class OrderModel { this.paymentInfo, this.form, this.currencyCode, + this.relatedTickets, + this.relatedSpots, + this.relatedProducts, + this.paymentInfoModel, }); } diff --git a/lib/dataModelsEshop/PaymentInfoModel.dart b/lib/dataModelsEshop/PaymentInfoModel.dart new file mode 100644 index 00000000..aec2f0d1 --- /dev/null +++ b/lib/dataModelsEshop/PaymentInfoModel.dart @@ -0,0 +1,57 @@ +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; + +class PaymentInfoModel { + int? id; + DateTime? createdAt; + int? bankAccount; + int? variableSymbol; + double? amount; + double? paid; + DateTime? deadline; + String? currencyCode; + + factory PaymentInfoModel.fromJson(Map json) { + return PaymentInfoModel( + id: json[TbEshop.payment_info.id], + createdAt: json[TbEshop.payment_info.created_at] != null + ? DateTime.parse(json[TbEshop.payment_info.created_at]) + : null, + bankAccount: json[TbEshop.payment_info.bank_account], + variableSymbol: json[TbEshop.payment_info.variable_symbol], + amount: json[TbEshop.payment_info.amount] != null + ? double.tryParse(json[TbEshop.payment_info.amount].toString()) + : null, + paid: json[TbEshop.payment_info.paid] != null + ? double.tryParse(json[TbEshop.payment_info.paid].toString()) + : null, + deadline: json[TbEshop.payment_info.deadline] != null + ? DateTime.parse(json[TbEshop.payment_info.deadline]) + : null, + currencyCode: json[TbEshop.payment_info.currency_code], + ); + } + + Map toJson() => { + TbEshop.payment_info.id: id, + TbEshop.payment_info.created_at: createdAt?.toIso8601String(), + TbEshop.payment_info.bank_account: bankAccount, + TbEshop.payment_info.variable_symbol: variableSymbol, + TbEshop.payment_info.amount: amount, + TbEshop.payment_info.paid: paid, + TbEshop.payment_info.deadline: deadline?.toIso8601String(), + TbEshop.payment_info.currency_code: currencyCode, + }; + + String toBasicString() => id != null ? "Payment Info #$id" : "Payment Info"; + + PaymentInfoModel({ + this.id, + this.createdAt, + this.bankAccount, + this.variableSymbol, + this.amount, + this.paid, + this.deadline, + this.currencyCode, + }); +} diff --git a/lib/dataModelsEshop/TbEshop.dart b/lib/dataModelsEshop/TbEshop.dart index c35cf3f1..18836cab 100644 --- a/lib/dataModelsEshop/TbEshop.dart +++ b/lib/dataModelsEshop/TbEshop.dart @@ -6,6 +6,7 @@ class TbEshop { static TicketsTb tickets = const TicketsTb(); static BlueprintTb blueprints = const BlueprintTb(); static SpotsTb spots = const SpotsTb(); + static PaymentInfoTb payment_info = const PaymentInfoTb(); } class ProductTypesTb { @@ -101,4 +102,17 @@ class SpotsTb { String get secret_expiration_time => "secret_expiration_time"; String get title => "title"; String get order_product_ticket => "order_product_ticket"; +} + +class PaymentInfoTb { + const PaymentInfoTb(); + String get table => "payment_info"; + String get id => "id"; + String get created_at => "created_at"; + String get bank_account => "bank_account"; + String get variable_symbol => "variable_symbol"; + String get amount => "amount"; + String get paid => "paid"; + String get deadline => "deadline"; + String get currency_code => "currency_code"; } \ No newline at end of file diff --git a/lib/dataModelsEshop/TicketModel.dart b/lib/dataModelsEshop/TicketModel.dart index ba2bea39..6763e955 100644 --- a/lib/dataModelsEshop/TicketModel.dart +++ b/lib/dataModelsEshop/TicketModel.dart @@ -1,4 +1,7 @@ import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'BlueprintObjectModel.dart'; +import 'ProductModel.dart'; +import 'OrderModel.dart'; class TicketModel { int? id; @@ -9,6 +12,13 @@ class TicketModel { int? occasion; String? note; + // Relating spots and products directly to the ticket + List? relatedSpots; + List? relatedProducts; + + // Relating order directly to the ticket + OrderModel? relatedOrder; + static const String orderedState = "ordered"; static const String activeState = "active"; static const String usedState = "used"; @@ -17,8 +27,12 @@ class TicketModel { factory TicketModel.fromJson(Map json) { return TicketModel( id: json[TbEshop.tickets.id], - createdAt: json[TbEshop.tickets.created_at] != null ? DateTime.parse(json[TbEshop.tickets.created_at]) : null, - updatedAt: json[TbEshop.tickets.updated_at] != null ? DateTime.parse(json[TbEshop.tickets.updated_at]) : null, + createdAt: json[TbEshop.tickets.created_at] != null + ? DateTime.parse(json[TbEshop.tickets.created_at]) + : null, + updatedAt: json[TbEshop.tickets.updated_at] != null + ? DateTime.parse(json[TbEshop.tickets.updated_at]) + : null, ticketSymbol: json[TbEshop.tickets.ticket_symbol], state: json[TbEshop.tickets.state], occasion: json[TbEshop.tickets.occasion], @@ -46,5 +60,8 @@ class TicketModel { this.state, this.occasion, this.note, + this.relatedSpots, + this.relatedProducts, + this.relatedOrder, }); } diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index ebb69eab..5d9f8b37 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -2,10 +2,17 @@ import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataModels/Tb.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintHelper.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; +import 'package:fstapp/dataModelsEshop/OrderModel.dart'; +import 'package:fstapp/dataModelsEshop/OrderProductTicketModel.dart'; +import 'package:fstapp/dataModelsEshop/PaymentInfoModel.dart'; import 'package:fstapp/dataModelsEshop/ProductModel.dart'; import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/dataModelsEshop/TicketModel.dart'; import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/services/Utilities.dart'; @@ -155,4 +162,74 @@ class DbEshop { } return true; } + + static Future?> getOrders(String formKey) async { + + final response = await _supabase.rpc( + 'get_orders', + params: { + 'form_key': formKey, + }, + ); + + if (response["code"] != 200) { + return null; + } + var json = response["data"]; + + final List? spots = BlueprintHelper.parseSpots(json); + final List? products = BlueprintHelper.parseProducts(json); + final List? tickets = BlueprintHelper.parseTickets(json); + final List? orders = BlueprintHelper.parseOrders(json); + final List? payments = BlueprintHelper.parsePaymentInfo(json); + final List? orderProductTickets = BlueprintHelper.parseOrderProductTickets(json); + + for (var order in orders!) { + // Attach tickets related to the order via orderProductTickets + final List relatedTickets = tickets!.where((ticket) { + return orderProductTickets!.any((opt) => opt.orderId == order.id && opt.ticketId == ticket.id); + }).toList(); + + for (var ticket in tickets!) { + // Relate spots to the ticket via orderProductTickets + ticket.relatedSpots = spots!.where((spot) { + return orderProductTickets!.any((opt) => opt.ticketId == ticket.id && opt.id == spot.orderProductTicket); + }).toList(); + + // Relate products to the ticket via orderProductTickets + ticket.relatedProducts = products!.where((product) { + return orderProductTickets!.any((opt) => opt.ticketId == ticket.id && opt.productId == product.id); + }).toList(); + + // Relate order to the ticket via orderProductTickets + ticket.relatedOrder = orders!.firstWhereOrNull((order) { + return orderProductTickets!.any((opt) => opt.ticketId == ticket.id && opt.orderId == order.id); + }); + } + + + // Attach spots related to the tickets + final List relatedSpots = spots!.where((spot) { + return relatedTickets.any((ticket) => ticket.id == spot.orderProductTicket); + }).toList(); + + // Attach products related to the tickets + final List relatedProducts = products!.where((product) { + return relatedTickets.any((ticket) => ticket.id == product.id); + }).toList(); + + // Attach payment info + final PaymentInfoModel? relatedPaymentInfo = + payments!.firstWhereOrNull((payment) => payment.id == order.paymentInfo); + + // Update the order object + order.relatedTickets = relatedTickets; + order.relatedSpots = relatedSpots; + order.relatedProducts = relatedProducts; + order.paymentInfoModel = relatedPaymentInfo; + } + + return orders; + } + } diff --git a/lib/pages/AdministrationOccasion/AdminPage.dart b/lib/pages/AdministrationOccasion/AdminPage.dart index f884eacd..e26f763c 100644 --- a/lib/pages/AdministrationOccasion/AdminPage.dart +++ b/lib/pages/AdministrationOccasion/AdminPage.dart @@ -25,7 +25,6 @@ class _AdminPageState extends State with SingleTickerProviderStateMix AdminTabDefinition.game, AdminTabDefinition.service, AdminTabDefinition.users, - AdminTabDefinition.form ]; @override From b9b19e86ae3b8091ce1f933f14de6f06875d7fe3 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:04:47 +0100 Subject: [PATCH 126/159] UserColumns --- .../AdministrationOccasion/ColumnHelper.dart | 2 +- .../AdministrationOccasion/ServiceTab.dart | 26 ++++++++-------- .../AdministrationOccasion/UsersTab.dart | 30 +++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/pages/AdministrationOccasion/ColumnHelper.dart b/lib/pages/AdministrationOccasion/ColumnHelper.dart index cf813ebf..44d47ede 100644 --- a/lib/pages/AdministrationOccasion/ColumnHelper.dart +++ b/lib/pages/AdministrationOccasion/ColumnHelper.dart @@ -8,7 +8,7 @@ import 'package:fstapp/dataServices/DbOccasions.dart'; import 'package:fstapp/dataServices/RightsService.dart'; import 'package:pluto_grid_plus/pluto_grid_plus.dart'; -class ColumnHelper { +class UserColumns { // Column identifier constants static const String ID = "id"; static const String EMAIL = "email"; diff --git a/lib/pages/AdministrationOccasion/ServiceTab.dart b/lib/pages/AdministrationOccasion/ServiceTab.dart index 625d29fc..4454ee8e 100644 --- a/lib/pages/AdministrationOccasion/ServiceTab.dart +++ b/lib/pages/AdministrationOccasion/ServiceTab.dart @@ -21,16 +21,16 @@ class ServiceTab extends StatefulWidget { class _ServiceTabState extends State { static const List columnIdentifiers = [ - ColumnHelper.ID, - ColumnHelper.EMAIL, - ColumnHelper.NAME, - ColumnHelper.SURNAME, - ColumnHelper.TEXT1, - ColumnHelper.TEXT2, - ColumnHelper.ACCOMMODATION, - ColumnHelper.FOOD, - ColumnHelper.DIET, - ColumnHelper.NOTE, + UserColumns.ID, + UserColumns.EMAIL, + UserColumns.NAME, + UserColumns.SURNAME, + UserColumns.TEXT1, + UserColumns.TEXT2, + UserColumns.ACCOMMODATION, + UserColumns.FOOD, + UserColumns.DIET, + UserColumns.NOTE, ]; List? allFood; @@ -81,9 +81,9 @@ class _ServiceTabState extends State { _foodDefinition(p0), isEnabled: RightsService.isManager), ], - columns: ColumnHelper.generateColumns(columnIdentifiers, data: { - ColumnHelper.FOOD: allFood, - ColumnHelper.ACCOMMODATION: allAccommodation + columns: UserColumns.generateColumns(columnIdentifiers, data: { + UserColumns.FOOD: allFood, + UserColumns.ACCOMMODATION: allAccommodation }), ).DataGrid(), ); diff --git a/lib/pages/AdministrationOccasion/UsersTab.dart b/lib/pages/AdministrationOccasion/UsersTab.dart index bfbbe0a5..fd82932f 100644 --- a/lib/pages/AdministrationOccasion/UsersTab.dart +++ b/lib/pages/AdministrationOccasion/UsersTab.dart @@ -23,20 +23,20 @@ class UsersTab extends StatefulWidget { class _UsersTabState extends State { static const List columnIdentifiers = [ - ColumnHelper.ID, - ColumnHelper.EMAIL, - ColumnHelper.NAME, - ColumnHelper.SURNAME, - ColumnHelper.SEX, - ColumnHelper.ACCOMMODATION, - ColumnHelper.PHONE, - ColumnHelper.BIRTHDAY, - ColumnHelper.ROLE, - ColumnHelper.ADMINISTRATOR, - ColumnHelper.EDITOR, - ColumnHelper.APPROVER, - ColumnHelper.APPROVED, - ColumnHelper.INVITED + UserColumns.ID, + UserColumns.EMAIL, + UserColumns.NAME, + UserColumns.SURNAME, + UserColumns.SEX, + UserColumns.ACCOMMODATION, + UserColumns.PHONE, + UserColumns.BIRTHDAY, + UserColumns.ROLE, + UserColumns.ADMINISTRATOR, + UserColumns.EDITOR, + UserColumns.APPROVER, + UserColumns.APPROVED, + UserColumns.INVITED ]; List? _allUsers; // Initialize as null to indicate loading state @@ -78,7 +78,7 @@ class _UsersTabState extends State { DataGridAction(name: "Change password".tr(), action: (SingleTableDataGrid p0, [_]) => _setPassword(p0), isEnabled: RightsService.canUpdateUsers), DataGridAction(name: "Add to group".tr(), action: (SingleTableDataGrid p0, [_]) => _addToGroup(p0)), ], - columns: ColumnHelper.generateColumns(columnIdentifiers), + columns: UserColumns.generateColumns(columnIdentifiers), ).DataGrid(), ); } From fbbe9ade4ce6bea1140e3fa01de4ade1213427ed Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:06:29 +0100 Subject: [PATCH 127/159] UserColumns --- lib/pages/AdministrationOccasion/ServiceTab.dart | 2 +- .../{ColumnHelper.dart => UserColumns.dart} | 0 lib/pages/AdministrationOccasion/UsersTab.dart | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename lib/pages/AdministrationOccasion/{ColumnHelper.dart => UserColumns.dart} (100%) diff --git a/lib/pages/AdministrationOccasion/ServiceTab.dart b/lib/pages/AdministrationOccasion/ServiceTab.dart index 4454ee8e..c451ec5f 100644 --- a/lib/pages/AdministrationOccasion/ServiceTab.dart +++ b/lib/pages/AdministrationOccasion/ServiceTab.dart @@ -9,7 +9,7 @@ import 'package:fstapp/dataModels/Tb.dart'; import 'package:fstapp/dataServices/DbOccasions.dart'; import 'package:fstapp/dataServices/DbUsers.dart'; import 'package:fstapp/dataServices/RightsService.dart'; -import 'package:fstapp/pages/AdministrationOccasion/ColumnHelper.dart'; +import 'package:fstapp/pages/AdministrationOccasion/UserColumns.dart'; import 'package:fstapp/widgets/ServiceDialog.dart'; class ServiceTab extends StatefulWidget { diff --git a/lib/pages/AdministrationOccasion/ColumnHelper.dart b/lib/pages/AdministrationOccasion/UserColumns.dart similarity index 100% rename from lib/pages/AdministrationOccasion/ColumnHelper.dart rename to lib/pages/AdministrationOccasion/UserColumns.dart diff --git a/lib/pages/AdministrationOccasion/UsersTab.dart b/lib/pages/AdministrationOccasion/UsersTab.dart index fd82932f..0ee8aa25 100644 --- a/lib/pages/AdministrationOccasion/UsersTab.dart +++ b/lib/pages/AdministrationOccasion/UsersTab.dart @@ -9,7 +9,7 @@ import 'package:fstapp/dataServices/AuthService.dart'; import 'package:fstapp/dataServices/DbGroups.dart'; import 'package:fstapp/dataServices/DbUsers.dart'; import 'package:fstapp/dataServices/RightsService.dart'; -import 'package:fstapp/pages/AdministrationOccasion/ColumnHelper.dart'; +import 'package:fstapp/pages/AdministrationOccasion/UserColumns.dart'; import 'package:fstapp/services/DialogHelper.dart'; import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/services/UserManagementHelper.dart'; From aab7a0b3c5198e84c1a02f8b85bd00ab12533707 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:31:28 +0100 Subject: [PATCH 128/159] refactor --- lib/AppRouter.dart | 2 +- lib/AppRouter.gr.dart | 7 +++---- lib/components/dataGrid/AdminPageHelper.dart | 4 ++-- lib/dataModelsEshop/OrderModel.dart | 2 +- .../{AdminDashboard => Eshop}/BlueprintEditorTab.dart | 0 lib/pages/{ => Eshop}/FormEditPage.dart | 0 .../{AdministrationOccasion => Eshop}/FormEditorTab.dart | 0 lib/pages/{ => Eshop}/FormPage.dart | 5 ++--- .../OrderFinishScreen.dart | 0 lib/pages/{ => Eshop}/OrderPreviewScreen.dart | 0 10 files changed, 9 insertions(+), 11 deletions(-) rename lib/pages/{AdminDashboard => Eshop}/BlueprintEditorTab.dart (100%) rename lib/pages/{ => Eshop}/FormEditPage.dart (100%) rename lib/pages/{AdministrationOccasion => Eshop}/FormEditorTab.dart (100%) rename lib/pages/{ => Eshop}/FormPage.dart (98%) rename lib/pages/{AdministrationOccasion => Eshop}/OrderFinishScreen.dart (100%) rename lib/pages/{ => Eshop}/OrderPreviewScreen.dart (100%) diff --git a/lib/AppRouter.dart b/lib/AppRouter.dart index 03183664..ae02c31c 100644 --- a/lib/AppRouter.dart +++ b/lib/AppRouter.dart @@ -5,7 +5,7 @@ import 'package:fstapp/pages/AdminDashboardPage.dart'; import 'package:fstapp/pages/CheckPage.dart'; import 'package:fstapp/pages/EventEditPage.dart'; import 'package:fstapp/pages/EventPage.dart'; -import 'package:fstapp/pages/FormPage.dart'; +import 'package:fstapp/pages/Eshop/FormPage.dart'; import 'package:fstapp/pages/HtmlEditorPage.dart'; import 'package:fstapp/pages/InfoPage.dart'; import 'package:fstapp/pages/InstallPage.dart'; diff --git a/lib/AppRouter.gr.dart b/lib/AppRouter.gr.dart index bf88c984..15a77a29 100644 --- a/lib/AppRouter.gr.dart +++ b/lib/AppRouter.gr.dart @@ -15,14 +15,13 @@ import 'package:fstapp/dataModels/PlaceModel.dart' as _i31; import 'package:fstapp/pages/AdminDashboardPage.dart' deferred as _i1; import 'package:fstapp/pages/AdministrationOccasion/AdminPage.dart' deferred as _i2; -import 'package:fstapp/pages/AdministrationOccasion/FormEditorTab.dart' - deferred as _i8; import 'package:fstapp/pages/CheckPage.dart' deferred as _i3; +import 'package:fstapp/pages/Eshop/FormEditorTab.dart' deferred as _i8; +import 'package:fstapp/pages/Eshop/FormEditPage.dart' deferred as _i7; +import 'package:fstapp/pages/Eshop/FormPage.dart' deferred as _i9; import 'package:fstapp/pages/EventEditPage.dart' deferred as _i4; import 'package:fstapp/pages/EventPage.dart' deferred as _i5; import 'package:fstapp/pages/ForgotPasswordPage.dart' deferred as _i6; -import 'package:fstapp/pages/FormEditPage.dart' deferred as _i7; -import 'package:fstapp/pages/FormPage.dart' deferred as _i9; import 'package:fstapp/pages/GamePage.dart' deferred as _i10; import 'package:fstapp/pages/HtmlEditorPage.dart' deferred as _i11; import 'package:fstapp/pages/InfoPage.dart' deferred as _i12; diff --git a/lib/components/dataGrid/AdminPageHelper.dart b/lib/components/dataGrid/AdminPageHelper.dart index a5d89631..9192f7ef 100644 --- a/lib/components/dataGrid/AdminPageHelper.dart +++ b/lib/components/dataGrid/AdminPageHelper.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fstapp/RouterService.dart'; -import 'package:fstapp/pages/AdministrationOccasion/FormEditorTab.dart'; +import 'package:fstapp/pages/Eshop/FormEditorTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/GameTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/InformationTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/PlacesTab.dart'; @@ -9,7 +9,7 @@ import 'package:fstapp/pages/AdministrationOccasion/ScheduleTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/ServiceTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/UserGroupsTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/UsersTab.dart'; -import 'package:fstapp/pages/AdminDashboard/BlueprintEditorTab.dart'; +import 'package:fstapp/pages/Eshop/BlueprintEditorTab.dart'; class AdminPageHelper { static PreferredSizeWidget buildAdminAppBar( diff --git a/lib/dataModelsEshop/OrderModel.dart b/lib/dataModelsEshop/OrderModel.dart index c169229a..a77fdec5 100644 --- a/lib/dataModelsEshop/OrderModel.dart +++ b/lib/dataModelsEshop/OrderModel.dart @@ -22,7 +22,7 @@ class OrderModel { List? relatedProducts; PaymentInfoModel? paymentInfoModel; - static const String pendingState = "pending"; + static const String orderedState = "ordered"; static const String completedState = "completed"; static const String canceledState = "canceled"; diff --git a/lib/pages/AdminDashboard/BlueprintEditorTab.dart b/lib/pages/Eshop/BlueprintEditorTab.dart similarity index 100% rename from lib/pages/AdminDashboard/BlueprintEditorTab.dart rename to lib/pages/Eshop/BlueprintEditorTab.dart diff --git a/lib/pages/FormEditPage.dart b/lib/pages/Eshop/FormEditPage.dart similarity index 100% rename from lib/pages/FormEditPage.dart rename to lib/pages/Eshop/FormEditPage.dart diff --git a/lib/pages/AdministrationOccasion/FormEditorTab.dart b/lib/pages/Eshop/FormEditorTab.dart similarity index 100% rename from lib/pages/AdministrationOccasion/FormEditorTab.dart rename to lib/pages/Eshop/FormEditorTab.dart diff --git a/lib/pages/FormPage.dart b/lib/pages/Eshop/FormPage.dart similarity index 98% rename from lib/pages/FormPage.dart rename to lib/pages/Eshop/FormPage.dart index 52b9cd36..ffc20ae5 100644 --- a/lib/pages/FormPage.dart +++ b/lib/pages/Eshop/FormPage.dart @@ -14,9 +14,8 @@ import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/dataServices/RightsService.dart'; -import 'package:fstapp/pages/AdministrationOccasion/OrderFinishScreen.dart'; -import 'package:fstapp/pages/FormEditPage.dart'; -import 'package:fstapp/pages/OrderPreviewScreen.dart'; +import 'package:fstapp/pages/Eshop/OrderFinishScreen.dart'; +import 'package:fstapp/pages/Eshop/OrderPreviewScreen.dart'; import 'package:fstapp/services/FormHelper.dart'; import 'package:fstapp/services/Utilities.dart'; import 'package:fstapp/services/UuidConverter.dart'; diff --git a/lib/pages/AdministrationOccasion/OrderFinishScreen.dart b/lib/pages/Eshop/OrderFinishScreen.dart similarity index 100% rename from lib/pages/AdministrationOccasion/OrderFinishScreen.dart rename to lib/pages/Eshop/OrderFinishScreen.dart diff --git a/lib/pages/OrderPreviewScreen.dart b/lib/pages/Eshop/OrderPreviewScreen.dart similarity index 100% rename from lib/pages/OrderPreviewScreen.dart rename to lib/pages/Eshop/OrderPreviewScreen.dart From 20ce9226bc518830ba79a28fb3d3194e4b4288fe Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:34:32 +0100 Subject: [PATCH 129/159] orders table --- lib/components/dataGrid/AdminPageHelper.dart | 3 +- lib/components/dataGrid/PlutoAbstract.dart | 3 +- .../dataGrid/SingleTableDataGrid.dart | 2 +- lib/dataModels/EventModel.dart | 2 +- lib/dataModels/ExclusiveGroupModel.dart | 4 +- lib/dataModels/InformationModel.dart | 3 +- lib/dataModels/OccasionUserModel.dart | 3 +- lib/dataModels/PlaceModel.dart | 3 +- lib/dataModels/UserGroupInfoModel.dart | 3 +- lib/dataModelsEshop/BlueprintObjectModel.dart | 2 +- lib/dataModelsEshop/OrderModel.dart | 75 +++++-- lib/dataServices/DbEshop.dart | 12 +- lib/pages/Eshop/EshopColumns.dart | 187 ++++++++++++++++++ lib/pages/Eshop/FormEditPage.dart | 3 +- lib/pages/Eshop/OrdersTab.dart | 46 +++++ lib/services/Utilities.dart | 2 +- 16 files changed, 318 insertions(+), 35 deletions(-) create mode 100644 lib/pages/Eshop/EshopColumns.dart create mode 100644 lib/pages/Eshop/OrdersTab.dart diff --git a/lib/components/dataGrid/AdminPageHelper.dart b/lib/components/dataGrid/AdminPageHelper.dart index 9192f7ef..7644e516 100644 --- a/lib/components/dataGrid/AdminPageHelper.dart +++ b/lib/components/dataGrid/AdminPageHelper.dart @@ -10,6 +10,7 @@ import 'package:fstapp/pages/AdministrationOccasion/ServiceTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/UserGroupsTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/UsersTab.dart'; import 'package:fstapp/pages/Eshop/BlueprintEditorTab.dart'; +import 'package:fstapp/pages/Eshop/OrdersTab.dart'; class AdminPageHelper { static PreferredSizeWidget buildAdminAppBar( @@ -83,7 +84,7 @@ class AdminTabDefinition { form: AdminTabDefinition(label: "Form".tr(), icon: Icons.list, widget: FormEditorTab()), blueprint: AdminTabDefinition(label: "Blueprint".tr(), icon: Icons.grid_on, widget: BlueprintTab()), tickets: AdminTabDefinition(label: "Tickets".tr(), icon: Icons.local_activity, widget: GameTab()), - orders: AdminTabDefinition(label: "Orders".tr(), icon: Icons.shopping_cart, widget: GameTab()), + orders: AdminTabDefinition(label: "Orders".tr(), icon: Icons.shopping_cart, widget: OrdersTab()), }; } diff --git a/lib/components/dataGrid/PlutoAbstract.dart b/lib/components/dataGrid/PlutoAbstract.dart index 6cecdd8f..af3a2a76 100644 --- a/lib/components/dataGrid/PlutoAbstract.dart +++ b/lib/components/dataGrid/PlutoAbstract.dart @@ -1,7 +1,8 @@ +import 'package:flutter/cupertino.dart'; import 'package:pluto_grid_plus/pluto_grid_plus.dart'; abstract class IPlutoRowModel { - PlutoRow? toPlutoRow(); + PlutoRow? toPlutoRow(BuildContext context); Future deleteMethod(); Future updateMethod(); String toBasicString(); diff --git a/lib/components/dataGrid/SingleTableDataGrid.dart b/lib/components/dataGrid/SingleTableDataGrid.dart index 2e3463d2..95b71e39 100644 --- a/lib/components/dataGrid/SingleTableDataGrid.dart +++ b/lib/components/dataGrid/SingleTableDataGrid.dart @@ -118,7 +118,7 @@ class SingleTableDataGrid { Future reloadData() async { var defaultRow = { firstColumnTypeId: PlutoCell(value: "delete")}; final dataList = await loadData(); - var rowList = dataList.map((i) => i.toPlutoRow()!).toList(); + var rowList = dataList.map((i) => i.toPlutoRow(context)!).toList(); for (var element in rowList) {element.cells.addAll(defaultRow);} stateManager.removeAllRows(); stateManager.appendRows(rowList); diff --git a/lib/dataModels/EventModel.dart b/lib/dataModels/EventModel.dart index faf8aaf9..088b4264 100644 --- a/lib/dataModels/EventModel.dart +++ b/lib/dataModels/EventModel.dart @@ -230,7 +230,7 @@ class EventModel extends IPlutoRowModel { } @override - PlutoRow toPlutoRow() { + PlutoRow toPlutoRow(BuildContext context) { return PlutoRow(cells: { idColumn: PlutoCell(value: id), isHiddenColumn: PlutoCell(value: isHidden.toString()), diff --git a/lib/dataModels/ExclusiveGroupModel.dart b/lib/dataModels/ExclusiveGroupModel.dart index 1c2be394..f9b39844 100644 --- a/lib/dataModels/ExclusiveGroupModel.dart +++ b/lib/dataModels/ExclusiveGroupModel.dart @@ -1,6 +1,6 @@ +import 'package:flutter/cupertino.dart'; import 'package:fstapp/components/dataGrid/PlutoAbstract.dart'; import 'package:fstapp/dataServices/DbEvents.dart'; -import 'package:fstapp/dataServices/DbGroups.dart'; import 'package:pluto_grid_plus/pluto_grid_plus.dart'; class ExclusiveGroupModel extends IPlutoRowModel { @@ -49,7 +49,7 @@ class ExclusiveGroupModel extends IPlutoRowModel { } @override - PlutoRow toPlutoRow() { + PlutoRow toPlutoRow(BuildContext context) { return PlutoRow(cells: { idColumn: PlutoCell(value: id), titleColumn: PlutoCell(value: title), diff --git a/lib/dataModels/InformationModel.dart b/lib/dataModels/InformationModel.dart index c9e52c6c..d7dc273f 100644 --- a/lib/dataModels/InformationModel.dart +++ b/lib/dataModels/InformationModel.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:fstapp/components/dataGrid/PlutoAbstract.dart'; import 'package:fstapp/dataModels/Tb.dart'; import 'package:fstapp/dataServices/DbInformation.dart'; @@ -92,7 +93,7 @@ class InformationModel extends IPlutoRowModel { } @override - PlutoRow toPlutoRow() { + PlutoRow toPlutoRow(BuildContext context) { return PlutoRow(cells: { Tb.information.id: PlutoCell(value: id), Tb.information.title: PlutoCell(value: title), diff --git a/lib/dataModels/OccasionUserModel.dart b/lib/dataModels/OccasionUserModel.dart index b0dbd9e5..fa7eaeef 100644 --- a/lib/dataModels/OccasionUserModel.dart +++ b/lib/dataModels/OccasionUserModel.dart @@ -1,4 +1,5 @@ import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; import 'package:fstapp/dataServices/DbOccasions.dart'; import 'package:fstapp/dataServices/DbUsers.dart'; import 'package:fstapp/dataServices/RightsService.dart'; @@ -142,7 +143,7 @@ class OccasionUserModel extends IPlutoRowModel { String toBasicString() => data?[Tb.occasion_users.data_email]??""; @override - PlutoRow toPlutoRow() { + PlutoRow toPlutoRow(BuildContext context) { Map json = {}; Map foodServices = servicesToPlutoRow(services, DbOccasions.serviceTypeFood); json.addAll(foodServices); diff --git a/lib/dataModels/PlaceModel.dart b/lib/dataModels/PlaceModel.dart index c136a3ce..0c7619f0 100644 --- a/lib/dataModels/PlaceModel.dart +++ b/lib/dataModels/PlaceModel.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:fstapp/components/dataGrid/PlutoAbstract.dart'; import 'package:fstapp/dataModels/EventModel.dart'; import 'package:fstapp/dataModels/Tb.dart'; @@ -87,7 +88,7 @@ class PlaceModel extends IPlutoRowModel { } @override - PlutoRow toPlutoRow() { + PlutoRow toPlutoRow(BuildContext context) { return PlutoRow(cells: { Tb.places.id: PlutoCell(value: id), Tb.places.title: PlutoCell(value: title), diff --git a/lib/dataModels/UserGroupInfoModel.dart b/lib/dataModels/UserGroupInfoModel.dart index c0ecaf66..9d1b0f16 100644 --- a/lib/dataModels/UserGroupInfoModel.dart +++ b/lib/dataModels/UserGroupInfoModel.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:fstapp/components/dataGrid/DataGridHelper.dart'; import 'package:fstapp/components/dataGrid/PlutoAbstract.dart'; import 'package:fstapp/dataModels/InformationModel.dart'; @@ -92,7 +93,7 @@ class UserGroupInfoModel extends IPlutoRowModel { } @override - PlutoRow toPlutoRow() { + PlutoRow toPlutoRow(BuildContext context) { var checkpoints = (data?["game"] ?? []) .where((item) => checkpointTitlesDict.containsKey(item["check_point"])) // Filter out non-existent checkpoints .map((item) => checkpointTitlesDict[item["check_point"]]) diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index 37a95ea2..a4f7932a 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -122,7 +122,7 @@ class BlueprintObjectModel { // Get the order details var order = blueprint?.orders?.firstWhere((p) => p.id == opt.orderId); var orderString = order != null - ? "\n${order.data?["name"]} ${order.data?["surname"]} (${order.data?["email"]})\n${order.data?["note"] ?? ""}" + ? "\n${order.toCustomerData()}\n${order.toCustomerNote()}" : ""; // Add ticket note if available diff --git a/lib/dataModelsEshop/OrderModel.dart b/lib/dataModelsEshop/OrderModel.dart index a77fdec5..0e51be30 100644 --- a/lib/dataModelsEshop/OrderModel.dart +++ b/lib/dataModelsEshop/OrderModel.dart @@ -1,10 +1,17 @@ +import 'dart:js_interop'; + +import 'package:flutter/cupertino.dart'; +import 'package:fstapp/components/dataGrid/PlutoAbstract.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; import 'package:fstapp/dataModelsEshop/TicketModel.dart'; import 'package:fstapp/dataModelsEshop/ProductModel.dart'; import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/dataModelsEshop/PaymentInfoModel.dart'; +import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/services/Utilities.dart'; +import 'package:pluto_grid_plus/pluto_grid_plus.dart'; -class OrderModel { +class OrderModel extends IPlutoRowModel { int? id; DateTime? createdAt; DateTime? updatedAt; @@ -26,6 +33,23 @@ class OrderModel { static const String completedState = "completed"; static const String canceledState = "canceled"; + OrderModel({ + this.id, + this.createdAt, + this.updatedAt, + this.price, + this.state, + this.data, + this.occasion, + this.paymentInfo, + this.form, + this.currencyCode, + this.relatedTickets, + this.relatedSpots, + this.relatedProducts, + this.paymentInfoModel, + }); + factory OrderModel.fromJson(Map json) { return OrderModel( id: json[TbEshop.orders.id], @@ -47,6 +71,11 @@ class OrderModel { ); } + static OrderModel fromPlutoJson(Map json) { + return OrderModel( + id: json[TbEshop.orders.id] == -1 ? null : json[TbEshop.orders.id]); + } + Map toJson() => { TbEshop.orders.id: id, TbEshop.orders.created_at: createdAt?.toIso8601String(), @@ -60,22 +89,32 @@ class OrderModel { TbEshop.orders.currency_code: currencyCode, }; - String toBasicString() => id != null ? "Order #$id" : "Order"; + @override + PlutoRow toPlutoRow(BuildContext context) { + return PlutoRow(cells: { + TbEshop.orders.id: PlutoCell(value: id ?? 0), + TbEshop.orders.price: PlutoCell(value: price != null ? Utilities.formatPrice(context, price!) : ""), + TbEshop.orders.state: PlutoCell(value: state ?? orderedState), + TbEshop.payment_info.amount: PlutoCell(value: paymentInfoModel?.amount != null ? Utilities.formatPrice(context, paymentInfoModel!.amount!) : ""), + TbEshop.payment_info.paid: PlutoCell(value: paymentInfoModel?.paid != null ? Utilities.formatPrice(context, paymentInfoModel!.paid!) : ""), + TbEshop.orders.data: PlutoCell(value: toCustomerData()), + }); + } - OrderModel({ - this.id, - this.createdAt, - this.updatedAt, - this.price, - this.state, - this.data, - this.occasion, - this.paymentInfo, - this.form, - this.currencyCode, - this.relatedTickets, - this.relatedSpots, - this.relatedProducts, - this.paymentInfoModel, - }); + @override + Future deleteMethod() async { + await DbEshop.deleteOrder(this); + } + + @override + Future updateMethod() async { + + } + + @override + String toBasicString() => "Order #$id"; + + String toCustomerData() => "${data?["name"]} ${data?["surname"]} (${data?["email"]})"; + + String toCustomerNote() => "${data?["note"] ?? ""}"; } diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 5d9f8b37..85e60629 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -163,7 +163,7 @@ class DbEshop { return true; } - static Future?> getOrders(String formKey) async { + static Future> getAllOrders(String formKey) async { final response = await _supabase.rpc( 'get_orders', @@ -173,7 +173,7 @@ class DbEshop { ); if (response["code"] != 200) { - return null; + return []; } var json = response["data"]; @@ -189,8 +189,9 @@ class DbEshop { final List relatedTickets = tickets!.where((ticket) { return orderProductTickets!.any((opt) => opt.orderId == order.id && opt.ticketId == ticket.id); }).toList(); + order.relatedTickets = relatedTickets; - for (var ticket in tickets!) { + for (var ticket in order.relatedTickets!) { // Relate spots to the ticket via orderProductTickets ticket.relatedSpots = spots!.where((spot) { return orderProductTickets!.any((opt) => opt.ticketId == ticket.id && opt.id == spot.orderProductTicket); @@ -223,7 +224,6 @@ class DbEshop { payments!.firstWhereOrNull((payment) => payment.id == order.paymentInfo); // Update the order object - order.relatedTickets = relatedTickets; order.relatedSpots = relatedSpots; order.relatedProducts = relatedProducts; order.paymentInfoModel = relatedPaymentInfo; @@ -232,4 +232,8 @@ class DbEshop { return orders; } + static Future deleteOrder(OrderModel model) async { + + } + } diff --git a/lib/pages/Eshop/EshopColumns.dart b/lib/pages/Eshop/EshopColumns.dart new file mode 100644 index 00000000..f17b4ecb --- /dev/null +++ b/lib/pages/Eshop/EshopColumns.dart @@ -0,0 +1,187 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:fstapp/components/dataGrid/DataGridHelper.dart'; +import 'package:fstapp/dataModelsEshop/OrderModel.dart'; +import 'package:fstapp/dataModelsEshop/TicketModel.dart'; +import 'package:pluto_grid_plus/pluto_grid_plus.dart'; +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; + +class EshopColumns { + // Column identifier constants + static const String TICKET_ID = "ticketId"; + static const String TICKET_SYMBOL = "ticketSymbol"; + static const String TICKET_STATE = "ticketState"; + static const String TICKET_NOTE = "ticketNote"; + + static const String ORDER_ID = "orderId"; + static const String ORDER_PRICE = "orderPrice"; + static const String ORDER_STATE = "orderState"; + + static const String PRODUCT_ID = "productId"; + static const String PRODUCT_TITLE = "productTitle"; + static const String PRODUCT_PRICE = "productPrice"; + + static const String PAYMENT_INFO_AMOUNT = "paymentInfoAmount"; + static const String PAYMENT_INFO_PAID = "paymentInfoPaid"; + + static const String CUSTOMER_DATA = "customerData"; + + // Define columns + static Map get columnBuilders => { + TICKET_ID: [ + PlutoColumn( + hide: true, + readOnly: true, + enableEditingMode: false, + title: "Id".tr(), + field: TbEshop.tickets.id, + type: PlutoColumnType.number(defaultValue: -1), + width: 50, + renderer: (rendererContext) => DataGridHelper.idRenderer(rendererContext), + ), + ], + TICKET_SYMBOL: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Ticket Symbol".tr(), + field: TbEshop.tickets.ticket_symbol, + type: PlutoColumnType.text(), + width: 150, + ), + ], + TICKET_STATE: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "State".tr(), + field: TbEshop.tickets.state, + type: PlutoColumnType.select( + [TicketModel.orderedState, TicketModel.activeState, TicketModel.usedState, TicketModel.stornoState], + ), + width: 120, + ), + ], + TICKET_NOTE: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Note".tr(), + field: TbEshop.tickets.note, + type: PlutoColumnType.text(), + width: 200, + ), + ], + ORDER_ID: [ + PlutoColumn( + hide: true, + title: "Id".tr(), + field: TbEshop.orders.id, + type: PlutoColumnType.number(defaultValue: -1), + readOnly: true, + enableEditingMode: false, + width: 50, + renderer: (rendererContext) => DataGridHelper.idRenderer(rendererContext), + ), + ], + ORDER_PRICE: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Price".tr(), + field: TbEshop.orders.price, + type: PlutoColumnType.text(), + textAlign: PlutoColumnTextAlign.end, + width: 100, + ), + ], + ORDER_STATE: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "State".tr(), + field: TbEshop.orders.state, + type: PlutoColumnType.select( + [OrderModel.orderedState, OrderModel.completedState, OrderModel.canceledState], + ), + width: 120, + ), + ], + PRODUCT_ID: [ + PlutoColumn( + hide: true, + title: "Id".tr(), + field: TbEshop.products.id, + type: PlutoColumnType.number(defaultValue: -1), + readOnly: true, + enableEditingMode: false, + width: 50, + renderer: (rendererContext) => DataGridHelper.idRenderer(rendererContext), + ), + ], + PRODUCT_TITLE: [ + PlutoColumn( + title: "Title".tr(), + field: TbEshop.products.title, + type: PlutoColumnType.text(), + width: 200, + ), + ], + PRODUCT_PRICE: [ + PlutoColumn( + title: "Product Price".tr(), + field: TbEshop.products.price, + type: PlutoColumnType.text(), + textAlign: PlutoColumnTextAlign.end, + width: 100, + ), + ], + PAYMENT_INFO_AMOUNT: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Amount".tr(), + field: TbEshop.payment_info.amount, + type: PlutoColumnType.text(), + textAlign: PlutoColumnTextAlign.end, + width: 120, + ), + ], + PAYMENT_INFO_PAID: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Paid".tr(), + field: TbEshop.payment_info.paid, + type: PlutoColumnType.text(), + textAlign: PlutoColumnTextAlign.end, + width: 120, + ), + ], + CUSTOMER_DATA: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Customer".tr(), + field: TbEshop.orders.data, + type: PlutoColumnType.text(), + width: 300, + ), + ], + }; + + /// Generates columns based on a list of column identifiers. + /// Optional `data` map is used for columns that require extra configuration. + static List generateColumns(List identifiers, {Map? data}) { + return identifiers + .where((id) => columnBuilders.containsKey(id)) + .expand((id) { + var columnEntry = columnBuilders[id]; + if (columnEntry is List) { + return columnEntry; // Static columns + } else if (columnEntry is Function) { + return (columnEntry(data ?? {}) as List); + } + return []; + }).toList(); + } +} diff --git a/lib/pages/Eshop/FormEditPage.dart b/lib/pages/Eshop/FormEditPage.dart index 61b84d37..896c6d00 100644 --- a/lib/pages/Eshop/FormEditPage.dart +++ b/lib/pages/Eshop/FormEditPage.dart @@ -20,7 +20,8 @@ class _FormEditPageState extends State with SingleTickerProviderSt // List of active tabs by name final List activeTabNames = [ AdminTabDefinition.form, - AdminTabDefinition.blueprint + AdminTabDefinition.blueprint, + AdminTabDefinition.orders, ]; @override diff --git a/lib/pages/Eshop/OrdersTab.dart b/lib/pages/Eshop/OrdersTab.dart new file mode 100644 index 00000000..5bd1353c --- /dev/null +++ b/lib/pages/Eshop/OrdersTab.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:fstapp/components/dataGrid/SingleTableDataGrid.dart'; +import 'package:fstapp/dataModelsEshop/OrderModel.dart'; +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/pages/Eshop/EshopColumns.dart'; +import 'package:auto_route/auto_route.dart'; + +class OrdersTab extends StatefulWidget { + const OrdersTab({super.key}); + + @override + _OrdersTabState createState() => _OrdersTabState(); +} + +class _OrdersTabState extends State { + String? formKey; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (formKey == null && context.routeData.pathParams.isNotEmpty) { + formKey = context.routeData.pathParams.getString("formKey"); + } + } + + static const List columnIdentifiers = [ + EshopColumns.ORDER_ID, + EshopColumns.CUSTOMER_DATA, + EshopColumns.ORDER_STATE, + EshopColumns.ORDER_PRICE, + EshopColumns.PAYMENT_INFO_PAID, + ]; + + @override + Widget build(BuildContext context) { + return SingleTableDataGrid( + context, + () => DbEshop.getAllOrders(formKey!), + OrderModel.fromPlutoJson, + DataGridFirstColumn.none, + TbEshop.orders.id, + columns: EshopColumns.generateColumns(columnIdentifiers), + ).DataGrid(); + } +} \ No newline at end of file diff --git a/lib/services/Utilities.dart b/lib/services/Utilities.dart index ccde34a8..eb0abd93 100644 --- a/lib/services/Utilities.dart +++ b/lib/services/Utilities.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class Utilities { - static formatPrice(BuildContext context, double price) { + static String formatPrice(BuildContext context, double price) { // Get locale from context or fallback to Czech locale //final locale = EasyLocalization.of(context)?.locale.toString() ?? 'cs_CZ'; final locale = 'cs_CZ'; From 5060dbb05eb9ef6cbc8ae85475192c8475151851 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 19:18:52 +0100 Subject: [PATCH 130/159] translations cs --- assets/translations/cs.json | 16 ++++++++++++++++ assets/translations/en.json | 16 ++++++++++++---- lib/pages/Eshop/BlueprintEditorTab.dart | 1 - lib/pages/Eshop/FormPage.dart | 2 +- lib/pages/Eshop/OrderPreviewScreen.dart | 6 +++--- lib/services/FormHelper.dart | 4 ++-- lib/widgets/HtmlView.dart | 2 +- lib/widgets/SeatReservationWidget.dart | 4 ++-- 8 files changed, 37 insertions(+), 14 deletions(-) diff --git a/assets/translations/cs.json b/assets/translations/cs.json index a5908c0d..a46b8202 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -296,5 +296,21 @@ "The processing has been cancelled.": "Zpracování bylo zrušeno.", "The processing has completed successfully.": "Zpracování bylo úspěšně dokončeno.", "Saving changes": "Ukládání změn", + "Total Price: {price}": "Celková cena: {price}", + "Price": "Cena", + "Ticket": "Vstupenka", + "Drop file here": "Přetáhněte soubor sem", + "Continue": "Pokračovat", + "Spot": "Místo", + "Back to Form": "Zpět k formuláři", + "Close": "Zavřít", + "Your order was accepted!": "Vaše objednávka byla přijata!", + "Order Failed": "Objednávka se nezdařila", + "Payment information has been sent to your email.": "Informace o platbě byly odeslány na váš e-mail.", + "An error occurred while processing your order.": "Při zpracování vaší objednávky došlo k chybě.", + "Please select a seat.": "Vyberte prosím místo.", + "Summary": "Souhrn", + "Submit order": "Odeslat objednávku", + "It is not possible to select more tickets.": "Více vstupenek není možné vybrat.", "_": "_" } \ No newline at end of file diff --git a/assets/translations/en.json b/assets/translations/en.json index 268532c5..4708ec53 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -296,13 +296,21 @@ "The processing has been cancelled.": "The processing has been cancelled.", "The processing has completed successfully.": "The processing has completed successfully.", "Saving changes": "Saving changes", - "Your order was successfully sent to your email {email}.": "Your order was successfully sent to your email {email}.", - "Your order has been sent successfully!": "Your order has been sent successfully!", "Total Price: {price}": "Total Price: {price}", - "Ticket {number}": "Ticket {number}", - "Add another ticket": "Add another ticket", "Price": "Price", "Ticket": "Ticket", "Drop file here": "Drop file here", + "Continue": "Continue", + "Spot": "Spot", + "Back to Form":"Back to Form", + "Close": "Close", + "Your order was accepted!":"Your order was accepted!", + "Order Failed": "Order Failed", + "Payment information has been sent to your email.":"Payment information has been sent to your email.", + "An error occurred while processing your order.": "An error occurred while processing your order.", + "Please select a seat.": "Please select a seat.", + "Summary": "Summary", + "Submit order": "Submit order", + "It is not possible to select more tickets.": "It is not possible to select more tickets.", "_":"_" } \ No newline at end of file diff --git a/lib/pages/Eshop/BlueprintEditorTab.dart b/lib/pages/Eshop/BlueprintEditorTab.dart index 8b9827df..eac5bcf2 100644 --- a/lib/pages/Eshop/BlueprintEditorTab.dart +++ b/lib/pages/Eshop/BlueprintEditorTab.dart @@ -1,7 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:fstapp/RouterService.dart'; import 'package:fstapp/components/seatReservation/widgets/SeatWidget.dart'; import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; diff --git a/lib/pages/Eshop/FormPage.dart b/lib/pages/Eshop/FormPage.dart index ffc20ae5..14d22ea2 100644 --- a/lib/pages/Eshop/FormPage.dart +++ b/lib/pages/Eshop/FormPage.dart @@ -296,7 +296,7 @@ class _FormPageState extends State { ButtonsHelper.primaryButton( context: context, onPressed: _isLoading ? null : _showOrderPreview, - label: "Continue", + label: "Continue".tr(), isLoading: _isLoading, height: 50.0, width: 250.0, diff --git a/lib/pages/Eshop/OrderPreviewScreen.dart b/lib/pages/Eshop/OrderPreviewScreen.dart index a2f7d38e..ca8edd49 100644 --- a/lib/pages/Eshop/OrderPreviewScreen.dart +++ b/lib/pages/Eshop/OrderPreviewScreen.dart @@ -43,7 +43,7 @@ class OrderPreviewScreen extends StatelessWidget { // Header Center( child: Text( - "Rekapitulace Vaší objednávky:".tr(), + "Summary".tr(), style: StylesConfig.textStyleBig.copyWith( fontSize: 18 * fontSizeFactor, fontWeight: FontWeight.bold, @@ -73,7 +73,7 @@ class OrderPreviewScreen extends StatelessWidget { child: ButtonsHelper.primaryButton( context: context, onPressed: onSendPressed, - label: "Odeslat objednávku", + label: "Submit order".tr(), height: 50.0, width: 250.0 )), @@ -166,7 +166,7 @@ class OrderPreviewScreen extends StatelessWidget { children: [ // Display ticket index at the top of the container Text( - "Ticket {number}".tr(namedArgs: {"number": (index).toString()}), + "${"Ticket".tr()} $index", style: StylesConfig.textStyleBig.copyWith( fontSize: 16 * fontSizeFactor, fontWeight: FontWeight.bold, diff --git a/lib/services/FormHelper.dart b/lib/services/FormHelper.dart index bc17de81..a8051404 100644 --- a/lib/services/FormHelper.dart +++ b/lib/services/FormHelper.dart @@ -209,7 +209,7 @@ class FormHelper { } formHolder.controller!.updateTotalPrice?.call(); }, - label: "Výběr místa", + label: "Seat selection".tr(), height: 50.0, width: 250.0, suffixIcon: Icon(Icons.event_seat) @@ -238,7 +238,7 @@ class FormHelper { child: Align( alignment: Alignment.center, child: Text( - "Ticket {number}".tr(namedArgs: {"number": (i + 1).toString()}), // Use translated string + "${"Ticket".tr()} ${i+1}", // Use translated string style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16 * fontSizeFactor, diff --git a/lib/widgets/HtmlView.dart b/lib/widgets/HtmlView.dart index a185eb58..feacce9a 100644 --- a/lib/widgets/HtmlView.dart +++ b/lib/widgets/HtmlView.dart @@ -16,7 +16,7 @@ class HtmlWithAppLinksWidget extends HtmlWidget { @override FutureOr Function(String p1)? get onTapUrl { return (String url) { - if (AppConfig.compatibleUrls().any((u) => url.isNotEmpty && (url.startsWith(u)) || url.contains("localhost"))) { + if (AppConfig.compatibleUrls().where((u) => u.isNotEmpty).any((u) =>url.startsWith(u)) || url.contains("localhost")) { var path = url.split('/#/').last; RouterService.navigate(context, path); return true; diff --git a/lib/widgets/SeatReservationWidget.dart b/lib/widgets/SeatReservationWidget.dart index 31b29161..0150e8fc 100644 --- a/lib/widgets/SeatReservationWidget.dart +++ b/lib/widgets/SeatReservationWidget.dart @@ -88,7 +88,7 @@ class _SeatReservationWidgetState extends State { onPressed: () { widget.onCloseSeatReservation?.call(widget.selectedSeats); }, - label: "Continue", + label: "Continue".tr(), width: 250 ), ), @@ -122,7 +122,7 @@ class _SeatReservationWidgetState extends State { } } else if (model.seatState == SeatState.available) { if(widget.maxTickets != null && widget.selectedSeats.length >= widget.maxTickets!){ - ToastHelper.Show(context, "Více vstupenek není možné vybrat.".tr()); + ToastHelper.Show(context, "It is not possible to select more tickets.".tr()); return; } model.seatState = SeatState.selected_by_me; From ca8e39994496874713c7a1f40e7088580f0ceb31 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Mon, 9 Dec 2024 19:27:41 +0100 Subject: [PATCH 131/159] translations cs fix --- assets/translations/cs.json | 1 + assets/translations/en.json | 1 + lib/widgets/HtmlView.dart | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/translations/cs.json b/assets/translations/cs.json index a46b8202..cd41f9d5 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -312,5 +312,6 @@ "Summary": "Souhrn", "Submit order": "Odeslat objednávku", "It is not possible to select more tickets.": "Více vstupenek není možné vybrat.", + "Seat selection": "Výběr místa", "_": "_" } \ No newline at end of file diff --git a/assets/translations/en.json b/assets/translations/en.json index 4708ec53..ffd9cf29 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -312,5 +312,6 @@ "Summary": "Summary", "Submit order": "Submit order", "It is not possible to select more tickets.": "It is not possible to select more tickets.", + "Seat selection": "Seat selection", "_":"_" } \ No newline at end of file diff --git a/lib/widgets/HtmlView.dart b/lib/widgets/HtmlView.dart index feacce9a..ee4c46bb 100644 --- a/lib/widgets/HtmlView.dart +++ b/lib/widgets/HtmlView.dart @@ -7,7 +7,7 @@ import 'package:fstapp/themeConfig.dart'; class HtmlWithAppLinksWidget extends HtmlWidget { HtmlWithAppLinksWidget(this.context, super.html, - {required ColumnMode renderMode, + {super.key, required ColumnMode renderMode, super.textStyle, super.customStylesBuilder}); From b785dfc9a4cac0641e2738441c61079a44cd6b71 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 07:10:18 +0100 Subject: [PATCH 132/159] detect editor on form --- lib/AppRouter.dart | 1 + lib/RouterService.dart | 5 +- lib/dataModelsEshop/OrderModel.dart | 5 +- lib/dataModelsEshop/TicketModel.dart | 2 +- lib/dataServices/RightsService.dart | 25 +++------- lib/dataServices/SynchroService.dart | 5 +- lib/pages/Eshop/EshopColumns.dart | 6 +-- lib/pages/LoginPage.dart | 3 +- lib/services/LinkModel.dart | 39 ++++++++++++++++ lib/tests/DataServiceTests.dart | 3 +- scripts/database/functions/get_app_config.sql | 46 +++++++++++++------ 11 files changed, 95 insertions(+), 45 deletions(-) create mode 100644 lib/services/LinkModel.dart diff --git a/lib/AppRouter.dart b/lib/AppRouter.dart index ae02c31c..6935c4ce 100644 --- a/lib/AppRouter.dart +++ b/lib/AppRouter.dart @@ -92,6 +92,7 @@ class AppRouter extends RootStackRouter { SettingsPage.ROUTE, InstallPage.ROUTE, AdminDashboardPage.ROUTE, + FormPage.ROUTE, ]; } } diff --git a/lib/RouterService.dart b/lib/RouterService.dart index b5b2f525..814d5f7d 100644 --- a/lib/RouterService.dart +++ b/lib/RouterService.dart @@ -5,6 +5,7 @@ import 'package:fstapp/AppRouter.gr.dart'; import 'package:fstapp/dataServices/AppConfigService.dart'; import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/dataServices/SynchroService.dart'; +import 'package:fstapp/services/LinkModel.dart'; class RouterService { static const LINK = "link"; @@ -101,9 +102,9 @@ class RouterService { static final router = AppRouter(); - static Future updateOccasionFromLink(String newLink) async { + static Future updateOccasionFromLink(LinkModel link) async { bool canContinue = true; - var checkedObject = await SynchroService.getAppConfig(newLink); + var checkedObject = await SynchroService.getAppConfig(occasionLink: link.occasionLink, formLink: link.formLink); RightsService.currentUserOccasion = checkedObject.user; RightsService.currentOccasion = checkedObject.occasionId; RightsService.currentLink = checkedObject.link; diff --git a/lib/dataModelsEshop/OrderModel.dart b/lib/dataModelsEshop/OrderModel.dart index 0e51be30..c68dfdef 100644 --- a/lib/dataModelsEshop/OrderModel.dart +++ b/lib/dataModelsEshop/OrderModel.dart @@ -30,8 +30,9 @@ class OrderModel extends IPlutoRowModel { PaymentInfoModel? paymentInfoModel; static const String orderedState = "ordered"; - static const String completedState = "completed"; - static const String canceledState = "canceled"; + static const String paidState = "paid"; + static const String canceledState = "storno"; + OrderModel({ this.id, diff --git a/lib/dataModelsEshop/TicketModel.dart b/lib/dataModelsEshop/TicketModel.dart index 6763e955..8999e916 100644 --- a/lib/dataModelsEshop/TicketModel.dart +++ b/lib/dataModelsEshop/TicketModel.dart @@ -20,7 +20,7 @@ class TicketModel { OrderModel? relatedOrder; static const String orderedState = "ordered"; - static const String activeState = "active"; + static const String paidState = "paid"; static const String usedState = "used"; static const String stornoState = "storno"; diff --git a/lib/dataServices/RightsService.dart b/lib/dataServices/RightsService.dart index 741c1902..de2eec56 100644 --- a/lib/dataServices/RightsService.dart +++ b/lib/dataServices/RightsService.dart @@ -3,6 +3,7 @@ import 'package:fstapp/RouterService.dart'; import 'package:fstapp/dataServices/OfflineDataService.dart'; import 'package:fstapp/dataModels/OccasionUserModel.dart'; import 'package:fstapp/dataServices/SynchroService.dart'; +import 'package:fstapp/services/LinkModel.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class RightsService{ @@ -14,17 +15,13 @@ class RightsService{ static Future updateOccasionData([String? link]) async { if (currentOccasion == null || link != currentLink) { - var toUpdate = link ?? RouterService.currentOccasionLink; - if (toUpdate.isEmpty) { - toUpdate = extractOccasionLink(Uri.base.toString()); - - var rootLinks = AppRouter.getRootLinks(); - if (rootLinks.any((rootLink) => toUpdate == rootLink)) { - toUpdate = ""; - } + LinkModel model = LinkModel(occasionLink: link); + var occasionLink = link ?? RouterService.currentOccasionLink; + if (occasionLink.isEmpty) { + model = LinkModel.extractOccasionLink(Uri.base.toString()); } - if (!await RouterService.updateOccasionFromLink(toUpdate)) { + if (!await RouterService.updateOccasionFromLink(model)) { throw Exception("Cannot continue."); } @@ -37,17 +34,7 @@ class RightsService{ return true; } - static String extractOccasionLink(String url) { - // Use a regular expression to match the pattern after the hash sign (#) - final regex = RegExp(r'#\/([^\/]+)'); - final match = regex.firstMatch(url); - - if (match != null && match.groupCount > 0) { - return match.group(1)!; - } - return ''; - } static Future getIsAdmin() async { var data = await _supabase.rpc("get_is_admin_on_occasion", diff --git a/lib/dataServices/SynchroService.dart b/lib/dataServices/SynchroService.dart index ac5c4c20..6da0dfd0 100644 --- a/lib/dataServices/SynchroService.dart +++ b/lib/dataServices/SynchroService.dart @@ -65,10 +65,11 @@ class SynchroService { await DbEvents.synchronizeMySchedule(); } - static Future getAppConfig(String link) async { + static Future getAppConfig({String? occasionLink, String? formLink}) async { var data = await _supabase.rpc("get_app_config", params: {"data_in": { - "link": link, + "link": occasionLink, + "form_link": formLink, "organization": AppConfig.organization, "platform": await PlatformHelper.getPlatform() }}); diff --git a/lib/pages/Eshop/EshopColumns.dart b/lib/pages/Eshop/EshopColumns.dart index f17b4ecb..0bd7459e 100644 --- a/lib/pages/Eshop/EshopColumns.dart +++ b/lib/pages/Eshop/EshopColumns.dart @@ -56,7 +56,7 @@ class EshopColumns { title: "State".tr(), field: TbEshop.tickets.state, type: PlutoColumnType.select( - [TicketModel.orderedState, TicketModel.activeState, TicketModel.usedState, TicketModel.stornoState], + [TicketModel.orderedState, TicketModel.paidState, TicketModel.usedState, TicketModel.stornoState], ), width: 120, ), @@ -101,7 +101,7 @@ class EshopColumns { title: "State".tr(), field: TbEshop.orders.state, type: PlutoColumnType.select( - [OrderModel.orderedState, OrderModel.completedState, OrderModel.canceledState], + [OrderModel.orderedState, OrderModel.paidState, OrderModel.canceledState], ), width: 120, ), @@ -150,7 +150,7 @@ class EshopColumns { PlutoColumn( readOnly: true, enableEditingMode: false, - title: "Paid".tr(), + title: "Paid amount".tr(), field: TbEshop.payment_info.paid, type: PlutoColumnType.text(), textAlign: PlutoColumnTextAlign.end, diff --git a/lib/pages/LoginPage.dart b/lib/pages/LoginPage.dart index 6b690d2d..2d730864 100644 --- a/lib/pages/LoginPage.dart +++ b/lib/pages/LoginPage.dart @@ -4,6 +4,7 @@ import 'package:fstapp/dataServices/AuthService.dart'; import 'package:fstapp/pages/ForgotPasswordPage.dart'; import 'package:fstapp/pages/SignupPage.dart'; import 'package:fstapp/pages/SettingsPage.dart'; +import 'package:fstapp/services/LinkModel.dart'; import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/styles/StylesConfig.dart'; import 'package:fstapp/themeConfig.dart'; @@ -141,7 +142,7 @@ class _LoginPageState extends State { Future _refreshSignedInStatus(value) async { var loggedIn = await AuthService.tryAuthUser(); if (loggedIn) { - RouterService.updateOccasionFromLink(RouterService.currentOccasionLink); + RouterService.updateOccasionFromLink(LinkModel(occasionLink: RouterService.currentOccasionLink)); RouterService.popOrHome(context); } } diff --git a/lib/services/LinkModel.dart b/lib/services/LinkModel.dart new file mode 100644 index 00000000..e1cb3abc --- /dev/null +++ b/lib/services/LinkModel.dart @@ -0,0 +1,39 @@ +import 'package:fstapp/AppRouter.dart'; +import 'package:fstapp/pages/Eshop/FormPage.dart'; + +class LinkModel { + String? formLink; + String? occasionLink; + + LinkModel({this.occasionLink, this.formLink}); + + factory LinkModel.extractOccasionLink(String url) { + String? firstPart; + String? secondPart; + + // Use a regular expression with named groups to match the pattern after the hash sign (#) + final regex = RegExp(r'#\/(?[^\/]+)\/(?[^\/]+)'); + final match = regex.firstMatch(url); + + var rootLinks = AppRouter.getRootLinks(); + if (match != null) { + // Extract named groups + firstPart = match.namedGroup('firstPart'); + secondPart = match.namedGroup('secondPart'); + + if (firstPart == FormPage.ROUTE) { + secondPart = match.namedGroup('secondPart'); + } + + if (rootLinks.any((rootLink) => firstPart == rootLink)) { + firstPart = ""; + } + } + + return LinkModel( + occasionLink: firstPart, + formLink: secondPart, + ); + } + +} \ No newline at end of file diff --git a/lib/tests/DataServiceTests.dart b/lib/tests/DataServiceTests.dart index 6aab57b6..3ff5adbe 100644 --- a/lib/tests/DataServiceTests.dart +++ b/lib/tests/DataServiceTests.dart @@ -2,12 +2,13 @@ import 'package:fstapp/dataServices/AuthService.dart'; import 'package:fstapp/dataModels/OccasionUserModel.dart'; import 'package:fstapp/dataServices/DbUsers.dart'; import 'package:fstapp/dataServices/SynchroService.dart'; +import 'package:fstapp/services/LinkModel.dart'; class DataServiceTests { static Future test_check_occasion_link() async { await AuthService.login("bujnmi@gmail.com", ""); - var tstData = await SynchroService.getAppConfig("conference2024"); + var tstData = await SynchroService.getAppConfig(occasionLink: "conference2024"); } static Future test_has_event_allowed_role() async { diff --git a/scripts/database/functions/get_app_config.sql b/scripts/database/functions/get_app_config.sql index 3ca60952..b48551a0 100644 --- a/scripts/database/functions/get_app_config.sql +++ b/scripts/database/functions/get_app_config.sql @@ -5,6 +5,7 @@ AS $$ DECLARE org_id bigint := (data_in->>'organization')::bigint; link_txt text := data_in->>'link'; + form_link text := data_in->>'form_link'; platform_info jsonb := data_in->'platform'; platform_name text := platform_info->>'platform'; occasionId bigint; @@ -23,8 +24,37 @@ BEGIN platform_name := 'web'; END IF; - -- If no link is provided, get the default occasion from the organization - IF link_txt IS NULL OR link_txt = '' THEN + -- If form_link is provided, fetch the occasion from forms + IF form_link IS NOT NULL AND form_link <> '' THEN + SELECT forms.occasion, occasions.link INTO occasionId, occasion_link + FROM forms + JOIN occasions ON forms.occasion = occasions.id + WHERE forms.link = form_link + AND occasions.organization = org_id + AND occasions.is_hidden = false; + + -- If no occasion is found, return a 404 response + IF occasionId IS NULL THEN + RETURN json_build_object('code', 404, 'message', 'No occasion found for the provided form link'); + END IF; + + -- If no form_link but link_txt is provided + ELSIF link_txt IS NOT NULL AND link_txt <> '' THEN + -- Get the occasion ID and link for the provided link within the specified organization + SELECT id, link INTO occasionId, occasion_link + FROM occasions + WHERE link = link_txt + AND is_hidden = false + AND organization = org_id; + + -- If the occasion ID is not found, return a 404 response + IF occasionId IS NULL THEN + RETURN json_build_object('code', 404, 'message', 'Occasion not found'); + END IF; + + -- If no link or form_link is provided + ELSE + -- Get the default occasion from the organization SELECT data->>'DEFAULT_OCCASION' INTO occasionId FROM organizations WHERE id = org_id; @@ -48,18 +78,6 @@ BEGIN FROM occasions WHERE id = occasionId AND organization = org_id; END IF; - ELSE - -- Get the occasion ID and link for the provided link within the specified organization - SELECT id, link INTO occasionId, occasion_link - FROM occasions - WHERE link = link_txt - AND is_hidden = false - AND organization = org_id; - - -- If the occasion ID is not found, return a 404 response - IF occasionId IS NULL THEN - RETURN json_build_object('code', 404, 'message', 'Occasion not found'); - END IF; END IF; -- Retrieve version_recommended for the specific platform, or leave as NULL if not found From fa90d3c155fd3e74161f683f987a0429de29dfb6 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 07:54:56 +0100 Subject: [PATCH 133/159] table orders enhanced --- lib/dataModelsEshop/OrderModel.dart | 11 ++++++ lib/dataModelsEshop/TbEshop.dart | 3 +- lib/pages/Eshop/EshopColumns.dart | 61 +++++++++++++++++++++++++---- lib/pages/Eshop/OrdersTab.dart | 6 ++- 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/lib/dataModelsEshop/OrderModel.dart b/lib/dataModelsEshop/OrderModel.dart index c68dfdef..eca24a98 100644 --- a/lib/dataModelsEshop/OrderModel.dart +++ b/lib/dataModelsEshop/OrderModel.dart @@ -9,6 +9,7 @@ import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; import 'package:fstapp/dataModelsEshop/PaymentInfoModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/services/Utilities.dart'; +import 'package:intl/intl.dart'; import 'package:pluto_grid_plus/pluto_grid_plus.dart'; class OrderModel extends IPlutoRowModel { @@ -98,7 +99,16 @@ class OrderModel extends IPlutoRowModel { TbEshop.orders.state: PlutoCell(value: state ?? orderedState), TbEshop.payment_info.amount: PlutoCell(value: paymentInfoModel?.amount != null ? Utilities.formatPrice(context, paymentInfoModel!.amount!) : ""), TbEshop.payment_info.paid: PlutoCell(value: paymentInfoModel?.paid != null ? Utilities.formatPrice(context, paymentInfoModel!.paid!) : ""), + TbEshop.payment_info.variable_symbol: PlutoCell(value: paymentInfoModel?.variableSymbol ?? 0), + TbEshop.payment_info.deadline: PlutoCell( + value: paymentInfoModel?.deadline != null + ? DateFormat('yyyy-MM-dd').format(paymentInfoModel!.deadline!) + : "", + ), TbEshop.orders.data: PlutoCell(value: toCustomerData()), + TbEshop.orders.data_note: PlutoCell(value: toCustomerNote()), + TbEshop.orders.data_note_hidden: PlutoCell(value: toCustomerNoteHidden()), + TbEshop.payment_info.variable_symbol: PlutoCell(value: paymentInfoModel?.variableSymbol ?? ""), }); } @@ -118,4 +128,5 @@ class OrderModel extends IPlutoRowModel { String toCustomerData() => "${data?["name"]} ${data?["surname"]} (${data?["email"]})"; String toCustomerNote() => "${data?["note"] ?? ""}"; + String toCustomerNoteHidden() => "${data?["note_hidden"] ?? ""}"; } diff --git a/lib/dataModelsEshop/TbEshop.dart b/lib/dataModelsEshop/TbEshop.dart index 18836cab..c64c1c3a 100644 --- a/lib/dataModelsEshop/TbEshop.dart +++ b/lib/dataModelsEshop/TbEshop.dart @@ -60,7 +60,8 @@ class OrdersTb { String get form => "form"; String get payment_info => "payment_info"; String get currency_code => "currency_code"; - + String get data_note => "note"; + String get data_note_hidden => "note_hidden"; } class TicketsTb { diff --git a/lib/pages/Eshop/EshopColumns.dart b/lib/pages/Eshop/EshopColumns.dart index 0bd7459e..ab1bd954 100644 --- a/lib/pages/Eshop/EshopColumns.dart +++ b/lib/pages/Eshop/EshopColumns.dart @@ -22,8 +22,12 @@ class EshopColumns { static const String PAYMENT_INFO_AMOUNT = "paymentInfoAmount"; static const String PAYMENT_INFO_PAID = "paymentInfoPaid"; + static const String PAYMENT_INFO_VARIABLE_SYMBOL = "paymentInfoVariableSymbol"; + static const String PAYMENT_INFO_DEADLINE = "orderDataDeadline"; - static const String CUSTOMER_DATA = "customerData"; + static const String ORDER_DATA = "orderData"; + static const String ORDER_DATA_NOTE = "orderDataNote"; + static const String ORDER_DATA_NOTE_HIDDEN = "orderDataNoteHidden"; // Define columns static Map get columnBuilders => { @@ -103,9 +107,40 @@ class EshopColumns { type: PlutoColumnType.select( [OrderModel.orderedState, OrderModel.paidState, OrderModel.canceledState], ), + textAlign: PlutoColumnTextAlign.end, width: 120, ), ], + ORDER_DATA: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Customer".tr(), + field: TbEshop.orders.data, + type: PlutoColumnType.text(), + width: 250, + ), + ], + ORDER_DATA_NOTE: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Note".tr(), + field: TbEshop.orders.data_note, + type: PlutoColumnType.text(), + width: 200, + ), + ], + ORDER_DATA_NOTE_HIDDEN: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Hidden note".tr(), + field: TbEshop.orders.data_note_hidden, + type: PlutoColumnType.text(), + width: 200, + ), + ], PRODUCT_ID: [ PlutoColumn( hide: true, @@ -132,7 +167,7 @@ class EshopColumns { field: TbEshop.products.price, type: PlutoColumnType.text(), textAlign: PlutoColumnTextAlign.end, - width: 100, + width: 80, ), ], PAYMENT_INFO_AMOUNT: [ @@ -143,7 +178,7 @@ class EshopColumns { field: TbEshop.payment_info.amount, type: PlutoColumnType.text(), textAlign: PlutoColumnTextAlign.end, - width: 120, + width: 80, ), ], PAYMENT_INFO_PAID: [ @@ -157,14 +192,26 @@ class EshopColumns { width: 120, ), ], - CUSTOMER_DATA: [ + PAYMENT_INFO_VARIABLE_SYMBOL: [ PlutoColumn( readOnly: true, enableEditingMode: false, - title: "Customer".tr(), - field: TbEshop.orders.data, + title: "Variable symbol".tr(), + field: TbEshop.payment_info.variable_symbol, type: PlutoColumnType.text(), - width: 300, + textAlign: PlutoColumnTextAlign.end, + width: 80, + ), + ], + PAYMENT_INFO_DEADLINE: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Deadline".tr(), + field: TbEshop.payment_info.deadline, + type: PlutoColumnType.text(), + textAlign: PlutoColumnTextAlign.end, + width: 100, ), ], }; diff --git a/lib/pages/Eshop/OrdersTab.dart b/lib/pages/Eshop/OrdersTab.dart index 5bd1353c..bd23e0ae 100644 --- a/lib/pages/Eshop/OrdersTab.dart +++ b/lib/pages/Eshop/OrdersTab.dart @@ -26,10 +26,14 @@ class _OrdersTabState extends State { static const List columnIdentifiers = [ EshopColumns.ORDER_ID, - EshopColumns.CUSTOMER_DATA, + EshopColumns.ORDER_DATA, EshopColumns.ORDER_STATE, EshopColumns.ORDER_PRICE, EshopColumns.PAYMENT_INFO_PAID, + EshopColumns.PAYMENT_INFO_VARIABLE_SYMBOL, + EshopColumns.ORDER_DATA_NOTE, + EshopColumns.ORDER_DATA_NOTE_HIDDEN, + EshopColumns.PAYMENT_INFO_DEADLINE, ]; @override From 8b74ee55cdc8ab503d2ccb97007d23f8dc0ed672 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:04:28 +0100 Subject: [PATCH 134/159] tickets datagrid --- lib/components/dataGrid/AdminPageHelper.dart | 3 +- lib/dataModelsEshop/OrderModel.dart | 1 + lib/dataModelsEshop/TbEshop.dart | 2 + lib/dataModelsEshop/TicketModel.dart | 82 ++++++++++++++++---- lib/dataServices/DbEshop.dart | 12 ++- lib/pages/Eshop/EshopColumns.dart | 49 +++++++++++- lib/pages/Eshop/FormEditPage.dart | 1 + lib/pages/Eshop/OrdersTab.dart | 6 ++ lib/pages/Eshop/TicketsTab.dart | 56 +++++++++++++ scripts/database/eshop/get_orders.sql | 1 + 10 files changed, 194 insertions(+), 19 deletions(-) create mode 100644 lib/pages/Eshop/TicketsTab.dart diff --git a/lib/components/dataGrid/AdminPageHelper.dart b/lib/components/dataGrid/AdminPageHelper.dart index 7644e516..6e6a2f0b 100644 --- a/lib/components/dataGrid/AdminPageHelper.dart +++ b/lib/components/dataGrid/AdminPageHelper.dart @@ -11,6 +11,7 @@ import 'package:fstapp/pages/AdministrationOccasion/UserGroupsTab.dart'; import 'package:fstapp/pages/AdministrationOccasion/UsersTab.dart'; import 'package:fstapp/pages/Eshop/BlueprintEditorTab.dart'; import 'package:fstapp/pages/Eshop/OrdersTab.dart'; +import 'package:fstapp/pages/Eshop/TicketsTab.dart'; class AdminPageHelper { static PreferredSizeWidget buildAdminAppBar( @@ -83,7 +84,7 @@ class AdminTabDefinition { form: AdminTabDefinition(label: "Form".tr(), icon: Icons.list, widget: FormEditorTab()), blueprint: AdminTabDefinition(label: "Blueprint".tr(), icon: Icons.grid_on, widget: BlueprintTab()), - tickets: AdminTabDefinition(label: "Tickets".tr(), icon: Icons.local_activity, widget: GameTab()), + tickets: AdminTabDefinition(label: "Tickets".tr(), icon: Icons.local_activity, widget: TicketsTab()), orders: AdminTabDefinition(label: "Orders".tr(), icon: Icons.shopping_cart, widget: OrdersTab()), }; diff --git a/lib/dataModelsEshop/OrderModel.dart b/lib/dataModelsEshop/OrderModel.dart index eca24a98..1508b9c3 100644 --- a/lib/dataModelsEshop/OrderModel.dart +++ b/lib/dataModelsEshop/OrderModel.dart @@ -95,6 +95,7 @@ class OrderModel extends IPlutoRowModel { PlutoRow toPlutoRow(BuildContext context) { return PlutoRow(cells: { TbEshop.orders.id: PlutoCell(value: id ?? 0), + TbEshop.orders.order_symbol: PlutoCell(value: id ?? 0), TbEshop.orders.price: PlutoCell(value: price != null ? Utilities.formatPrice(context, price!) : ""), TbEshop.orders.state: PlutoCell(value: state ?? orderedState), TbEshop.payment_info.amount: PlutoCell(value: paymentInfoModel?.amount != null ? Utilities.formatPrice(context, paymentInfoModel!.amount!) : ""), diff --git a/lib/dataModelsEshop/TbEshop.dart b/lib/dataModelsEshop/TbEshop.dart index c64c1c3a..11a88852 100644 --- a/lib/dataModelsEshop/TbEshop.dart +++ b/lib/dataModelsEshop/TbEshop.dart @@ -58,6 +58,7 @@ class OrdersTb { String get data => "data"; String get occasion => "occasion"; String get form => "form"; + String get order_symbol => "order_symbol"; String get payment_info => "payment_info"; String get currency_code => "currency_code"; String get data_note => "note"; @@ -74,6 +75,7 @@ class TicketsTb { String get state => "state"; String get occasion => "occasion"; String get note => "note"; + String get note_hidden => "hidden_note"; } class BlueprintTb { diff --git a/lib/dataModelsEshop/TicketModel.dart b/lib/dataModelsEshop/TicketModel.dart index 8999e916..be94392e 100644 --- a/lib/dataModelsEshop/TicketModel.dart +++ b/lib/dataModelsEshop/TicketModel.dart @@ -1,9 +1,13 @@ +import 'package:flutter/cupertino.dart'; +import 'package:fstapp/components/dataGrid/PlutoAbstract.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:pluto_grid_plus/pluto_grid_plus.dart'; +import 'OrderModel.dart'; import 'BlueprintObjectModel.dart'; import 'ProductModel.dart'; -import 'OrderModel.dart'; +import 'package:intl/intl.dart'; -class TicketModel { +class TicketModel extends IPlutoRowModel { int? id; DateTime? createdAt; DateTime? updatedAt; @@ -11,6 +15,7 @@ class TicketModel { String? state; int? occasion; String? note; + String? noteHidden; // Relating spots and products directly to the ticket List? relatedSpots; @@ -24,6 +29,23 @@ class TicketModel { static const String usedState = "used"; static const String stornoState = "storno"; + static const String metaRelatedOrder = "related_order"; + static const String metaTicketsProducts = "ticket_products"; + + TicketModel({ + this.id, + this.createdAt, + this.updatedAt, + this.ticketSymbol, + this.state, + this.occasion, + this.note, + this.noteHidden, + this.relatedSpots, + this.relatedProducts, + this.relatedOrder, + }); + factory TicketModel.fromJson(Map json) { return TicketModel( id: json[TbEshop.tickets.id], @@ -50,18 +72,48 @@ class TicketModel { TbEshop.tickets.note: note, }; - String toBasicString() => ticketSymbol ?? id.toString(); + @override + PlutoRow toPlutoRow(BuildContext context) { + return PlutoRow(cells: { + TbEshop.tickets.id: PlutoCell(value: id ?? 0), + TbEshop.tickets.created_at: PlutoCell( + value: createdAt != null + ? DateFormat('yyyy-MM-dd').format(createdAt!) + : ""), + TbEshop.tickets.ticket_symbol: PlutoCell(value: ticketSymbol ?? ""), + TbEshop.tickets.state: PlutoCell(value: state ?? orderedState), + TbEshop.tickets.note: PlutoCell(value: note ?? ""), + TbEshop.tickets.note_hidden: PlutoCell(value: noteHidden ?? ""), + TbEshop.orders.order_symbol: PlutoCell( + value: relatedOrder != null + ? relatedOrder!.toBasicString() + : ""), + TbEshop.orders.data: PlutoCell( + value: relatedOrder != null + ? relatedOrder!.toCustomerData() + : ""), + metaTicketsProducts: PlutoCell( + value: relatedProducts != null + ? relatedProducts!.map((p)=>p.toBasicString()).join(" | ") + : ""), + }); + } - TicketModel({ - this.id, - this.createdAt, - this.updatedAt, - this.ticketSymbol, - this.state, - this.occasion, - this.note, - this.relatedSpots, - this.relatedProducts, - this.relatedOrder, - }); + static TicketModel fromPlutoJson(Map json) { + return TicketModel( + id: json[TbEshop.tickets.id] == -1 ? null : json[TbEshop.tickets.id]); + } + + @override + Future deleteMethod() async { + // Implement your delete logic here + } + + @override + Future updateMethod() async { + // Implement your update logic here + } + + @override + String toBasicString() => ticketSymbol ?? id.toString(); } diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 85e60629..b2bfb3e7 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -228,10 +228,18 @@ class DbEshop { order.relatedProducts = relatedProducts; order.paymentInfoModel = relatedPaymentInfo; } - - return orders; + return orders.sortedBy((ou)=>ou.createdAt!).reversed.toList(); } + static Future> getAllTickets(String formKey) async { + var orders = await getAllOrders(formKey); + List toReturn = []; + for(var o in orders){ + toReturn.addAll(o.relatedTickets??[]); + } + toReturn = toReturn.sortedBy((ou)=>ou.createdAt!).reversed.toList(); + return toReturn; + } static Future deleteOrder(OrderModel model) async { } diff --git a/lib/pages/Eshop/EshopColumns.dart b/lib/pages/Eshop/EshopColumns.dart index ab1bd954..402b9f4d 100644 --- a/lib/pages/Eshop/EshopColumns.dart +++ b/lib/pages/Eshop/EshopColumns.dart @@ -11,6 +11,9 @@ class EshopColumns { static const String TICKET_SYMBOL = "ticketSymbol"; static const String TICKET_STATE = "ticketState"; static const String TICKET_NOTE = "ticketNote"; + static const String TICKET_NOTE_HIDDEN = "ticketNoteHidden"; + static const String TICKET_PRODUCTS = "ticketProducts"; + static const String TICKET_CREATED_AT = "ticketCreatedAt"; static const String ORDER_ID = "orderId"; static const String ORDER_PRICE = "orderPrice"; @@ -25,6 +28,7 @@ class EshopColumns { static const String PAYMENT_INFO_VARIABLE_SYMBOL = "paymentInfoVariableSymbol"; static const String PAYMENT_INFO_DEADLINE = "orderDataDeadline"; + static const String ORDER_SYMBOL = "orderSymbol"; static const String ORDER_DATA = "orderData"; static const String ORDER_DATA_NOTE = "orderDataNote"; static const String ORDER_DATA_NOTE_HIDDEN = "orderDataNoteHidden"; @@ -50,7 +54,7 @@ class EshopColumns { title: "Ticket Symbol".tr(), field: TbEshop.tickets.ticket_symbol, type: PlutoColumnType.text(), - width: 150, + width: 120, ), ], TICKET_STATE: [ @@ -65,6 +69,29 @@ class EshopColumns { width: 120, ), ], + TICKET_CREATED_AT: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Created".tr(), + field: TbEshop.tickets.created_at, + type: PlutoColumnType.text(), + textAlign: PlutoColumnTextAlign.end, + width: 100, + ), + ], + TICKET_PRODUCTS: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Products".tr(), + field: TicketModel.metaTicketsProducts, + type: PlutoColumnType.select( + [TicketModel.orderedState, TicketModel.paidState, TicketModel.usedState, TicketModel.stornoState], + ), + width: 300, + ), + ], TICKET_NOTE: [ PlutoColumn( readOnly: true, @@ -75,6 +102,16 @@ class EshopColumns { width: 200, ), ], + TICKET_NOTE_HIDDEN: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Hidden note".tr(), + field: TbEshop.tickets.note_hidden, + type: PlutoColumnType.text(), + width: 200, + ), + ], ORDER_ID: [ PlutoColumn( hide: true, @@ -87,6 +124,16 @@ class EshopColumns { renderer: (rendererContext) => DataGridHelper.idRenderer(rendererContext), ), ], + ORDER_SYMBOL: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Order Symbol".tr(), + field: TbEshop.orders.order_symbol, + type: PlutoColumnType.text(), + width: 120, + ), + ], ORDER_PRICE: [ PlutoColumn( readOnly: true, diff --git a/lib/pages/Eshop/FormEditPage.dart b/lib/pages/Eshop/FormEditPage.dart index 896c6d00..2fe1b3da 100644 --- a/lib/pages/Eshop/FormEditPage.dart +++ b/lib/pages/Eshop/FormEditPage.dart @@ -22,6 +22,7 @@ class _FormEditPageState extends State with SingleTickerProviderSt AdminTabDefinition.form, AdminTabDefinition.blueprint, AdminTabDefinition.orders, + AdminTabDefinition.tickets, ]; @override diff --git a/lib/pages/Eshop/OrdersTab.dart b/lib/pages/Eshop/OrdersTab.dart index bd23e0ae..7ffb2b26 100644 --- a/lib/pages/Eshop/OrdersTab.dart +++ b/lib/pages/Eshop/OrdersTab.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:fstapp/components/dataGrid/DataGridAction.dart'; import 'package:fstapp/components/dataGrid/SingleTableDataGrid.dart'; import 'package:fstapp/dataModelsEshop/OrderModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/pages/Eshop/EshopColumns.dart'; import 'package:auto_route/auto_route.dart'; @@ -26,6 +28,7 @@ class _OrdersTabState extends State { static const List columnIdentifiers = [ EshopColumns.ORDER_ID, + EshopColumns.ORDER_SYMBOL, EshopColumns.ORDER_DATA, EshopColumns.ORDER_STATE, EshopColumns.ORDER_PRICE, @@ -44,6 +47,9 @@ class _OrdersTabState extends State { OrderModel.fromPlutoJson, DataGridFirstColumn.none, TbEshop.orders.id, + actionsExtended: DataGridActionsController( + areAllActionsEnabled: RightsService.canUpdateUsers, + isAddActionPossible: () => false), columns: EshopColumns.generateColumns(columnIdentifiers), ).DataGrid(); } diff --git a/lib/pages/Eshop/TicketsTab.dart b/lib/pages/Eshop/TicketsTab.dart new file mode 100644 index 00000000..268920af --- /dev/null +++ b/lib/pages/Eshop/TicketsTab.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:fstapp/components/dataGrid/DataGridAction.dart'; +import 'package:fstapp/components/dataGrid/SingleTableDataGrid.dart'; +import 'package:fstapp/dataModelsEshop/OrderModel.dart'; +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/dataModelsEshop/TicketModel.dart'; +import 'package:fstapp/dataServices/DbEshop.dart'; +import 'package:fstapp/dataServices/RightsService.dart'; +import 'package:fstapp/pages/Eshop/EshopColumns.dart'; +import 'package:auto_route/auto_route.dart'; + +class TicketsTab extends StatefulWidget { + const TicketsTab({super.key}); + + @override + _TicketsTabState createState() => _TicketsTabState(); +} + +class _TicketsTabState extends State { + String? formKey; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (formKey == null && context.routeData.pathParams.isNotEmpty) { + formKey = context.routeData.pathParams.getString("formKey"); + } + } + + static const List columnIdentifiers = [ + EshopColumns.TICKET_ID, + EshopColumns.ORDER_SYMBOL, + EshopColumns.ORDER_DATA, + EshopColumns.TICKET_SYMBOL, + EshopColumns.TICKET_CREATED_AT, + EshopColumns.TICKET_STATE, + EshopColumns.TICKET_PRODUCTS, + EshopColumns.TICKET_NOTE, + EshopColumns.TICKET_NOTE_HIDDEN, + ]; + + @override + Widget build(BuildContext context) { + return SingleTableDataGrid( + context, + () => DbEshop.getAllTickets(formKey!), + TicketModel.fromPlutoJson, + DataGridFirstColumn.none, + TbEshop.tickets.id, + actionsExtended: DataGridActionsController( + areAllActionsEnabled: RightsService.canUpdateUsers, + isAddActionPossible: () => false), + columns: EshopColumns.generateColumns(columnIdentifiers), + ).DataGrid(); + } +} \ No newline at end of file diff --git a/scripts/database/eshop/get_orders.sql b/scripts/database/eshop/get_orders.sql index 2b99f434..d9eba7a7 100644 --- a/scripts/database/eshop/get_orders.sql +++ b/scripts/database/eshop/get_orders.sql @@ -70,6 +70,7 @@ BEGIN -- Fetch tickets linked to the occasion SELECT jsonb_agg(jsonb_build_object( 'id', t.id, + 'created_at', t.created_at, 'ticket_symbol', t.ticket_symbol, 'state', t.state, 'note', t.note From 19087b512654a5234d92a9cceb715057554721a4 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:44:46 +0100 Subject: [PATCH 135/159] adjust --- lib/dataModelsEshop/BlueprintModel.dart | 1 - lib/dataModelsEshop/BlueprintObjectModel.dart | 8 ++++++-- lib/pages/Eshop/BlueprintEditorTab.dart | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/dataModelsEshop/BlueprintModel.dart b/lib/dataModelsEshop/BlueprintModel.dart index 2d542912..3eeec1af 100644 --- a/lib/dataModelsEshop/BlueprintModel.dart +++ b/lib/dataModelsEshop/BlueprintModel.dart @@ -1,4 +1,3 @@ -import 'package:fstapp/components/seatReservation/utils/SeatState.dart'; import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; import 'package:fstapp/dataModelsEshop/BlueprintHelper.dart'; import 'package:fstapp/dataModelsEshop/OrderModel.dart'; diff --git a/lib/dataModelsEshop/BlueprintObjectModel.dart b/lib/dataModelsEshop/BlueprintObjectModel.dart index a4f7932a..07320cdc 100644 --- a/lib/dataModelsEshop/BlueprintObjectModel.dart +++ b/lib/dataModelsEshop/BlueprintObjectModel.dart @@ -121,12 +121,16 @@ class BlueprintObjectModel { // Get the order details var order = blueprint?.orders?.firstWhere((p) => p.id == opt.orderId); + var orderNote = order?.toCustomerNote() ?? ""; + if(orderNote.isNotEmpty){ + orderNote = "\n$orderNote"; + } var orderString = order != null - ? "\n${order.toCustomerData()}\n${order.toCustomerNote()}" + ? "\n${order.toCustomerData()}$orderNote" : ""; // Add ticket note if available - var ticketNoteString = ticket.note != null ? "\n${ticket.note}" : ""; + var ticketNoteString = ticket.note != null && ticket.note!.isNotEmpty ? "\n${ticket.note}" : ""; return "${product?.title} ${title ?? ""}\n${"Ticket".tr()} ${ticket.ticketSymbol}$ticketNoteString\n$productsString$orderString"; } diff --git a/lib/pages/Eshop/BlueprintEditorTab.dart b/lib/pages/Eshop/BlueprintEditorTab.dart index eac5bcf2..13c4f719 100644 --- a/lib/pages/Eshop/BlueprintEditorTab.dart +++ b/lib/pages/Eshop/BlueprintEditorTab.dart @@ -440,7 +440,7 @@ class _BlueprintTabState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text("Import background"), + Text("Import background").tr(), const SizedBox(width: 8), // Optional spacing between text and icon const Icon(Icons.grid_on), ], From 072185812beac6aad15e3e805436d7175ed65457 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:38:20 +0100 Subject: [PATCH 136/159] create_ticket_order.sql correction --- lib/dataModelsEshop/TicketModel.dart | 8 ++------ scripts/database/eshop/create_ticket_order.sql | 15 ++++++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/dataModelsEshop/TicketModel.dart b/lib/dataModelsEshop/TicketModel.dart index be94392e..84d34ba3 100644 --- a/lib/dataModelsEshop/TicketModel.dart +++ b/lib/dataModelsEshop/TicketModel.dart @@ -59,17 +59,13 @@ class TicketModel extends IPlutoRowModel { state: json[TbEshop.tickets.state], occasion: json[TbEshop.tickets.occasion], note: json[TbEshop.tickets.note], + noteHidden: json[TbEshop.tickets.note_hidden], ); } Map toJson() => { - TbEshop.tickets.id: id, - TbEshop.tickets.created_at: createdAt?.toIso8601String(), - TbEshop.tickets.updated_at: updatedAt?.toIso8601String(), - TbEshop.tickets.ticket_symbol: ticketSymbol, TbEshop.tickets.state: state, - TbEshop.tickets.occasion: occasion, - TbEshop.tickets.note: note, + TbEshop.tickets.note_hidden: noteHidden, }; @override diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index c8f338a3..47fa7e97 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -83,10 +83,10 @@ BEGIN END IF; -- Create the order before processing tickets - INSERT INTO eshop.orders (created_at, updated_at, price, state, data, occasion) + INSERT INTO eshop.orders (created_at, updated_at, occasion) VALUES ( - now, now, 0, 'pending', - input_data, -- Store all input_data directly + now, + now, occasion_id ) RETURNING id INTO order_id; @@ -223,11 +223,16 @@ BEGIN VALUES (bank_account_id, generated_variable_symbol, calculated_price, now, deadline) RETURNING id INTO payment_info_id; - -- Update the order with the calculated price and payment info + -- Update the order with the calculated price, payment info, and tickets UPDATE eshop.orders - SET price = calculated_price, state = 'ordered', payment_info = payment_info_id + SET + price = calculated_price, + state = 'ordered', + payment_info = payment_info_id, + data = input_data - 'ticket' || JSONB_BUILD_OBJECT('tickets', ticket_details) WHERE id = order_id; + -- Log order to orders_history with price and tickets INSERT INTO eshop.orders_history (created_at, data, "order", state, price) VALUES ( From b731fd6643d988e13aec1d0ff87074e267a93f71 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 19:12:56 +0100 Subject: [PATCH 137/159] check datagrid type --- .../dataGrid/SingleTableDataGrid.dart | 46 ++++++++++--------- lib/pages/Eshop/OrdersTab.dart | 2 +- lib/pages/Eshop/TicketsTab.dart | 2 +- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/components/dataGrid/SingleTableDataGrid.dart b/lib/components/dataGrid/SingleTableDataGrid.dart index 95b71e39..2ce4ae57 100644 --- a/lib/components/dataGrid/SingleTableDataGrid.dart +++ b/lib/components/dataGrid/SingleTableDataGrid.dart @@ -9,7 +9,7 @@ import 'PlutoAbstract.dart'; import 'AdministrationHeader.dart'; enum DataGridFirstColumn{ - none, delete, deleteAndDuplicate, deleteAndCheck + none, delete, deleteAndDuplicate, deleteAndCheck, check } class SingleTableDataGrid { @@ -138,30 +138,32 @@ class SingleTableDataGrid { enableDropToResize: false, enableColumnDrag: false, enableContextMenu: false, - enableRowChecked: firstColumnType == DataGridFirstColumn.deleteAndCheck, + enableRowChecked: firstColumnType == DataGridFirstColumn.deleteAndCheck || firstColumnType == DataGridFirstColumn.check, cellPadding: EdgeInsets.zero, - width: firstColumnType == DataGridFirstColumn.delete ? 50 : 100, + width: firstColumnType == DataGridFirstColumn.delete || firstColumnType == DataGridFirstColumn.check ? 50 : 100, renderer: (rendererContext) { List rowChildren = []; - rowChildren.add(IconButton( - onPressed: () async{ - var row = rendererContext.row; - if(deletedRows.contains(row)) - { - deletedRows.remove(row); - } - else if (newRows.contains(row)) - { - newRows.remove(row); - rendererContext.stateManager.removeRows([rendererContext.row]); - } - else{ - deletedRows.add(row); - } - row.setState(PlutoRowState.updated); - rendererContext.stateManager.notifyListeners(); - }, - icon: const Icon(Icons.delete_forever))); + if(firstColumnType != DataGridFirstColumn.check){ + rowChildren.add(IconButton( + onPressed: () async{ + var row = rendererContext.row; + if(deletedRows.contains(row)) + { + deletedRows.remove(row); + } + else if (newRows.contains(row)) + { + newRows.remove(row); + rendererContext.stateManager.removeRows([rendererContext.row]); + } + else{ + deletedRows.add(row); + } + row.setState(PlutoRowState.updated); + rendererContext.stateManager.notifyListeners(); + }, + icon: const Icon(Icons.delete_forever))); + } if(firstColumnType == DataGridFirstColumn.deleteAndDuplicate) { rowChildren.add(IconButton( diff --git a/lib/pages/Eshop/OrdersTab.dart b/lib/pages/Eshop/OrdersTab.dart index 7ffb2b26..2105aae6 100644 --- a/lib/pages/Eshop/OrdersTab.dart +++ b/lib/pages/Eshop/OrdersTab.dart @@ -45,7 +45,7 @@ class _OrdersTabState extends State { context, () => DbEshop.getAllOrders(formKey!), OrderModel.fromPlutoJson, - DataGridFirstColumn.none, + DataGridFirstColumn.check, TbEshop.orders.id, actionsExtended: DataGridActionsController( areAllActionsEnabled: RightsService.canUpdateUsers, diff --git a/lib/pages/Eshop/TicketsTab.dart b/lib/pages/Eshop/TicketsTab.dart index 268920af..33502e68 100644 --- a/lib/pages/Eshop/TicketsTab.dart +++ b/lib/pages/Eshop/TicketsTab.dart @@ -45,7 +45,7 @@ class _TicketsTabState extends State { context, () => DbEshop.getAllTickets(formKey!), TicketModel.fromPlutoJson, - DataGridFirstColumn.none, + DataGridFirstColumn.check, TbEshop.tickets.id, actionsExtended: DataGridActionsController( areAllActionsEnabled: RightsService.canUpdateUsers, From cb58fc3d94298cf43c749d25196322d66ffc2eee Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 19:29:03 +0100 Subject: [PATCH 138/159] storno basic --- scripts/database/eshop/storno_ticket.sql | 75 ++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 scripts/database/eshop/storno_ticket.sql diff --git a/scripts/database/eshop/storno_ticket.sql b/scripts/database/eshop/storno_ticket.sql new file mode 100644 index 00000000..c069c95f --- /dev/null +++ b/scripts/database/eshop/storno_ticket.sql @@ -0,0 +1,75 @@ +CREATE OR REPLACE FUNCTION storno_ticket(ticket_id BIGINT) +RETURNS JSONB +LANGUAGE plpgsql +AS $$ +DECLARE + ticket_record RECORD; + order_id BIGINT; + ticket_price NUMERIC(10, 2) := 0; + updated_tickets JSONB := '[]'::JSONB; + original_data JSONB; +BEGIN + -- Check if the ticket exists and fetch its associated data + SELECT t.*, o.id AS order_id, o.data AS order_data + INTO ticket_record + FROM eshop.tickets t + JOIN eshop.order_product_ticket opt ON opt.ticket = t.id + JOIN eshop.orders o ON o.id = opt."order" + WHERE t.id = ticket_id; + + IF NOT FOUND THEN + RETURN JSONB_BUILD_OBJECT('code', 1001, 'message', 'Ticket not found'); + END IF; + + -- Check permissions (replace `isEditor` logic with your implementation) + IF NOT is_editor_function() THEN + RETURN JSONB_BUILD_OBJECT('code', 1002, 'message', 'Permission denied'); + END IF; + + -- Update ticket state to "storno" + UPDATE eshop.tickets + SET state = 'storno', updated_at = NOW() + WHERE id = ticket_id; + + -- Nullify the relevant spot's order_product_ticket + UPDATE eshop.spots + SET order_product_ticket = NULL, updated_at = NOW() + WHERE order_product_ticket IN ( + SELECT id FROM eshop.order_product_ticket WHERE ticket = ticket_id + ); + + -- Remove the ticket from orders.data and calculate the price reduction + original_data := ticket_record.order_data; + FOR ticket IN SELECT * FROM JSONB_ARRAY_ELEMENTS(original_data->'tickets') LOOP + IF (ticket->>'ticket_id')::BIGINT = ticket_id THEN + -- Calculate the price of all products in the ticket + FOR product IN SELECT * FROM JSONB_ARRAY_ELEMENTS(ticket->'products') LOOP + ticket_price := ticket_price + COALESCE((product->>'price')::NUMERIC, 0); + END LOOP; + ELSE + -- Keep all other tickets + updated_tickets := updated_tickets || ticket; + END IF; + END LOOP; + + -- Update the order's data and price + UPDATE eshop.orders + SET + data = original_data || JSONB_BUILD_OBJECT('tickets', updated_tickets), + price = COALESCE(price, 0) - ticket_price, + updated_at = NOW() + WHERE id = ticket_record.order_id; + + -- Return success response + RETURN JSONB_BUILD_OBJECT( + 'code', 200, + 'message', 'Ticket successfully cancelled', + 'ticket_id', ticket_id, + 'order_id', ticket_record.order_id, + 'price_reduction', ticket_price + ); +EXCEPTION + WHEN OTHERS THEN + RETURN JSONB_BUILD_OBJECT('code', 500, 'message', 'An error occurred', 'details', SQLERRM); +END; +$$; From 83428b717a8bae87286367fdec4adfc9d2774ca4 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:22:24 +0100 Subject: [PATCH 139/159] storno adjust --- scripts/database/eshop/storno_ticket.sql | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/scripts/database/eshop/storno_ticket.sql b/scripts/database/eshop/storno_ticket.sql index c069c95f..e8d08422 100644 --- a/scripts/database/eshop/storno_ticket.sql +++ b/scripts/database/eshop/storno_ticket.sql @@ -1,6 +1,7 @@ CREATE OR REPLACE FUNCTION storno_ticket(ticket_id BIGINT) RETURNS JSONB LANGUAGE plpgsql +SECURITY DEFINER AS $$ DECLARE ticket_record RECORD; @@ -8,9 +9,11 @@ DECLARE ticket_price NUMERIC(10, 2) := 0; updated_tickets JSONB := '[]'::JSONB; original_data JSONB; + ticket JSONB; -- Declare ticket as JSONB + product JSONB; -- Declare product as JSONB BEGIN -- Check if the ticket exists and fetch its associated data - SELECT t.*, o.id AS order_id, o.data AS order_data + SELECT t.*, o.id AS order_id, o.data AS order_data, t.occasion INTO ticket_record FROM eshop.tickets t JOIN eshop.order_product_ticket opt ON opt.ticket = t.id @@ -21,8 +24,8 @@ BEGIN RETURN JSONB_BUILD_OBJECT('code', 1001, 'message', 'Ticket not found'); END IF; - -- Check permissions (replace `isEditor` logic with your implementation) - IF NOT is_editor_function() THEN + -- Check permissions using get_is_editor_on_occasion + IF NOT get_is_editor_on_occasion(ticket_record.occasion) THEN RETURN JSONB_BUILD_OBJECT('code', 1002, 'message', 'Permission denied'); END IF; @@ -38,12 +41,16 @@ BEGIN SELECT id FROM eshop.order_product_ticket WHERE ticket = ticket_id ); + -- Remove all order_product_ticket records associated with the ticket + DELETE FROM eshop.order_product_ticket + WHERE ticket = ticket_id; + -- Remove the ticket from orders.data and calculate the price reduction original_data := ticket_record.order_data; - FOR ticket IN SELECT * FROM JSONB_ARRAY_ELEMENTS(original_data->'tickets') LOOP + FOR ticket IN SELECT JSONB_ARRAY_ELEMENTS(original_data->'tickets') LOOP IF (ticket->>'ticket_id')::BIGINT = ticket_id THEN -- Calculate the price of all products in the ticket - FOR product IN SELECT * FROM JSONB_ARRAY_ELEMENTS(ticket->'products') LOOP + FOR product IN SELECT JSONB_ARRAY_ELEMENTS(ticket->'products') LOOP ticket_price := ticket_price + COALESCE((product->>'price')::NUMERIC, 0); END LOOP; ELSE From 2881dcbf5f8ca9135afd30a62f23abd338728585 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:30:34 +0100 Subject: [PATCH 140/159] ticket storno --- lib/dataServices/DbEshop.dart | 14 ++++++ lib/pages/Eshop/TicketsTab.dart | 63 ++++++++++++++++++++++-- scripts/database/eshop/storno_ticket.sql | 34 +++++++++---- 3 files changed, 96 insertions(+), 15 deletions(-) diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index b2bfb3e7..9914f6f1 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -107,6 +107,20 @@ class DbEshop { return b; } + static Future stornoTicket(int ticketId) async { + final response = await _supabase.rpc( + 'storno_ticket', + params: { + 'ticket_id': ticketId, + }, + ); + + if (response["code"] != 200) { + return false; + } + return true; + } + static Future getBlueprintForEdit(String formKey) async { final response = await _supabase.rpc( diff --git a/lib/pages/Eshop/TicketsTab.dart b/lib/pages/Eshop/TicketsTab.dart index 33502e68..d5714383 100644 --- a/lib/pages/Eshop/TicketsTab.dart +++ b/lib/pages/Eshop/TicketsTab.dart @@ -1,13 +1,15 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fstapp/components/dataGrid/DataGridAction.dart'; import 'package:fstapp/components/dataGrid/SingleTableDataGrid.dart'; -import 'package:fstapp/dataModelsEshop/OrderModel.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; import 'package:fstapp/dataModelsEshop/TicketModel.dart'; import 'package:fstapp/dataServices/DbEshop.dart'; import 'package:fstapp/dataServices/RightsService.dart'; import 'package:fstapp/pages/Eshop/EshopColumns.dart'; import 'package:auto_route/auto_route.dart'; +import 'package:fstapp/services/DialogHelper.dart'; +import 'package:fstapp/services/ToastHelper.dart'; class TicketsTab extends StatefulWidget { const TicketsTab({super.key}); @@ -43,14 +45,65 @@ class _TicketsTabState extends State { Widget build(BuildContext context) { return SingleTableDataGrid( context, - () => DbEshop.getAllTickets(formKey!), + () => DbEshop.getAllTickets(formKey!), TicketModel.fromPlutoJson, DataGridFirstColumn.check, TbEshop.tickets.id, actionsExtended: DataGridActionsController( - areAllActionsEnabled: RightsService.canUpdateUsers, - isAddActionPossible: () => false), + areAllActionsEnabled: RightsService.canUpdateUsers, + isAddActionPossible: () => false, + ), + headerChildren: [ + DataGridAction( + name: "Storno".tr(), + action: (SingleTableDataGrid dataGrid, [_]) => _stornoTickets(dataGrid), + isEnabled: RightsService.isEditor, + ), + ], columns: EshopColumns.generateColumns(columnIdentifiers), ).DataGrid(); } -} \ No newline at end of file + + Future _stornoTickets(SingleTableDataGrid dataGrid) async { + var selectedTickets = _getCheckedTickets(dataGrid); + + if (selectedTickets.isEmpty) { + return; + } + + var confirm = await DialogHelper.showConfirmationDialogAsync( + context, + "Storno".tr(), + "Are you sure you want to storno the selected tickets?".tr(), + ); + + if (confirm) { + var stornoFutures = selectedTickets.map((ticket) { + return () async { + if(await DbEshop.stornoTicket(ticket.id!)) { + ToastHelper.Show(context, "Storno completed for {ticket}.".tr(namedArgs: { + "ticket": ticket.ticketSymbol ?? ticket.id.toString() + })); + } else{ + throw Exception("Ticket storno has failed."); + } + }; + }).toList(); + + await DialogHelper.showProgressDialogAsync( + context, + "Processing".tr(), + stornoFutures.length, + futures: stornoFutures, + ); + } + } + + List _getCheckedTickets(SingleTableDataGrid dataGrid) { + return List.from( + dataGrid.stateManager.refRows.originalList + .where((row) => row.checked == true) + .map((row) => TicketModel.fromPlutoJson(row.toJson())), + ); + } +} diff --git a/scripts/database/eshop/storno_ticket.sql b/scripts/database/eshop/storno_ticket.sql index e8d08422..b3b10d9a 100644 --- a/scripts/database/eshop/storno_ticket.sql +++ b/scripts/database/eshop/storno_ticket.sql @@ -9,8 +9,9 @@ DECLARE ticket_price NUMERIC(10, 2) := 0; updated_tickets JSONB := '[]'::JSONB; original_data JSONB; - ticket JSONB; -- Declare ticket as JSONB - product JSONB; -- Declare product as JSONB + ticket_data JSONB; -- Renamed variable + product JSONB; + remaining_opt_id BIGINT; -- Variable to store the remaining order_product_ticket ID BEGIN -- Check if the ticket exists and fetch its associated data SELECT t.*, o.id AS order_id, o.data AS order_data, t.occasion @@ -41,21 +42,33 @@ BEGIN SELECT id FROM eshop.order_product_ticket WHERE ticket = ticket_id ); - -- Remove all order_product_ticket records associated with the ticket - DELETE FROM eshop.order_product_ticket - WHERE ticket = ticket_id; + -- Find and keep one order_product_ticket and update its product to NULL + SELECT id INTO remaining_opt_id + FROM eshop.order_product_ticket + WHERE ticket = ticket_id + LIMIT 1; + + IF remaining_opt_id IS NOT NULL THEN + UPDATE eshop.order_product_ticket + SET product = NULL + WHERE id = remaining_opt_id; + + -- Delete all other order_product_ticket records for the ticket + DELETE FROM eshop.order_product_ticket + WHERE ticket = ticket_id AND id != remaining_opt_id; + END IF; -- Remove the ticket from orders.data and calculate the price reduction original_data := ticket_record.order_data; - FOR ticket IN SELECT JSONB_ARRAY_ELEMENTS(original_data->'tickets') LOOP - IF (ticket->>'ticket_id')::BIGINT = ticket_id THEN + FOR ticket_data IN SELECT JSONB_ARRAY_ELEMENTS(original_data->'tickets') LOOP + IF (ticket_data->>'ticket_id')::BIGINT = ticket_id THEN -- Calculate the price of all products in the ticket - FOR product IN SELECT JSONB_ARRAY_ELEMENTS(ticket->'products') LOOP + FOR product IN SELECT JSONB_ARRAY_ELEMENTS(ticket_data->'products') LOOP ticket_price := ticket_price + COALESCE((product->>'price')::NUMERIC, 0); END LOOP; ELSE -- Keep all other tickets - updated_tickets := updated_tickets || ticket; + updated_tickets := updated_tickets || ticket_data; END IF; END LOOP; @@ -73,7 +86,8 @@ BEGIN 'message', 'Ticket successfully cancelled', 'ticket_id', ticket_id, 'order_id', ticket_record.order_id, - 'price_reduction', ticket_price + 'price_reduction', ticket_price, + 'remaining_order_product_ticket', remaining_opt_id ); EXCEPTION WHEN OTHERS THEN From ef01ff7ddd63a3d904d99388fe774eb0c46b363d Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 21:35:01 +0100 Subject: [PATCH 141/159] SupabaseStorageService --- lib/dataServices/SupabaseStorageService.dart | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 lib/dataServices/SupabaseStorageService.dart diff --git a/lib/dataServices/SupabaseStorageService.dart b/lib/dataServices/SupabaseStorageService.dart new file mode 100644 index 00000000..154baa26 --- /dev/null +++ b/lib/dataServices/SupabaseStorageService.dart @@ -0,0 +1,38 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:fstapp/dataModels/FormModel.dart'; +import 'package:fstapp/dataModels/Tb.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintHelper.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; +import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; +import 'package:fstapp/dataModelsEshop/OrderModel.dart'; +import 'package:fstapp/dataModelsEshop/OrderProductTicketModel.dart'; +import 'package:fstapp/dataModelsEshop/PaymentInfoModel.dart'; +import 'package:fstapp/dataModelsEshop/ProductModel.dart'; +import 'package:fstapp/dataModelsEshop/ProductTypeModel.dart'; +import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/dataModelsEshop/TicketModel.dart'; +import 'package:fstapp/dataServices/RightsService.dart'; +import 'package:fstapp/services/ToastHelper.dart'; +import 'package:fstapp/services/Utilities.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class SupabaseStorageService { + static const String _bucketName = 'editor-files'; + + static final _supabase = Supabase.instance.client; + + static Future uploadFile(BuildContext context, Uint8List data, String format) async { + final path = '${DateTime.now().millisecondsSinceEpoch}.$format'; + final uploadResponse = await _supabase.storage.from(_bucketName).uploadBinary(path, data); + if (uploadResponse.isEmpty) { + + } + return false; + } + + + +} From 3a05af8525e53af2bbae3814555be309e64b16f1 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:12:26 +0100 Subject: [PATCH 142/159] translations --- assets/translations/cs.json | 34 +++++++++++++++ assets/translations/en.json | 34 +++++++++++++++ lib/pages/Eshop/BlueprintEditorTab.dart | 58 ++++++++++++------------- lib/pages/Eshop/EshopColumns.dart | 2 +- 4 files changed, 98 insertions(+), 30 deletions(-) diff --git a/assets/translations/cs.json b/assets/translations/cs.json index cd41f9d5..2d20ed7d 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -313,5 +313,39 @@ "Submit order": "Odeslat objednávku", "It is not possible to select more tickets.": "Více vstupenek není možné vybrat.", "Seat selection": "Výběr místa", + "Change title": "Změnit název", + "Click an option in the legend to add seats.": "Klikněte na možnost v legendě pro přidání sedadel.", + "Groups (Tables):": "Skupiny (Stoly):", + "Add new": "Přidat nový", + "Rename": "Přejmenovat", + "Group number": "Číslo skupiny", + "Black area": "Černá plocha", + "Available": "Dostupné", + "Empty": "Prázdné", + "Selected": "Vybrané", + "Width": "Šířka", + "Height": "Výška", + "Import SVG background": "Importovat SVG pozadí", + "File uploaded successfully.": "Soubor byl úspěšně nahrán.", + "Import background": "Importovat pozadí", + "Places that are occupied cannot be changed.": "Místa, která jsou obsazená, není možné měnit.", + "First, select or create a group to add a spot (on the right).": "Nejdřív vyberte nebo vytvořte skupinu pro přidání místa (vpravo).", + "Spot added:": "Přidáno místo:", + "Spot removed.": "Odstraněno místo.", + "Area removed.": "Odstraněna plocha.", + "Ticket Symbol": "Symbol vstupenky", + "Created": "Vytvořeno", + "Products": "Produkty", + "Hidden note": "Skrytá poznámka", + "Order Symbol": "Symbol objednávky", + "Customer": "Zákazník", + "Amount": "Částka", + "Paid amount": "Zaplacená částka", + "Variable symbol": "Variabilní symbol", + "Deadline": "Termín", + "Form": "Formulář", + "Blueprint": "Plánek", + "Tickets": "Vstupenky", + "Orders": "Objednávky", "_": "_" } \ No newline at end of file diff --git a/assets/translations/en.json b/assets/translations/en.json index ffd9cf29..33b285d6 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -313,5 +313,39 @@ "Submit order": "Submit order", "It is not possible to select more tickets.": "It is not possible to select more tickets.", "Seat selection": "Seat selection", + "Change title": "Change title", + "Click an option in the legend to add seats.": "Click an option in the legend to add seats.", + "Groups (Tables):": "Groups (Tables):", + "Add new": "Add new", + "Rename": "Rename", + "Group number": "Group number", + "Black area": "Black area", + "Available": "Available", + "Empty": "Empty", + "Selected": "Selected", + "Width": "Width", + "Height": "Height", + "Import SVG background": "Import SVG background", + "File uploaded successfully.": "File uploaded successfully.", + "Import background": "Import background", + "Places that are occupied cannot be changed.": "Places that are occupied cannot be changed.", + "First, select or create a group to add a spot (on the right).": "First, select or create a group to add a spot (on the right).", + "Spot added:": "Spot added:", + "Spot removed.": "Spot removed.", + "Area removed.": "Area removed.", + "Ticket Symbol": "Ticket Symbol", + "Created": "Created", + "Products": "Products", + "Hidden note": "Hidden note", + "Order Symbol": "Order Symbol", + "Customer": "Customer", + "Amount": "Amount", + "Paid amount": "Paid amount", + "Variable symbol": "Variable symbol", + "Deadline": "Deadline", + "Form": "Form", + "Blueprint": "Blueprint", + "Tickets": "Tickets", + "Orders": "Orders", "_":"_" } \ No newline at end of file diff --git a/lib/pages/Eshop/BlueprintEditorTab.dart b/lib/pages/Eshop/BlueprintEditorTab.dart index 13c4f719..cae1db18 100644 --- a/lib/pages/Eshop/BlueprintEditorTab.dart +++ b/lib/pages/Eshop/BlueprintEditorTab.dart @@ -58,7 +58,7 @@ class _BlueprintTabState extends State { actions: [ IconButton( icon: const Icon(Icons.edit), - tooltip: "Změnit název".tr(), + tooltip: "Change title".tr(), onPressed: editBlueprintTitle, ), ], @@ -77,10 +77,10 @@ class _BlueprintTabState extends State { Padding( padding: const EdgeInsets.only(bottom: 16.0), child: Text( - "Pro přidávání sedadel a stolů klikněte na čtvereček v legendě.", + "Click an option in the legend to add seats.", style: Theme.of(context).textTheme.bodySmall, textAlign: TextAlign.left, - ), + ).tr(), ), buildLegend(), ], @@ -163,24 +163,24 @@ class _BlueprintTabState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "Skupiny (Stoly):", + "Groups (Tables):", style: Theme.of(context).textTheme.titleMedium, - ), + ).tr(), Row( children: [ IconButton( icon: const Icon(Icons.add), - tooltip: "Přidat nový stůl", + tooltip: "Add new".tr(), onPressed: addGroup, ), IconButton( icon: const Icon(Icons.delete), - tooltip: "Smazat vybraný stůl", + tooltip: "Delete".tr(), onPressed: deleteGroup, ), IconButton( icon: const Icon(Icons.edit), - tooltip: "Přejmenovat vybraný stůl", + tooltip: "Rename".tr(), onPressed: renameGroup, ), ], @@ -260,8 +260,8 @@ class _BlueprintTabState extends State { final newTitle = await DialogHelper.showInputDialog( context: context, - dialogTitle: "Přidat nový stůl", - labelText: "Číslo stolu", + dialogTitle: "Add new".tr(), + labelText: "Group number".tr(), initialValue: defaultName, ); @@ -287,8 +287,8 @@ class _BlueprintTabState extends State { final newTitle = await DialogHelper.showInputDialog( context: context, - dialogTitle: "Přejmenovat stůl", - labelText: "Nový název", + dialogTitle: "Rename".tr(), + labelText: "Title".tr(), initialValue: currentGroup!.title, ); @@ -305,7 +305,7 @@ class _BlueprintTabState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ buildLegendItem( - "Stůl", + "Black area".tr(), SeatState.black, isActive: currentSelectionMode == selectionMode.addBlack, onTap: () { @@ -316,7 +316,7 @@ class _BlueprintTabState extends State { ), const SizedBox(height: 8), buildLegendItem( - "Dostupné", + "Available".tr(), SeatState.available, isActive: currentSelectionMode == selectionMode.addAvailable, onTap: () { @@ -327,7 +327,7 @@ class _BlueprintTabState extends State { ), const SizedBox(height: 8), buildLegendItem( - "Prázdné", + "Empty".tr(), SeatState.empty, isActive: currentSelectionMode == selectionMode.emptyArea, onTap: () { @@ -338,14 +338,14 @@ class _BlueprintTabState extends State { ), const SizedBox(height: 8), buildLegendItem( - "Obsazené", + "Occupied".tr(), SeatState.ordered, isActive: false, // Not clickable grayedOut: true, ), const SizedBox(height: 8), buildLegendItem( - "Vybrané", + "Selected".tr(), SeatState.selected_by_me, isActive: false, // Not clickable grayedOut: true, @@ -400,7 +400,7 @@ class _BlueprintTabState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ buildDimensionEditor( - "Šířka", + "Width".tr(), blueprint!.configuration!.width!, (value) { setState(() { @@ -411,7 +411,7 @@ class _BlueprintTabState extends State { ), const SizedBox(width: 12), // Reduced spacing between editors buildDimensionEditor( - "Výška", + "Height", blueprint!.configuration!.height!, (value) { setState(() { @@ -425,7 +425,7 @@ class _BlueprintTabState extends State { onPressed: () async { var file = await DialogHelper.dropFilesHere( context, - "Import SVG pozadí".tr(), + "Import SVG background".tr(), "Ok".tr(), "Storno".tr(), ); @@ -434,7 +434,7 @@ class _BlueprintTabState extends State { setState(() { blueprint!.backgroundSvg = content; }); - ToastHelper.Show(context, "SVG pozadí bylo úspěšně nahráno."); + ToastHelper.Show(context, "File uploaded successfully.".tr()); } }, child: Row( @@ -514,8 +514,8 @@ class _BlueprintTabState extends State { void editBlueprintTitle() async { final newTitle = await DialogHelper.showInputDialog( context: context, - dialogTitle: "Změnit název", - labelText: "Název blueprintu", + dialogTitle: "Rename".tr(), + labelText: "Title".tr(), initialValue: blueprint?.title ?? "", ); @@ -528,7 +528,7 @@ class _BlueprintTabState extends State { void handleSeatTap(SeatModel model) { if (model.objectModel?.isOrdered() ?? false) { - ToastHelper.Show(context, "Místa, která byla objednána není možné měnit.", severity: ToastSeverity.NotOk); + ToastHelper.Show(context, "Places that are occupied cannot be changed.".tr(), severity: ToastSeverity.NotOk); return; } @@ -570,7 +570,7 @@ class _BlueprintTabState extends State { /// Handle adding an available (spot) seat. void _handleAddAvailable(SeatModel model) { if(currentGroup == null){ - ToastHelper.Show(context, "Nejdřív vyberte/vytvořte skupinu pro přidání místa (vpravo).", severity: ToastSeverity.NotOk); + ToastHelper.Show(context, "First, select or create a group to add a spot (on the right).".tr(), severity: ToastSeverity.NotOk); return; } @@ -596,7 +596,7 @@ class _BlueprintTabState extends State { blueprint!.objects!.add(model.objectModel!); // Notify the user - ToastHelper.Show(context, "Přidáno sedadlo ${model.objectModel!.title}."); + ToastHelper.Show(context, "${"Spot added:".tr()} ${model.objectModel!.title}"); } /// Handle clearing an area (emptying a seat). @@ -604,9 +604,9 @@ class _BlueprintTabState extends State { if (model.objectModel != null) { // Determine the type of area being removed and notify the user if (model.seatState == SeatState.black) { - ToastHelper.Show(context, "Odstraněna plocha."); + ToastHelper.Show(context, "Area removed.".tr()); } else { - ToastHelper.Show(context, "Odstraněno místo."); + ToastHelper.Show(context, "Spot removed.".tr()); } // Remove the object from blueprint and all groups @@ -624,7 +624,7 @@ class _BlueprintTabState extends State { void saveChanges() async { var success = await DbEshop.updateBlueprint(context, blueprint!); if(success){ - ToastHelper.Show(context, "Změny byly uloženy."); + ToastHelper.Show(context, "Saved".tr()); await loadData(); } } diff --git a/lib/pages/Eshop/EshopColumns.dart b/lib/pages/Eshop/EshopColumns.dart index 402b9f4d..1a4efeb1 100644 --- a/lib/pages/Eshop/EshopColumns.dart +++ b/lib/pages/Eshop/EshopColumns.dart @@ -210,7 +210,7 @@ class EshopColumns { ], PRODUCT_PRICE: [ PlutoColumn( - title: "Product Price".tr(), + title: "Price".tr(), field: TbEshop.products.price, type: PlutoColumnType.text(), textAlign: PlutoColumnTextAlign.end, From 3a506f42ae7c0d11a2ea74f6da03cf4755308976 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:42:36 +0100 Subject: [PATCH 143/159] translations --- assets/translations/cs.json | 1 + assets/translations/en.json | 1 + lib/pages/Eshop/BlueprintEditorTab.dart | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/translations/cs.json b/assets/translations/cs.json index 2d20ed7d..da3ef9ae 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -347,5 +347,6 @@ "Blueprint": "Plánek", "Tickets": "Vstupenky", "Orders": "Objednávky", + "State": "Stav", "_": "_" } \ No newline at end of file diff --git a/assets/translations/en.json b/assets/translations/en.json index 33b285d6..6d559afd 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -347,5 +347,6 @@ "Blueprint": "Blueprint", "Tickets": "Tickets", "Orders": "Orders", + "State": "State", "_":"_" } \ No newline at end of file diff --git a/lib/pages/Eshop/BlueprintEditorTab.dart b/lib/pages/Eshop/BlueprintEditorTab.dart index cae1db18..7d055f31 100644 --- a/lib/pages/Eshop/BlueprintEditorTab.dart +++ b/lib/pages/Eshop/BlueprintEditorTab.dart @@ -411,7 +411,7 @@ class _BlueprintTabState extends State { ), const SizedBox(width: 12), // Reduced spacing between editors buildDimensionEditor( - "Height", + "Height".tr(), blueprint!.configuration!.height!, (value) { setState(() { From dce6dac3562e93e1fae6648c8a274729f05ace05 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 07:07:54 +0100 Subject: [PATCH 144/159] bigger font in tooltip --- .../seatReservation/widgets/SeatLayoutWidget.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index b0225e3e..417c3fb8 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:fstapp/themeConfig.dart'; import '../model/SeatLayoutStateModel.dart'; import '../model/SeatModel.dart'; @@ -161,8 +162,7 @@ class _SeatLayoutWidgetState extends State { ) : Tooltip( showDuration: const Duration(seconds: 0), - message: - "${seatModel.objectModel?.blueprintTooltip(context)}", + message: "${seatModel.objectModel?.blueprintTooltip(context)}", child: GestureDetector( onTap: () { if (widget.onSeatTap != null) { @@ -174,6 +174,16 @@ class _SeatLayoutWidgetState extends State { size: seatModel.seatSize.toDouble(), ), ), + decoration: BoxDecoration( + color: ThemeConfig.whiteColor(context), // Tooltip background color + borderRadius: BorderRadius.circular(8.0), + border: Border.all(width: 2, color: ThemeConfig.blackColor(context)) + ), + textStyle: TextStyle( + fontSize: 16.0, // Adjust the font size + color: ThemeConfig.blackColor(context), // Tooltip text color + ), + verticalOffset: 20.0, // Adjust the vertical offset ), ); }).toList(), From 639d5713ba1d63d71d626baa600a1b8f0245b08d Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:29:13 +0100 Subject: [PATCH 145/159] email qr extra info --- supabase/functions/send-ticket-order/index.ts | 64 +++++++++++++++---- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/supabase/functions/send-ticket-order/index.ts b/supabase/functions/send-ticket-order/index.ts index 039717fc..1e652bc7 100644 --- a/supabase/functions/send-ticket-order/index.ts +++ b/supabase/functions/send-ticket-order/index.ts @@ -1,6 +1,8 @@ import { sendEmailWithSubs } from "../_shared/emailClient.ts"; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.46.2'; import { qrcode } from 'https://deno.land/x/qrcode/mod.ts'; +import { encode } from "https://deno.land/std/encoding/base64.ts"; +import { createCanvas, loadImage } from "https://deno.land/x/canvas/mod.ts"; const _DEFAULT_EMAIL = Deno.env.get("DEFAULT_EMAIL")!; @@ -15,19 +17,55 @@ const supabaseAdmin = createClient( ); // Function to generate QR code as base64 -async function generateQrCode(paymentInfo: any, orderDetails: any): Promise { +async function generateQrCode(paymentInfo: any, orderDetails: any, occasionTitle: string): Promise { + console.log("Starting QR code generation with text..."); + console.log("Payment Info:", paymentInfo); + console.log("Order Details:", orderDetails); + console.log("Occasion Title:", occasionTitle); + + // Create canvas + const canvas = createCanvas(500, 700); // Adjust height to fit QR code and text + const ctx = canvas.getContext("2d"); + + console.log("Canvas initialized."); + + // Fill the background with white + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 500, 700); + + // Generate QR code const qrData = `SPD*1.0*ACC:${paymentInfo.account_number}*AM:${paymentInfo.amount.toFixed( 2 )}*CC:${paymentInfo.currency_code}*MSG:${orderDetails.name} ${orderDetails.surname}*X-VS:${paymentInfo.variable_symbol}`; - const base64QrCode = await qrcode(qrData, { size: 500 }); - console.log("QR Code Output:", base64QrCode); - - if (typeof base64QrCode !== "string") { - throw new Error("Unexpected QR code format returned by qrcode function."); - } + console.log("Generated QR data string:", qrData); - const base64String = base64QrCode.split(",")[1]; // Remove the `data:image/...` prefix - return Uint8Array.from(atob(base64String), (c) => c.charCodeAt(0)); // Convert Base64 to binary + const base64QrCode = await qrcode(qrData, { size: 500 }); + console.log("QR Code Base64 Output:", base64QrCode); + + // Convert base64 QR code into image + const qrImage = await loadImage(`data:image/png;base64,${base64QrCode.split(",")[1]}`); + console.log("QR Image loaded successfully."); + + // Draw QR code on canvas + ctx.drawImage(qrImage, 0, 0, 500, 500); + console.log("QR Code drawn on canvas."); + + // Add text below the QR code + ctx.font = "20px Arial"; + ctx.fillStyle = "black"; + ctx.textAlign = "left"; + ctx.fillText(`Objednávka: ${occasionTitle}`, 32, 550); + ctx.fillText(`Bankovní účet: ${paymentInfo.account_number_human_readable}`, 32, 580); + ctx.fillText(`VS: ${paymentInfo.variable_symbol}`, 32, 610); + ctx.fillText(`Poznámka: ${orderDetails.name} ${orderDetails.surname}`, 32, 640); + ctx.fillText(`Celková cena: ${formatCurrency(paymentInfo.amount, paymentInfo.currency_code)}`, 32, 670); + console.log("Text added below QR code."); + + // Convert canvas to binary data + const buffer = canvas.toBuffer(); + console.log("Canvas converted to buffer. Buffer size:", buffer.length); + + return buffer; } function formatDatetime(datetime: string): string { @@ -115,7 +153,7 @@ Deno.serve(async (req) => { const { data: occasionData, error: occasionError } = await supabaseAdmin .from("occasions") - .select("organization") + .select("organization, link") .eq("id", occasion.id) .single(); @@ -138,7 +176,7 @@ Deno.serve(async (req) => { } const formattedDeadline = formatDatetime(paymentInfo.deadline); - const qrCode = await generateQrCode(paymentInfo, orderDetails); + const qrCode = await generateQrCode(paymentInfo, orderDetails, occasion.occasion_title); const fullOrder = generateFullOrder(orderDetails, ticketOrder.tickets); const subs = { @@ -160,9 +198,9 @@ Deno.serve(async (req) => { from: `${occasion.occasion_title} | Festapp <${_DEFAULT_EMAIL}>`, attachments: [ { - filename: "payment-qr-code.gif", // Name of the file + filename: `qr-payment.${occasionData.link}.png`, // Name of the file content: qrCode, // Ensure qrCode is a Uint8Array - contentType: "image/gif", // MIME type for GIF + contentType: "image/png", // MIME type for GIF encoding: "binary", // Specify binary encoding }, ], From 8963284e8aa6f62e28d875658842d19242904de0 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:57:28 +0100 Subject: [PATCH 146/159] generate_variable_symbol --- scripts/database/eshop/generate_variable_symbol.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/database/eshop/generate_variable_symbol.sql b/scripts/database/eshop/generate_variable_symbol.sql index c61b1b62..466291f9 100644 --- a/scripts/database/eshop/generate_variable_symbol.sql +++ b/scripts/database/eshop/generate_variable_symbol.sql @@ -2,13 +2,13 @@ CREATE OR REPLACE FUNCTION generate_variable_symbol(bank_account_id BIGINT) RETURNS BIGINT AS $$ DECLARE v_symbol BIGINT; - digit_count INT := 4; + digit_count INT := 4; -- Start with 4 digits attempt_count INT := 0; - max_attempts INT := 1; -- Maximum allowed attempts + max_attempts INT := 1; -- Maximum allowed attempts per digit count BEGIN LOOP -- Generate a random number with the current digit count - v_symbol := FLOOR(RANDOM() * POWER(10, digit_count - 1)) + POWER(10, digit_count - 1); + v_symbol := FLOOR(RANDOM() * (POWER(10, digit_count) - POWER(10, digit_count - 1))) + POWER(10, digit_count - 1); -- Ensure uniqueness within the bank_account IF NOT EXISTS ( @@ -22,16 +22,16 @@ BEGIN -- Increment attempt count and adjust digit count after each failure attempt_count := attempt_count + 1; - -- Extend digit count after each failure + -- Extend digit count after reaching max attempts IF attempt_count >= max_attempts THEN digit_count := digit_count + 1; attempt_count := 0; -- Reset attempt count for the extended digit count END IF; - -- Fail entirely if the system cannot generate a unique symbol after many attempts + -- Fail entirely if the system cannot generate a unique symbol after too many attempts IF digit_count > 15 THEN -- Arbitrary upper limit for digit count RAISE EXCEPTION 'Failed to generate unique variable symbol after too many attempts.'; END IF; END LOOP; END; -$$ LANGUAGE plpgsql; +$$ LANGUAGE plpgsql; \ No newline at end of file From 7aaa4966c2b4aad1779fbd48e30c65ad1546222e Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:39:06 +0100 Subject: [PATCH 147/159] layout refactor --- .../widgets/SeatLayoutWidget.dart | 28 ++++--------------- lib/pages/Eshop/BlueprintEditorTab.dart | 9 +++--- ...AutoResizeInteractiveViewerController.dart | 14 ++++++++++ 3 files changed, 25 insertions(+), 26 deletions(-) create mode 100644 lib/widgets/AutoResizeInteractiveViewerController.dart diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index 417c3fb8..ac27e7c8 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:fstapp/themeConfig.dart'; +import 'package:fstapp/widgets/AutoResizeInteractiveViewerController.dart'; import '../model/SeatLayoutStateModel.dart'; import '../model/SeatModel.dart'; @@ -10,7 +11,7 @@ import 'SeatWidget.dart'; class SeatLayoutWidget extends StatefulWidget { final SeatLayoutStateModel stateModel; - final SeatLayoutWidgetController? controller; + final AutoResizeInteractiveViewerController? controller; final void Function(SeatModel model)? onSeatTap; final bool? isEditorMode; const SeatLayoutWidget({ @@ -30,12 +31,11 @@ class _SeatLayoutWidgetState extends State { late List _seats; double _minScale = 1.0; // Dynamic minimum scale - @override @override void initState() { super.initState(); - widget.controller?.attachFitLayout(_fitLayout); + widget.controller?.attachFitContent(_fitLayout); _seats = _generateSeatModels(); @@ -120,11 +120,11 @@ class _SeatLayoutWidgetState extends State { width: layoutWidth.toDouble(), height: layoutHeight.toDouble(), decoration: BoxDecoration( - color: Colors.transparent, // No background color - borderRadius: BorderRadius.circular(12.0), // Rounded corners + color: Colors.transparent, + borderRadius: BorderRadius.circular(12.0), ), child: ClipRRect( - borderRadius: BorderRadius.circular(12.0), // Match the rounded corners + borderRadius: BorderRadius.circular(12.0), child: SvgPicture.string( widget.stateModel.backgroundSvg!, width: layoutWidth.toDouble(), @@ -134,7 +134,6 @@ class _SeatLayoutWidgetState extends State { ), ), ), - // Seat Layout with Positioned widgets SizedBox( width: layoutWidth.toDouble(), height: layoutHeight.toDouble(), @@ -251,20 +250,5 @@ class _SeatLayoutWidgetState extends State { ..setTranslationRaw(clampedX, clampedY, 0); } } - - } -class SeatLayoutWidgetController { - Function()? _fitLayout; - - /// Attach a fit layout method - void attachFitLayout(Function()? fitLayout) { - _fitLayout = fitLayout; - } - - /// Call the fit layout method - void fitLayout() { - _fitLayout?.call(); - } -} diff --git a/lib/pages/Eshop/BlueprintEditorTab.dart b/lib/pages/Eshop/BlueprintEditorTab.dart index 7d055f31..af02e2ce 100644 --- a/lib/pages/Eshop/BlueprintEditorTab.dart +++ b/lib/pages/Eshop/BlueprintEditorTab.dart @@ -11,6 +11,7 @@ import 'package:fstapp/services/DialogHelper.dart'; import 'package:fstapp/services/ToastHelper.dart'; import 'package:fstapp/services/Utilities.dart'; import 'package:fstapp/themeConfig.dart'; +import 'package:fstapp/widgets/AutoResizeInteractiveViewerController.dart'; import 'package:fstapp/widgets/SeatReservationWidget.dart'; import 'package:collection/collection.dart'; @@ -37,8 +38,8 @@ class _BlueprintTabState extends State { selectionMode currentSelectionMode = selectionMode.none; - final SeatLayoutWidgetController seatLayoutController = - SeatLayoutWidgetController(); + final AutoResizeInteractiveViewerController seatLayoutController = + AutoResizeInteractiveViewerController(); @override void didChangeDependencies() { @@ -406,7 +407,7 @@ class _BlueprintTabState extends State { setState(() { blueprint!.configuration!.width = value; }); - seatLayoutController.fitLayout(); // Fit layout after width change + seatLayoutController.fitContent(); }, ), const SizedBox(width: 12), // Reduced spacing between editors @@ -417,7 +418,7 @@ class _BlueprintTabState extends State { setState(() { blueprint!.configuration!.height = value; }); - seatLayoutController.fitLayout(); // Fit layout after height change + seatLayoutController.fitContent(); }, ), const SizedBox(width: 12), diff --git a/lib/widgets/AutoResizeInteractiveViewerController.dart b/lib/widgets/AutoResizeInteractiveViewerController.dart new file mode 100644 index 00000000..b8cf2d2c --- /dev/null +++ b/lib/widgets/AutoResizeInteractiveViewerController.dart @@ -0,0 +1,14 @@ +class AutoResizeInteractiveViewerController { + int? layoutWidth; + int? layoutHeight; + + Function()? _fitContent; + + void attachFitContent(Function()? fitContent) { + _fitContent = fitContent; + } + + void fitContent() { + _fitContent?.call(); + } +} \ No newline at end of file From dbdfc4113936ef99ef1133abe0a85aba55fb5710 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:43:40 +0100 Subject: [PATCH 148/159] layout refactor --- .../widgets/SeatLayoutWidget.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart index ac27e7c8..8b9e9f8d 100644 --- a/lib/components/seatReservation/widgets/SeatLayoutWidget.dart +++ b/lib/components/seatReservation/widgets/SeatLayoutWidget.dart @@ -15,12 +15,12 @@ class SeatLayoutWidget extends StatefulWidget { final void Function(SeatModel model)? onSeatTap; final bool? isEditorMode; const SeatLayoutWidget({ - Key? key, + super.key, required this.stateModel, this.controller, this.onSeatTap, this.isEditorMode, - }) : super(key: key); + }); @override _SeatLayoutWidgetState createState() => _SeatLayoutWidgetState(); @@ -103,8 +103,8 @@ class _SeatLayoutWidgetState extends State { @override Widget build(BuildContext context) { - int layoutWidth = widget.stateModel.cols * widget.stateModel.seatSize; - int layoutHeight = widget.stateModel.rows * widget.stateModel.seatSize; + double layoutWidth = (widget.stateModel.cols * widget.stateModel.seatSize).toDouble(); + double layoutHeight = (widget.stateModel.rows * widget.stateModel.seatSize).toDouble(); return InteractiveViewer( minScale: _minScale, @@ -117,8 +117,8 @@ class _SeatLayoutWidgetState extends State { if (widget.stateModel.backgroundSvg != null) Positioned.fill( child: Container( - width: layoutWidth.toDouble(), - height: layoutHeight.toDouble(), + width: layoutWidth, + height: layoutHeight, decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(12.0), @@ -127,16 +127,16 @@ class _SeatLayoutWidgetState extends State { borderRadius: BorderRadius.circular(12.0), child: SvgPicture.string( widget.stateModel.backgroundSvg!, - width: layoutWidth.toDouble(), - height: layoutHeight.toDouble(), + width: layoutWidth, + height: layoutHeight, fit: BoxFit.cover, ), ), ), ), SizedBox( - width: layoutWidth.toDouble(), - height: layoutHeight.toDouble(), + width: layoutWidth, + height: layoutHeight, child: Stack( children: _seats .where((seatModel) => From 4afae1e7083aec7abdda5c9f0f263cf14efafd36 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:25:26 +0100 Subject: [PATCH 149/159] storno ticket to history --- scripts/database/eshop/storno_ticket.sql | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/scripts/database/eshop/storno_ticket.sql b/scripts/database/eshop/storno_ticket.sql index b3b10d9a..88e47ea3 100644 --- a/scripts/database/eshop/storno_ticket.sql +++ b/scripts/database/eshop/storno_ticket.sql @@ -9,9 +9,9 @@ DECLARE ticket_price NUMERIC(10, 2) := 0; updated_tickets JSONB := '[]'::JSONB; original_data JSONB; - ticket_data JSONB; -- Renamed variable + ticket_data JSONB; product JSONB; - remaining_opt_id BIGINT; -- Variable to store the remaining order_product_ticket ID + remaining_opt_id BIGINT; BEGIN -- Check if the ticket exists and fetch its associated data SELECT t.*, o.id AS order_id, o.data AS order_data, t.occasion @@ -80,6 +80,22 @@ BEGIN updated_at = NOW() WHERE id = ticket_record.order_id; + -- Save the change to orders_history + INSERT INTO eshop.orders_history ( + "order", + data, + state, + price, + created_at + ) + VALUES ( + ticket_record.order_id, + original_data || JSONB_BUILD_OBJECT('tickets', updated_tickets), + 'ordered', + COALESCE((SELECT price FROM eshop.orders WHERE id = ticket_record.order_id), 0), + NOW() + ); + -- Return success response RETURN JSONB_BUILD_OBJECT( 'code', 200, From b21567de25bea2456bab6f1332e4c34ba999e232 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:41:54 +0100 Subject: [PATCH 150/159] storno update --- lib/pages/Eshop/TicketsTab.dart | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/pages/Eshop/TicketsTab.dart b/lib/pages/Eshop/TicketsTab.dart index d5714383..7f03e236 100644 --- a/lib/pages/Eshop/TicketsTab.dart +++ b/lib/pages/Eshop/TicketsTab.dart @@ -20,6 +20,8 @@ class TicketsTab extends StatefulWidget { class _TicketsTabState extends State { String? formKey; + Key refreshKey = UniqueKey(); + @override void didChangeDependencies() { @@ -29,6 +31,12 @@ class _TicketsTabState extends State { } } + Future refreshData() async { + setState(() { + refreshKey = UniqueKey(); + }); + } + static const List columnIdentifiers = [ EshopColumns.TICKET_ID, EshopColumns.ORDER_SYMBOL, @@ -43,7 +51,10 @@ class _TicketsTabState extends State { @override Widget build(BuildContext context) { - return SingleTableDataGrid( + return KeyedSubtree( + key: refreshKey, + child: + SingleTableDataGrid( context, () => DbEshop.getAllTickets(formKey!), TicketModel.fromPlutoJson, @@ -61,7 +72,7 @@ class _TicketsTabState extends State { ), ], columns: EshopColumns.generateColumns(columnIdentifiers), - ).DataGrid(); + ).DataGrid()); } Future _stornoTickets(SingleTableDataGrid dataGrid) async { @@ -74,7 +85,7 @@ class _TicketsTabState extends State { var confirm = await DialogHelper.showConfirmationDialogAsync( context, "Storno".tr(), - "Are you sure you want to storno the selected tickets?".tr(), + "${"Are you sure you want to storno the selected tickets?".tr()} (${selectedTickets.length})", ); if (confirm) { @@ -96,6 +107,7 @@ class _TicketsTabState extends State { stornoFutures.length, futures: stornoFutures, ); + refreshData(); } } From 7c5c06311598334c383da8d1a22b5a4b558c14f1 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:55:41 +0100 Subject: [PATCH 151/159] all as pure id --- scripts/database/eshop/create_ticket_order.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index 47fa7e97..cebe5646 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -159,7 +159,7 @@ BEGIN RETURN JSONB_BUILD_OBJECT( 'code', 1012, 'message', 'Selected product is hidden and cannot be ordered', - 'product_id', product_id + 'id', product_id ); END IF; @@ -171,7 +171,7 @@ BEGIN RETURN JSONB_BUILD_OBJECT( 'code', 1014, 'message', 'Products in the order must have the same currency', - 'product_id', product_id, + 'id', product_id, 'expected_currency', first_currency_code, 'actual_currency', product_data.currency_code ); @@ -180,7 +180,7 @@ BEGIN -- Add product details to ticket products ticket_products := ticket_products || JSONB_BUILD_OBJECT( - 'product_id', product_id, + 'id', product_id, 'title', product_data.title, 'type', product_data.type, 'price', product_data.price, @@ -208,7 +208,7 @@ BEGIN -- Add ticket with its products and note to ticket details ticket_details := ticket_details || JSONB_BUILD_OBJECT( - 'ticket_id', ticket_id, + 'id', ticket_id, 'ticket_symbol', ticket_symbol, 'note', ticket_data->>'note', 'products', ticket_products @@ -246,10 +246,10 @@ BEGIN -- Return success response with order ID, tickets, and payment info RETURN JSONB_BUILD_OBJECT( 'code', 200, - 'order_id', order_id, + 'id', order_id, 'tickets', ticket_details, 'payment_info', JSONB_BUILD_OBJECT( - 'payment_info_id', payment_info_id, + 'id', payment_info_id, 'variable_symbol', generated_variable_symbol, 'amount', calculated_price, 'deadline', deadline, From b28ea489f8aebd08c555736c300594fada8e88f2 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:04:31 +0100 Subject: [PATCH 152/159] all as pure id --- scripts/database/eshop/storno_ticket.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/database/eshop/storno_ticket.sql b/scripts/database/eshop/storno_ticket.sql index 88e47ea3..d2165816 100644 --- a/scripts/database/eshop/storno_ticket.sql +++ b/scripts/database/eshop/storno_ticket.sql @@ -61,7 +61,7 @@ BEGIN -- Remove the ticket from orders.data and calculate the price reduction original_data := ticket_record.order_data; FOR ticket_data IN SELECT JSONB_ARRAY_ELEMENTS(original_data->'tickets') LOOP - IF (ticket_data->>'ticket_id')::BIGINT = ticket_id THEN + IF (ticket_data->>'id')::BIGINT = ticket_id THEN -- Calculate the price of all products in the ticket FOR product IN SELECT JSONB_ARRAY_ELEMENTS(ticket_data->'products') LOOP ticket_price := ticket_price + COALESCE((product->>'price')::NUMERIC, 0); From f9de2bed3d935a31580a7eb7f5b83c3fe936cd23 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:10:06 +0100 Subject: [PATCH 153/159] removed price from history --- scripts/database/eshop/create_ticket_order.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/database/eshop/create_ticket_order.sql b/scripts/database/eshop/create_ticket_order.sql index cebe5646..7c8938cc 100644 --- a/scripts/database/eshop/create_ticket_order.sql +++ b/scripts/database/eshop/create_ticket_order.sql @@ -237,7 +237,7 @@ BEGIN INSERT INTO eshop.orders_history (created_at, data, "order", state, price) VALUES ( now, - JSONB_BUILD_OBJECT('input_data', input_data, 'tickets', ticket_details, 'price', calculated_price, 'currency_code', first_currency_code), + JSONB_BUILD_OBJECT('input_data', input_data, 'tickets', ticket_details), order_id, 'ordered', calculated_price From 8b9652f897abcacb89f095427640279ab17fe33f Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:15:24 +0100 Subject: [PATCH 154/159] total price tickets --- lib/dataModelsEshop/TicketModel.dart | 4 ++++ lib/dataServices/DbEshop.dart | 14 +++++++++++++- lib/pages/Eshop/EshopColumns.dart | 12 ++++++++++++ lib/pages/Eshop/TicketsTab.dart | 1 + 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/dataModelsEshop/TicketModel.dart b/lib/dataModelsEshop/TicketModel.dart index 84d34ba3..d66f4779 100644 --- a/lib/dataModelsEshop/TicketModel.dart +++ b/lib/dataModelsEshop/TicketModel.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:fstapp/components/dataGrid/PlutoAbstract.dart'; import 'package:fstapp/dataModelsEshop/TbEshop.dart'; +import 'package:fstapp/services/Utilities.dart'; import 'package:pluto_grid_plus/pluto_grid_plus.dart'; import 'OrderModel.dart'; import 'BlueprintObjectModel.dart'; @@ -16,6 +17,7 @@ class TicketModel extends IPlutoRowModel { int? occasion; String? note; String? noteHidden; + double? totalPrice; // Relating spots and products directly to the ticket List? relatedSpots; @@ -31,6 +33,7 @@ class TicketModel extends IPlutoRowModel { static const String metaRelatedOrder = "related_order"; static const String metaTicketsProducts = "ticket_products"; + static const String metaPrice = "price"; TicketModel({ this.id, @@ -92,6 +95,7 @@ class TicketModel extends IPlutoRowModel { value: relatedProducts != null ? relatedProducts!.map((p)=>p.toBasicString()).join(" | ") : ""), + metaPrice: PlutoCell(value: totalPrice != null ? Utilities.formatPrice(context, totalPrice!) : ""), }); } diff --git a/lib/dataServices/DbEshop.dart b/lib/dataServices/DbEshop.dart index 9914f6f1..7fa7fa8c 100644 --- a/lib/dataServices/DbEshop.dart +++ b/lib/dataServices/DbEshop.dart @@ -2,7 +2,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; import 'package:fstapp/dataModels/FormModel.dart'; import 'package:fstapp/dataModels/Tb.dart'; -import 'package:fstapp/dataModelsEshop/BlueprintGroup.dart'; import 'package:fstapp/dataModelsEshop/BlueprintHelper.dart'; import 'package:fstapp/dataModelsEshop/BlueprintModel.dart'; import 'package:fstapp/dataModelsEshop/BlueprintObjectModel.dart'; @@ -249,6 +248,19 @@ class DbEshop { var orders = await getAllOrders(formKey); List toReturn = []; for(var o in orders){ + + for(TicketModel t in o.relatedTickets??[]){ + var tckst = o.data?[TbEshop.tickets.table]; + t.totalPrice = tckst.isNotEmpty ? 0 : null; + for(var tt in tckst){ + if(tt[TbEshop.tickets.ticket_symbol] != t.ticketSymbol){ + continue; + } + for(var p in tt[TbEshop.products.table]){ + t.totalPrice = t.totalPrice! + (p[TbEshop.products.price] ?? 0); + } + } + } toReturn.addAll(o.relatedTickets??[]); } toReturn = toReturn.sortedBy((ou)=>ou.createdAt!).reversed.toList(); diff --git a/lib/pages/Eshop/EshopColumns.dart b/lib/pages/Eshop/EshopColumns.dart index 1a4efeb1..b7370e32 100644 --- a/lib/pages/Eshop/EshopColumns.dart +++ b/lib/pages/Eshop/EshopColumns.dart @@ -12,6 +12,7 @@ class EshopColumns { static const String TICKET_STATE = "ticketState"; static const String TICKET_NOTE = "ticketNote"; static const String TICKET_NOTE_HIDDEN = "ticketNoteHidden"; + static const String TICKET_TOTAL_PRICE = "ticketTotalPrice"; static const String TICKET_PRODUCTS = "ticketProducts"; static const String TICKET_CREATED_AT = "ticketCreatedAt"; @@ -69,6 +70,17 @@ class EshopColumns { width: 120, ), ], + TICKET_TOTAL_PRICE: [ + PlutoColumn( + readOnly: true, + enableEditingMode: false, + title: "Price".tr(), + field: TicketModel.metaPrice, + type: PlutoColumnType.text(), + textAlign: PlutoColumnTextAlign.end, + width: 100, + ), + ], TICKET_CREATED_AT: [ PlutoColumn( readOnly: true, diff --git a/lib/pages/Eshop/TicketsTab.dart b/lib/pages/Eshop/TicketsTab.dart index 7f03e236..12d26766 100644 --- a/lib/pages/Eshop/TicketsTab.dart +++ b/lib/pages/Eshop/TicketsTab.dart @@ -44,6 +44,7 @@ class _TicketsTabState extends State { EshopColumns.TICKET_SYMBOL, EshopColumns.TICKET_CREATED_AT, EshopColumns.TICKET_STATE, + EshopColumns.TICKET_TOTAL_PRICE, EshopColumns.TICKET_PRODUCTS, EshopColumns.TICKET_NOTE, EshopColumns.TICKET_NOTE_HIDDEN, From beb6ea107a394080c7f15d0d4394479bff2d1bd7 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:29:14 +0100 Subject: [PATCH 155/159] storno also secret on spot --- scripts/database/eshop/storno_ticket.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/database/eshop/storno_ticket.sql b/scripts/database/eshop/storno_ticket.sql index d2165816..ea0aa0cd 100644 --- a/scripts/database/eshop/storno_ticket.sql +++ b/scripts/database/eshop/storno_ticket.sql @@ -37,7 +37,7 @@ BEGIN -- Nullify the relevant spot's order_product_ticket UPDATE eshop.spots - SET order_product_ticket = NULL, updated_at = NOW() + SET order_product_ticket = NULL, secret = NULL, secret_expiration_time = NULL, updated_at = NOW() WHERE order_product_ticket IN ( SELECT id FROM eshop.order_product_ticket WHERE ticket = ticket_id ); From 4e964a82f0bc3ecbba3b5362e389821974602293 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:00:13 +0100 Subject: [PATCH 156/159] limit flutter version --- netlify.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/netlify.toml b/netlify.toml index 25acc410..f8199039 100644 --- a/netlify.toml +++ b/netlify.toml @@ -6,6 +6,5 @@ package = "netlify-plugin-flutter" channel = "stable" [build] - -command = "flutter build web --web-renderer canvaskit --release" +command = "flutter --version || (curl -L https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.24.5-stable.tar.xz | tar -xJf - -C \$HOME && export PATH=\$HOME/flutter/bin:\$PATH && flutter precache) && flutter build web --web-renderer canvaskit --release" publish = "build/web" \ No newline at end of file From a53b5a7607f4a8504467ad71f58f8ad5cb19acb9 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:20:55 +0100 Subject: [PATCH 157/159] flutter 3.24.5 pinpoint on netlify --- netlify.toml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/netlify.toml b/netlify.toml index f8199039..bf18f80f 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,10 +1,3 @@ -[[plugins]] - -package = "netlify-plugin-flutter" - -[plugins.inputs] -channel = "stable" - [build] -command = "flutter --version || (curl -L https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.24.5-stable.tar.xz | tar -xJf - -C \$HOME && export PATH=\$HOME/flutter/bin:\$PATH && flutter precache) && flutter build web --web-renderer canvaskit --release" +command = "curl -L https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.24.5-stable.tar.xz | tar -xJf - -C /opt/buildhome && PATH=/opt/buildhome/flutter/bin:$PATH flutter precache && PATH=/opt/buildhome/flutter/bin:$PATH flutter build web --web-renderer canvaskit --release" publish = "build/web" \ No newline at end of file From 46e8f0aac59c62b8f8deb4d369dfbd3e7552cfa1 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 12 Dec 2024 04:41:53 +0100 Subject: [PATCH 158/159] flutter 3.24.5 github --- .github/workflows/web.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 3780d3ff..6d45cb68 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -15,7 +15,9 @@ jobs: - name: Flutter uses: subosito/flutter-action@v2 with: + flutter-version: '3.24.5' channel: 'stable' + - run: flutter build web --web-renderer canvaskit --release --base-href / - name: Deploy From 6958865855c2ca41bbc91ae40e8af9645d2daee4 Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 12 Dec 2024 04:48:48 +0100 Subject: [PATCH 159/159] version: 0.11.0+102 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index e924ba4f..822520c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.10.16+101 +version: 0.11.0+102 environment: sdk: '>=2.19.5 <3.22.1'