From 36a70db69f7f19edfed18b9af40f91f219c25420 Mon Sep 17 00:00:00 2001 From: mr Date: Mon, 26 Aug 2024 17:37:23 +0200 Subject: [PATCH] Booking forms + Scheduler adaptation --- lib/core/services/router.dart | 2 + .../specialized_services/shared_service.dart | 24 ++ lib/models/response.dart | 2 + lib/models/shared.dart | 112 ++++++++++ lib/pages/shared.dart | 63 ++++++ lib/widgets/dialog/new_box_shared.dart | 207 ++++++++++++++++++ lib/widgets/forms/scheduler_forms.dart | 13 +- lib/widgets/menu_clipper/clipper_menu.dart | 1 - 8 files changed, 419 insertions(+), 5 deletions(-) create mode 100644 lib/core/services/specialized_services/shared_service.dart create mode 100644 lib/models/shared.dart create mode 100644 lib/pages/shared.dart create mode 100644 lib/widgets/dialog/new_box_shared.dart diff --git a/lib/core/services/router.dart b/lib/core/services/router.dart index 31dc94e..ed3abfb 100644 --- a/lib/core/services/router.dart +++ b/lib/core/services/router.dart @@ -5,6 +5,7 @@ import 'package:oc_front/pages/catalog_item.dart'; import 'package:oc_front/pages/datacenter.dart'; import 'package:oc_front/pages/map.dart'; import 'package:oc_front/pages/scheduler.dart'; +import 'package:oc_front/pages/shared.dart'; import 'package:oc_front/pages/workflow.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter/material.dart'; @@ -62,6 +63,7 @@ class AppRouter { description: "Manage & monitor your datacenter.", help: "not implemented for now", factory: DatacenterFactory()), RouterItem(icon: Icons.public_outlined, label: "localisations", route: "map", factory: MapFactory()), + RouterItem(icon: Icons.share_rounded, label: "shared spaces", route: "shared", factory: SharedFactory()), catalogItem, ]; static List history = []; diff --git a/lib/core/services/specialized_services/shared_service.dart b/lib/core/services/specialized_services/shared_service.dart new file mode 100644 index 0000000..f2f9686 --- /dev/null +++ b/lib/core/services/specialized_services/shared_service.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:oc_front/core/services/api_service.dart'; +import 'package:oc_front/core/services/specialized_services/abstract_service.dart'; +import 'package:oc_front/models/response.dart'; +import 'package:oc_front/models/shared.dart'; + +class SharedService extends AbstractService { + @override APIService service = APIService( + baseURL: const String.fromEnvironment('SHAREDWORKSPACE_HOST', defaultValue: 'http://localhost:8091') + ); + @override String subPath = "/oc/shared/workspace/"; + + Future> addWorkspace(BuildContext? context, String id, String id2) { + return service.get("$id/workspace/$id2", true, context); + } + + Future> addWorkflow(BuildContext? context, String id, String id2) { + return service.get("$id/workflow/$id2", true, context); + } + + Future> addPeer(BuildContext? context, String id, String id2) { + return service.get("$id/peer/$id2", true, context); + } +} \ No newline at end of file diff --git a/lib/models/response.dart b/lib/models/response.dart index 92250a9..fa19e49 100644 --- a/lib/models/response.dart +++ b/lib/models/response.dart @@ -1,6 +1,7 @@ import 'package:oc_front/models/abstract.dart'; import 'package:oc_front/models/logs.dart'; import 'package:oc_front/models/search.dart'; +import 'package:oc_front/models/shared.dart'; import 'package:oc_front/models/workflow.dart'; import 'package:oc_front/models/workspace.dart'; @@ -17,6 +18,7 @@ Map refs = { WorkflowExecutions: WorkflowExecutions(), LogsResult: LogsResult(), Check: Check(), + SharedWorkspace: SharedWorkspace(), }; class APIResponse { diff --git a/lib/models/shared.dart b/lib/models/shared.dart new file mode 100644 index 0000000..e77d1ac --- /dev/null +++ b/lib/models/shared.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:oc_front/models/abstract.dart'; +import 'package:oc_front/models/workflow.dart'; +import 'package:oc_front/models/workspace.dart'; + +class SharedWorkspace extends SerializerDeserializer { + String? id; + String? name; + String? description; + String? creatorID; + String? version; + Map attributes = {}; + List workspaces = []; + List workflows = []; + List peers = []; + List rules = []; + + SharedWorkspace( + {this.id, + this.name, + this.description, + this.creatorID, + this.version, + this.attributes = const {}, + this.workspaces = const [], + this.workflows = const [], + this.peers = const [], + this.rules = const []}); + + @override + deserialize(dynamic json) { + try { json = json as Map; + } catch (e) { return SharedWorkspace(); } + return SharedWorkspace( + id: json.containsKey("id") ? json["id"] : null, + name: json.containsKey("name") ? json["name"] : null, + description: json.containsKey("description") ? json["description"] : null, + creatorID: json.containsKey("creator_id") ? json["creator_id"] : null, + version: json.containsKey("version") ? json["version"] : null, + attributes: json.containsKey("attributes") ? json["attributes"] : {}, + workspaces: json.containsKey("shared_workspaces") ? fromListJson(json["shared_workspaces"], Workspace()) : [], + workflows: json.containsKey("shared_workflows") ? fromListJson(json["shared_workflows"], Workflow()) : [], + peers: json.containsKey("shared_peers") ? fromListJson(json["shared_peers"], Peer()) : [], + rules: json.containsKey("shared_rules") ? fromListJson(json["shared_rules"], Rule()) : [], + ); + } + @override + Map serialize() => { + "id": id, + "name": name, + "description": description, + "creator_id": creatorID, + "version": version, + "attributes": attributes, + "workspaces": workspaces.map((e) => e.id).toList(), + "workflows": workflows.map((e) => e.id).toList(), + "peers": peers.map((e) => e.id).toList(), + "rules": rules.map((e) => e.id).toList(), + }; +} + +class Rule extends SerializerDeserializer { + String? id; + String? name; + String? description; + + Rule( + {this.id, + this.name, + this.description,}); + + @override + deserialize(dynamic json) { + try { json = json as Map; + } catch (e) { return Rule(); } + return Rule( + id: json.containsKey("id") ? json["id"] : null, + name: json.containsKey("name") ? json["name"] : null, + description: json.containsKey("description") ? json["description"] : null, + ); + } + @override + Map serialize() => { + "id": id, + "name": name, + "description": description, + }; +} + +class Peer extends SerializerDeserializer { + String? id; + String? name; + + Peer( + {this.id, + this.name,}); + + @override + deserialize(dynamic json) { + try { json = json as Map; + } catch (e) { return Peer(); } + return Peer( + id: json.containsKey("id") ? json["id"] : null, + name: json.containsKey("name") ? json["name"] : null, + ); + } + @override + Map serialize() => { + "id": id, + "name": name, + }; +} \ No newline at end of file diff --git a/lib/pages/shared.dart b/lib/pages/shared.dart new file mode 100644 index 0000000..a40bf45 --- /dev/null +++ b/lib/pages/shared.dart @@ -0,0 +1,63 @@ +import 'package:alert_banner/exports.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:oc_front/core/sections/header/header.dart'; +import 'package:oc_front/pages/abstract_page.dart'; +import 'package:oc_front/widgets/dialog/new_box_shared.dart'; + +class SharedFactory implements AbstractFactory { + static GlobalKey key = GlobalKey(); + @override bool searchFill() { return false; } + @override Widget factory(GoRouterState state, List args) { return SharedPageWidget(); } + @override void search(BuildContext context) { } +} + +class SharedPageWidget extends StatefulWidget { + SharedPageWidget(): super(key: SharedFactory.key); + @override SharedPageWidgetState createState() => SharedPageWidgetState(); + static void search(BuildContext context) { } + static Widget factory() { return SharedPageWidget(); } +} +class SharedPageWidgetState extends State { + + @override Widget build(BuildContext context) { + Future.delayed(Duration(milliseconds: 100), () { + showDialog(context: context, builder: (BuildContext ctx) => AlertDialog( + titlePadding: EdgeInsets.zero, + insetPadding: EdgeInsets.zero, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(0)), + title:NewBoxSharedWidget())); + }); + return Expanded( + child : Column( children: [ + Container( + height: 50, + color: const Color.fromRGBO(38, 166, 154, 1), + width: MediaQuery.of(context).size.width, + ), + Row( + children: [ + Container( + color: Colors.grey, + height: MediaQuery.of(context).size.height - HeaderConstants.height - 50, + width: 50, + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 10), + child: const Column( + children: [ + Tooltip( message: "dashboard", + child: InkWell( mouseCursor: SystemMouseCursors.click, + child: Padding( padding: EdgeInsets.symmetric(vertical: 10), child : Icon(Icons.dashboard, color: Colors.white, size: 20)))), + Tooltip( message: "shared workspaces", + child: InkWell( mouseCursor: SystemMouseCursors.click, + child: Padding( padding: EdgeInsets.symmetric(vertical: 10), child : Icon(Icons.workspaces, color: Colors.white, size: 20)))), + Tooltip( message: "shared workflows", + child: InkWell( mouseCursor: SystemMouseCursors.click, + child: Padding( padding: EdgeInsets.symmetric(vertical: 10), child : Icon(Icons.rebase_edit, color: Colors.white, size: 20)))), + ]) + ), + ] + ) ]) + ); + } +} \ No newline at end of file diff --git a/lib/widgets/dialog/new_box_shared.dart b/lib/widgets/dialog/new_box_shared.dart new file mode 100644 index 0000000..06feeea --- /dev/null +++ b/lib/widgets/dialog/new_box_shared.dart @@ -0,0 +1,207 @@ +import 'package:flutter/material.dart'; +import 'package:oc_front/models/response.dart'; +import 'package:oc_front/core/services/router.dart'; +import 'package:oc_front/core/services/specialized_services/shared_service.dart'; + +class NewBoxSharedWidget extends StatefulWidget { + String? _selected; + SharedService service = SharedService(); + final TextEditingController _ctrl = TextEditingController(); + final TextEditingController _ctrlDescr = TextEditingController(); + NewBoxSharedWidget ({ super.key, }); + @override NewBoxSharedWidgetState createState() => NewBoxSharedWidgetState(); +} +class NewBoxSharedWidgetState extends State { + GlobalKey key = GlobalKey(); + GlobalKey key2 = GlobalKey(); + @override Widget build(BuildContext context) { + return Container( + color: Colors.white, + padding: const EdgeInsets.only( top: 0, bottom: 20, left: 20, right: 20), + child: Column( + children: [ + Container( + alignment: Alignment.centerRight, + height: 50, + child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: + Text("load or create a new shared workspace", style: TextStyle(color: Colors.grey, fontSize: 15) + )), + Padding ( padding: const EdgeInsets.symmetric(horizontal: 10), child: + Tooltip( message: "back", child: InkWell( + mouseCursor: SystemMouseCursors.click, + onTap: () { + AppRouter.catalog.go(context, {}); + }, + child: const Icon(Icons.arrow_back, color: Colors.black))), + ), + Row ( mainAxisAlignment: MainAxisAlignment.end, children: [ + Tooltip( message: "close", child: InkWell( + mouseCursor: SystemMouseCursors.click, + onTap: () { Navigator.pop(context); }, + child: const Icon(Icons.close, color: Colors.black))), + ]), + ],), + ), + FutureBuilder>( + future: widget.service.all(context), + builder: (context, snapshot) { + List items = []; + if (snapshot.data != null && snapshot.data!.data != null) { + for (var item in snapshot.data!.data!.values) { + items.add(DropdownMenuItem( + value: item["id"].toString(), + child: Text(item["name"].toString()), + )); + } + } + if (widget._selected != null + && !items.where((element) => element.value == widget._selected).isNotEmpty) { + items.add(DropdownMenuItem( + value: widget._selected.toString(), + child: Text(widget._selected.toString()), + )); + } + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children : [ + SizedBox( width: MediaQuery.of(context).size.width <= 540 ? MediaQuery.of(context).size.width - 140 : 400, height: 50, + child: DropdownButtonFormField( + value: widget._selected, + isExpanded: true, + hint: const Text("load shared workspace...", style: TextStyle(color: Colors.grey, fontSize: 15)), + decoration: InputDecoration( + filled: true, + focusedBorder: const OutlineInputBorder( borderRadius: BorderRadius.zero, + borderSide: BorderSide(color: Color.fromARGB(38, 166, 154, 1), width: 0), + ), + fillColor: Colors.grey.shade300, + contentPadding: const EdgeInsets.only(left: 30, right: 30, top: 10, bottom: 30), + enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.zero, + borderSide: BorderSide(color: Colors.grey.shade300, width: 0), + ), + border: OutlineInputBorder( borderRadius: BorderRadius.zero, + borderSide: BorderSide(color: Colors.grey.shade300, width: 0)), + ), + items: items, + onChanged: (value) { + setState(() { + widget._selected = value.toString(); + }); + })), + Tooltip( + message: 'empty selection', + child: InkWell( + mouseCursor: widget._selected == null || widget._selected!.isEmpty ? MouseCursor.defer : SystemMouseCursors.click, + onTap: () { + if (widget._selected == null || widget._selected!.isEmpty) { return; } + setState(() { widget._selected = null; }); + }, + child: Container( + width: 50, height: 50, + decoration: const BoxDecoration( color: Colors.black, + border: Border(right: BorderSide(color: Colors.white))), + child: Icon(Icons.refresh, color: widget._selected == null || widget._selected!.isEmpty ? Colors.grey : Colors.white), + ) + ) + ), + Tooltip( + message: 'load shared workspace selected', + child: InkWell( + mouseCursor: widget._selected == null || widget._selected!.isEmpty + ? MouseCursor.defer : SystemMouseCursors.click, + onTap: () async { + }, + child: Container( + width: 50, height: 50, + color: Colors.black, + child: Icon(Icons.open_in_browser_outlined, + color: widget._selected == null || widget._selected!.isEmpty ? Colors.grey : Colors.white), + ) + ) + ) + ]);}), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.only(top: 10), + width: MediaQuery.of(context).size.width <= 540 ? MediaQuery.of(context).size.width - 90 : 450, + height: 50, + child: TextFormField( key: key, + expands: true, + maxLines: null, + minLines: null, + cursorColor: const Color.fromARGB(38, 166, 154, 1), + controller: widget._ctrl, + onChanged: (value) { widget._ctrl.text = value; }, + validator: (value) => value == null || value.isEmpty ? "name is required" : null, + decoration: InputDecoration( + hintText: "name a new shared workspace...", + fillColor: Colors.grey.shade300, + filled: true, + errorStyle: const TextStyle(fontSize: 0), + contentPadding: const EdgeInsets.only(left: 30, right: 30, top: 15, bottom: 5), + hintStyle: const TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w300 + ), + border: InputBorder.none + ) + ) + ), + Tooltip( + message: 'add', + child:InkWell( + mouseCursor: widget._ctrl.value.text.isEmpty ? MouseCursor.defer : SystemMouseCursors.click, + onTap: () async { + if (key.currentState!.validate() && key2.currentState!.validate()) { + await widget.service.post(context, { + "name" :widget._ctrl.value.text, + "description" : widget._ctrlDescr.value.text }, {}); + } + }, + child: Container( + margin: const EdgeInsets.only(top: 10), + width: 50, + height: 50, + color: Colors.black, + child: Icon(Icons.add, color: widget._ctrl.value.text.isEmpty ? Colors.grey : Colors.white) + ) + ) + ) + ]), + Container( + margin: const EdgeInsets.only(top: 10), + width: MediaQuery.of(context).size.width <= 540 ? MediaQuery.of(context).size.width - 40 : 500, + height: 50, + child: TextFormField( key: key2, + expands: true, + maxLines: null, + minLines: null, + cursorColor: const Color.fromARGB(38, 166, 154, 1), + controller: widget._ctrlDescr, + onChanged: (value) { widget._ctrlDescr.text = value; }, + validator: (value) => value == null || value.isEmpty ? "name is required" : null, + decoration: InputDecoration( + hintText: "description of a new shared workspace...", + fillColor: Colors.grey.shade300, + filled: true, + errorStyle: const TextStyle(fontSize: 0), + contentPadding: const EdgeInsets.only(left: 30, right: 30, top: 15, bottom: 5), + hintStyle: const TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w300 + ), + border: InputBorder.none + ) + ) + ), + ] + ) + ); + } +} \ No newline at end of file diff --git a/lib/widgets/forms/scheduler_forms.dart b/lib/widgets/forms/scheduler_forms.dart index 147ba19..916d05e 100644 --- a/lib/widgets/forms/scheduler_forms.dart +++ b/lib/widgets/forms/scheduler_forms.dart @@ -330,6 +330,7 @@ class SchedulerFormsWidgetState extends State { Tooltip( message: "check booking", child: InkWell( mouseCursor: SystemMouseCursors.click, onTap: () { + if (dash.scheduleActive) { return; } if (dash.scheduler["start"] == null ) { DateTime now = DateTime.now().add(const Duration(minutes: 5)); dash.scheduler["start"] = now.toUtc().toIso8601String(); @@ -359,10 +360,11 @@ class SchedulerFormsWidgetState extends State { ); }, child: Container( margin: const EdgeInsets.all(10), decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), - border: Border.all(color: widget.booking == null ? Colors.black : (widget.booking == true ? Colors.green : Colors.red), width: 1)), + border: Border.all(color: widget.booking == null && !dash.scheduleActive ? Colors.grey : (widget.booking == true || dash.scheduleActive ? Colors.green : Colors.red), width: 1)), width: 140, height: 30, child: Icon( - Icons.verified_outlined, color:widget.booking == null ? Colors.black : (widget.booking == true? Colors.green : Colors.red)), + Icons.verified_outlined, + color: widget.booking == null && !dash.scheduleActive ? Colors.grey : (widget.booking == true || dash.scheduleActive ? Colors.green : Colors.red)), )) ), Tooltip( message: dash.scheduleActive ? "unbook" : "book", @@ -376,9 +378,12 @@ class SchedulerFormsWidgetState extends State { } else { k.currentState!.save();} } } - if (dash.scheduler["start"] == null ) { - DateTime now = DateTime.now().add(const Duration(minutes: 5)); + DateTime now = DateTime.now().add(const Duration(minutes: 5)); + if (dash.scheduler["start"] == null || DateTime.parse(dash.scheduler["start"]!).isBefore(now)) { dash.scheduler["start"] = now.toUtc().toIso8601String(); + if (dash.scheduler["end"] != null) { + dash.scheduler["end"] = now.add(const Duration(minutes: 1)).toUtc().toIso8601String(); + } } widget.item.save!(widget.item.id); setState(() { }); diff --git a/lib/widgets/menu_clipper/clipper_menu.dart b/lib/widgets/menu_clipper/clipper_menu.dart index 02e1c06..918342b 100644 --- a/lib/widgets/menu_clipper/clipper_menu.dart +++ b/lib/widgets/menu_clipper/clipper_menu.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:oc_front/core/sections/header/menu.dart'; import 'package:oc_front/core/services/router.dart'; import 'package:oc_front/widgets/menu_clipper/arrow_clipper.dart';