diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 872ece0..6fbdf13 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,4 +1,6 @@
+
+
+
diff --git a/lib/backend/DataManager.dart b/lib/backend/DataManager.dart
index 446d25e..49d9a3a 100644
--- a/lib/backend/DataManager.dart
+++ b/lib/backend/DataManager.dart
@@ -1,9 +1,11 @@
+import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:queue_mgr/backend/DebugHelper.dart';
+import 'package:queue_mgr/backend/Dialogs.dart';
// DateFormat dateFormat;
final String API_ENDPOINT= "https://vps.playpoolstudios.com/qms/api/";
@@ -16,6 +18,7 @@ class DataManager{
m_instance ??= _DataManager();
return m_instance!;
}
+
}
@@ -37,6 +40,10 @@ class _DataManager{
Debug.LogError(e);
}
+ await ProcessServices();
+ }
+
+ Future ProcessServices() async{
List _services= [];
for (var s in services) {
Map se = jsonDecode(s);
@@ -45,7 +52,19 @@ class _DataManager{
continue;
}
- _services.add(s);
+ try{
+ var response = (await http.post(
+ Uri.parse('${API_ENDPOINT}get_appointments.php'),
+ body: {
+ 'serviceId':se['id']
+ }));
+
+ se.putIfAbsent("appointments", () => jsonDecode(response.body.toString()));
+ }catch(e){
+ Debug.LogError(e);
+ }
+
+ _services.add(se);
}
services = _services;
}
@@ -66,11 +85,73 @@ class _DataManager{
}catch(e){
Debug.LogError(e);
}
+ await ProcessServices();
+
+ }
+
+ Future DeleteService(String id) async {
+ try{
+ var response = (await http.post(
+ Uri.parse('${API_ENDPOINT}remove_service.php'),
+ body: {
+ 'id':id,
+ }));
+ Debug.LogResponse(response.body.toString());
+ services = jsonDecode(response.body.toString());
+ Debug.LogResponse(services);
+ }catch(e){
+ Debug.LogError(e);
+ }
+ await ProcessServices();
+ }
+
+ Future EditService(String id,String name, DateTime sTime, DateTime eTime,Duration duration) async {
+ try{
+ var response = (await http.post(
+ Uri.parse('${API_ENDPOINT}edit_service.php'),
+ body: {
+ 'id':id,
+ 'name':name,
+ 'stime':sTime.toString(),
+ 'etime':eTime.toString(),
+ 'duration':"${duration.ToHoursAndMinutes()}"
+ }));
+ Debug.LogResponse(response.body.toString());
+ services = jsonDecode(response.body.toString());
+ Debug.LogResponse(services);
+ }catch(e){
+ Debug.LogError(e);
+ }
+ await ProcessServices();
+
+ }
+
+ Future CompleteService(dynamic serviceId, dynamic tokenId) async {
+ String res = "";
+ Debug.Log("Completing $tokenId from Service $serviceId");
+ try{
+ var response = (await http.post(
+ Uri.parse('${API_ENDPOINT}complete_token.php'),
+ body: {
+ 'service_id':serviceId.toString(),
+ 'user_id':tokenId.toString(),
+ }));
+ res = response.body.toString();
+ Debug.LogResponse(response.body.toString());
+ services = jsonDecode(response.body.toString());
+ await ProcessServices();
+
+ Debug.LogResponse(services);
+ return "0";
+ }catch(e){
+ Debug.LogError(e);
+ return res;
+ }
}
Map GetServiceById(String id){
for (var service in services) {
- Map s = jsonDecode(service.toString());
+ Map s = (service);
if(s['id']==id){
return s;
@@ -80,9 +161,19 @@ class _DataManager{
}
}
+class Helpers{
+ static Duration ParseDuration(String input){
+ if(input.contains(":")){
+ return Duration(hours: int.parse(input.split(":")[0]), minutes: int.parse(input.split(":")[1]));
+ }
+
+ return Duration(minutes: 0);
+ }
+}
extension DurationExtensions on Duration{
String ToHoursAndMinutes(){
- return "${this.inHours}:${this.inMinutes}";
+ int mins = this.inMinutes %60;
+ return "${this.inHours}:${mins}";
}
}
\ No newline at end of file
diff --git a/lib/backend/DebugHelper.dart b/lib/backend/DebugHelper.dart
index 89a0c61..bf6dc40 100644
--- a/lib/backend/DebugHelper.dart
+++ b/lib/backend/DebugHelper.dart
@@ -15,6 +15,8 @@ class Debug{
static void LogError(Object? msg){
if(!enableLogging){return;}
if(!enableErrorLoggin) {return;}
+ print(StackTrace.current);
+
print('\x1B[31m$msg\x1B[0m');
}
diff --git a/lib/backend/Dialogs.dart b/lib/backend/Dialogs.dart
index fe7efe7..0d5ed14 100644
--- a/lib/backend/Dialogs.dart
+++ b/lib/backend/Dialogs.dart
@@ -42,8 +42,7 @@ class Dialogs{
}
static bool showing = false;
- static BuildContext? context;
- static waiting(){
+ static waiting(BuildContext context){
showing=true;
// context=navigatorKey.currentContext;
if(context!=null) {
@@ -68,6 +67,36 @@ class Dialogs{
);
}
}
+ static Future AskQuestion(BuildContext context, String title, String message) async{
+ bool yes = false;
+
+ AlertDialog alert = AlertDialog(
+ backgroundColor: Color(0xFF1F1F1F),
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)),
+ title: Text(title,textAlign: TextAlign.center,),
+ content: Text(message,textAlign: TextAlign.center,),
+ actions: [
+ TextButton(onPressed: (){
+ yes=true;
+ Navigator.of(context).pop();
+ }, child: Text("Yes")),
+ TextButton(onPressed: (){
+ Navigator.of(context).pop();
+ }, child: Text("No")),
+ ],
+
+ );
+
+ // show the dialog
+ await showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return alert;
+ },
+ );
+
+ return yes;
+ }
static showDurationPicker(BuildContext context, String title) {
// set up the button
@@ -103,11 +132,12 @@ class Dialogs{
},
);
}
-// static hide(){
-// showing=false;
-// Navigator.of(navigatorKey.currentContext!).popUntil((route){
-// return route.settings.name!="Progress";
-// });
-// }
+
+ static hide(BuildContext context){
+ showing=false;
+ Navigator.of(context).popUntil((route){
+ return route.settings.name!="Progress";
+ });
+ }
}
diff --git a/lib/main.dart b/lib/main.dart
index 2681163..4663dd0 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,3 +1,4 @@
+import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
@@ -38,12 +39,24 @@ class Home extends StatefulWidget {
class _HomeState extends State {
+
+ Timer timer = Timer(Duration(hours: 1), () { });
@override
void initState() {
// TODO: implement initState
super.initState();
refresh();
+ timer = Timer.periodic(const Duration(seconds: 10), (timer) {
+ refresh();
+ });
+ }
+
+ @override
+ void dispose() {
+ // TODO: implement dispose
+ timer.cancel();
+ super.dispose();
}
void refresh()async{
@@ -66,10 +79,12 @@ class _HomeState extends State {
shrinkWrap: true,
itemCount:DataManager.instance().services.length,
itemBuilder: (context,index){
- Map service = jsonDecode(DataManager.instance().services[index]);
+ Map service = (DataManager.instance().services[index]);
return InkWell(
onTap: (){
- Navigator.of(context).push(MaterialPageRoute(builder: (context)=> ServicePage(serviceId: service['id'])));
+ Navigator.of(context).push(MaterialPageRoute(builder: (context)=> ServicePage(serviceId: service['id']))).then((value){setState(() {
+
+ });});
},
child: Card(
child: Center(
@@ -80,7 +95,7 @@ class _HomeState extends State {
children: [
Text(service['start_time']),
Text(service['name']),
- Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(50),color: Colors.deepPurple), child: SizedBox(width:20,height: 20,child: Center(child: Text(service['members'].toString().split(',').length.toString()))),)
+ Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(50),color: Colors.deepPurple), child: SizedBox(width:20,height: 20,child: Center(child: Text((service['appointments'].length).toString()))),)
],
),
),
@@ -92,7 +107,9 @@ class _HomeState extends State {
SizedBox(height: 50,),
InkWell(
onTap: (){
- Navigator.of(context).push(MaterialPageRoute(builder: (context)=> NewService()));
+ Navigator.of(context).push(MaterialPageRoute(builder: (context)=> NewService())).then((value) {setState(() {
+
+ });});
},
child: Card(
child: Container(
diff --git a/lib/new_service.dart b/lib/new_service.dart
index a27e5bd..39cc3ea 100644
--- a/lib/new_service.dart
+++ b/lib/new_service.dart
@@ -1,10 +1,11 @@
import 'package:duration_picker/duration_picker.dart';
import 'package:flutter/material.dart';
import 'package:queue_mgr/backend/DataManager.dart';
+import 'package:queue_mgr/backend/Dialogs.dart';
class NewService extends StatefulWidget {
- const NewService({Key? key}) : super(key: key);
-
+ NewService({Key? key,this.id}) : super(key: key);
+ String? id;
@override
State createState() => _NewServiceState();
}
@@ -17,10 +18,31 @@ class _NewServiceState extends State {
TimeOfDay eTime = TimeOfDay(hour: 2, minute: 2);
Duration duration = Duration(hours:0, minutes: 15);
+ @override
+ void initState() {
+ // TODO: implement initState
+ super.initState();
+ bool isEdit = widget.id != null;
+
+ if(isEdit){
+ Map service = DataManager.instance().GetServiceById(widget.id.toString());
+ nameController.text = service['name'];
+ sDate = DateTime.parse(service['start_time']);
+ sTime = TimeOfDay.fromDateTime(sDate);
+ eDate = DateTime.parse(service['end_time']);
+ eTime = TimeOfDay.fromDateTime(eDate);
+ duration = Helpers.ParseDuration(service['duration']);
+ setState(() {
+
+ });
+ }
+ }
+
@override
Widget build(BuildContext context) {
+ bool isEdit = widget.id != null;
return Scaffold(
- appBar: AppBar(title: Text("New Service")),
+ appBar: AppBar(title: Text(isEdit? "Edit Service" :"New Service")),
body: Container(
padding: EdgeInsets.all(20),
child: Column(
@@ -55,7 +77,7 @@ class _NewServiceState extends State {
ElevatedButton(
child: Padding(
padding: const EdgeInsets.all(8.0),
- child: Text("${sTime.hour}:${sTime.minute}"),
+ child: Text(sTime.format(context)),
),
onPressed: () async{
sTime = await showTimePicker(context: context, initialTime: sTime) ?? sTime;
@@ -89,7 +111,7 @@ class _NewServiceState extends State {
ElevatedButton(
child: Padding(
padding: const EdgeInsets.all(8.0),
- child: Text("${eTime.hour}:${eTime.minute}"),
+ child: Text(eTime.format(context)),
),
onPressed: () async{
eTime = await showTimePicker(context: context, initialTime: eTime) ?? eTime;
@@ -104,10 +126,10 @@ class _NewServiceState extends State {
ListTile(
title:Text("Session Duration"),
subtitle: ElevatedButton(
- child: Text("${duration.inHours}:${duration.inMinutes}"),
+ child: Text("${duration.ToHoursAndMinutes()}"),
onPressed: () async{
// duration = await showTimePicker(context: context, initialTime: duration) ?? duration;
- duration = await showDurationPicker(context: context, initialTime: Duration(minutes: 15)) ?? Duration(minutes: 15);
+ duration = await showDurationPicker(context: context, initialTime: duration) ?? Duration(minutes: 15);
setState(() {
});
@@ -116,17 +138,52 @@ class _NewServiceState extends State {
)
],
),
- Container(margin: EdgeInsets.all(20),width:200,height:50,child: ElevatedButton(onPressed: () async{
- DateTime s_date = DateTime(sDate.year, sDate.month, sDate.day, sTime.hour, sTime.minute);
- DateTime e_date = DateTime(eDate.year, eDate.month, eDate.day, eTime.hour, eTime.minute);
- await DataManager.instance().AddService(nameController.text, s_date, e_date,duration);
- setState(() {
+ Column(
+ children: [
+ Container(width:200,height:50,child: ElevatedButton(onPressed: () async{
- });
+ Dialogs.waiting(context);
+ DateTime s_date = DateTime(sDate.year, sDate.month, sDate.day, sTime.hour, sTime.minute);
+ DateTime e_date = DateTime(eDate.year, eDate.month, eDate.day, eTime.hour, eTime.minute);
+ if(isEdit){
+ await DataManager.instance().EditService(widget.id.toString(),nameController.text, s_date, e_date,duration);
+ }else{
+ await DataManager.instance().AddService(nameController.text, s_date, e_date,duration);
+ }
+ setState(() {
- Navigator.pop(context);
+ });
+ Dialogs.hide(context);
+
+ Navigator.pop(context);
+
+ }, child: Text(isEdit ? "Save" : "Add"))),
+
+ (isEdit) ? Container(margin: EdgeInsets.all(20),width:200,height:50,child: ElevatedButton(onPressed: () async{
+ bool confirm = await Dialogs.AskQuestion(context, "CAUTION", "Are you sure to delete this service?");
+ if(!confirm){return;}
+
+ Dialogs.waiting(context);
+
+ await DataManager.instance().DeleteService(widget.id.toString());
+
+ setState(() {
+
+ });
+ Dialogs.hide(context);
+
+ Navigator.pop(context);
+
+ }, child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(Icons.delete),
+ Text(" Delete"),
+ ],
+ ),style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent, foregroundColor: Colors.white),)) : Container(),
+ ],
+ ),
- }, child: Text("Add")))
],
),
),
diff --git a/lib/scanning_page.dart b/lib/scanning_page.dart
index e66958a..66c4403 100644
--- a/lib/scanning_page.dart
+++ b/lib/scanning_page.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:queue_mgr/backend/DataManager.dart';
+import 'package:queue_mgr/backend/Dialogs.dart';
class ScanningPage extends StatefulWidget {
ScanningPage({super.key,required this.id});
@@ -65,7 +66,21 @@ class _ScanningPageState extends State {
padding: const EdgeInsets.all(18.0),
child: Text("Clear",style: TextStyle(fontSize: 15)),
),),
- ElevatedButton(onPressed: (){}, child: Padding(
+ ElevatedButton(onPressed: () async{
+ Dialogs.waiting(context);
+ String result = await DataManager.instance().CompleteService(widget.id, tokenID);
+ tokenID=0;
+ setState(() {
+
+ });
+ Dialogs.hide(context);
+
+ if(result == "0"){
+ //success
+ }else{
+ Dialogs.showAlertDialog(context, "Error", result);
+ }
+ }, child: Padding(
padding: const EdgeInsets.all(18.0),
child: Text("Complete",style: TextStyle(fontSize: 15)),
))
diff --git a/lib/service_info.dart b/lib/service_info.dart
index 294044b..0fda75a 100644
--- a/lib/service_info.dart
+++ b/lib/service_info.dart
@@ -1,5 +1,11 @@
+import 'dart:async';
+import 'dart:convert';
+
import 'package:flutter/material.dart';
import 'package:queue_mgr/backend/DataManager.dart';
+import 'package:queue_mgr/backend/DebugHelper.dart';
+import 'package:queue_mgr/backend/Dialogs.dart';
+import 'package:queue_mgr/new_service.dart';
import 'package:queue_mgr/scanning_page.dart';
class ServicePage extends StatefulWidget {
@@ -10,13 +16,62 @@ class ServicePage extends StatefulWidget {
}
class _ServicePageState extends State {
+
+ Timer timer = Timer(Duration(hours: 1), () { });
+ @override
+ void initState() {
+ // TODO: implement initState
+ super.initState();
+ timer = Timer.periodic(const Duration(seconds: 10), (timer) {
+ refresh();
+ });
+ }
+
+ void refresh() async{
+ await DataManager.instance().GetData();
+ setState(() {
+
+ });
+ }
+
+ @override
+ void dispose() {
+ // TODO: implement dispose
+ timer.cancel();
+ Debug.Log("Cancelling timer");
+ super.dispose();
+ }
+
@override
Widget build(BuildContext context) {
Map service = DataManager.instance().GetServiceById(widget.serviceId.toString());
- List members = service['members'].toString().split(',');
- members.removeAt(0);
+ // List members = service['members'].toString().split(',');
+ // members.removeAt(0);
+ List appointments = service['appointments'];
+ Debug.Log(appointments);
return Scaffold(
- appBar: AppBar(title: Text(service['name']),),
+ appBar: AppBar(title: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(service['name']),
+ InkWell(
+ onTap: (){
+ Navigator.of(context).push(MaterialPageRoute(builder:(_)=> NewService(id: widget.serviceId))).then((value) {
+ Map _service = DataManager.instance().GetServiceById(widget.serviceId.toString());
+ if(_service == null || _service.isEmpty){
+ Navigator.of(context).pop();
+ }
+ setState(() {
+
+ });});
+ },
+ child: Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: Icon(Icons.edit),
+ ),
+ )
+ ],
+ ),),
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
@@ -66,7 +121,7 @@ class _ServicePageState extends State {
Text(" Queue Length"),
],
),
- Text(members.length.toString(),style: TextStyle(fontSize: 25),)
+ Text(appointments.length.toString(),style: TextStyle(fontSize: 25),)
],
),
),
@@ -74,8 +129,10 @@ class _ServicePageState extends State {
SizedBox(
height: 50,
),
- ElevatedButton(onPressed: (){
- Navigator.of(context).push(MaterialPageRoute(builder: (context)=> ScanningPage(id: widget.serviceId)));
+ ElevatedButton(onPressed: () async{
+ Navigator.of(context).push(MaterialPageRoute(builder: (context)=> ScanningPage(id: widget.serviceId))).then((value){setState(() {
+
+ });});
}, child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
@@ -87,7 +144,7 @@ class _ServicePageState extends State {
),
)),
SizedBox(height: 50,),
- (members.length >0) ? Card(
+ (appointments.length >0) ? Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
@@ -102,12 +159,29 @@ class _ServicePageState extends State {
mainAxisSpacing: 10,
crossAxisSpacing: 10,
shrinkWrap: true,
- children: List.generate(members.length, (index){
- if(members[index].length <=0){return Container();}
- return Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(20),color: Colors.black.withOpacity(0.2)),child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Center(child: Text(members[index],style: TextStyle(fontSize: 18),)),
- ));
+ children: List.generate(appointments.length, (index){
+ Map appointment = jsonDecode(appointments[index].toString());
+ Debug.Log(appointment);
+
+ bool past = DateTime.parse(service['start_time']).add(Helpers.ParseDuration(service['duration'])).isBefore(DateTime.now());
+ return InkWell(
+ onTap: () async{
+ bool confirm = await Dialogs.AskQuestion(context, "Complete Token ${appointment['id']}", "Press Yes to remove this token from queue");
+
+ if(confirm){
+ Dialogs.waiting(context);
+ await DataManager.instance().CompleteService(service['id'],appointment['user_id']);
+ Dialogs.hide(context);
+ }
+ setState(() {
+
+ });
+ },
+ child: Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(20),color: (past) ? Colors.red.withOpacity(0.2) :Colors.black.withOpacity(0.2)),child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Center(child: Text(appointment['appointment_id'],style: TextStyle(fontSize: 18),)),
+ )),
+ );
}),
),
],