diff --git a/lib/Data.dart b/lib/Data.dart index b8cc8e7..85d7656 100644 --- a/lib/Data.dart +++ b/lib/Data.dart @@ -127,17 +127,19 @@ class Project{ class ProjectStep{ ProjectStep(this.stepName,this.eta,this.progress); - ProjectStep.fromJson(Map json): stepName=json['name'], eta=json['eta'], progress=json['progress']; + ProjectStep.fromJson(Map json): stepName=json['name'], eta=json['eta'], progress=json['progress'], finishedDate=(json['finishedDate']!=null) ? DateTime.parse(json['finishedDate']) : null; Map toJson()=>{ 'name': stepName, 'eta':eta, - 'progress':progress + 'progress':progress, + if(finishedDate!=null) 'finishedDate':finishedDate.toString() }; String stepName; int eta; int progress; + DateTime? finishedDate; } diff --git a/lib/Dialogs.dart b/lib/Dialogs.dart index f63bd13..7ff465e 100644 --- a/lib/Dialogs.dart +++ b/lib/Dialogs.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -186,6 +187,81 @@ class Dialogs{ } } + static completeStepDialog(Project project,Function(DateTime) onComplete, ProjectStep step) async{ + context=navigatorKey.currentContext; + DateTime finishedDate = DateTime.now(); + DateFormat dateFormat = DateFormat('yyyy-MM-dd'); + if(context!=null) { + // return StatefulBuilder(builder: (context, setState) { + return showDialog( + context: context!, + builder: (BuildContext context) { + return StatefulBuilder(builder: (context, setState) + { + return AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), + backgroundColor: Color(0xFF1C1C1C), + title: Text('Completed ${step.stepName}?'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('When did you complete?'), + SizedBox(height: 20,), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + onTap: () { + DatePicker.showDatePicker(context, + showTitleActions: true, + theme: DatePickerTheme(), onChanged: (date) { + // print('change $date'); + }, onConfirm: (date) { + setState(() { + finishedDate = date; + }); + }, currentTime: finishedDate, locale: LocaleType.en); + setState(() {}); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.black26, + borderRadius: BorderRadius.circular(15) + ), + padding: EdgeInsets.symmetric(horizontal: 25, vertical: 10), + child: Text((dateFormat.format(finishedDate) ==dateFormat.format(DateTime.now())) ? 'Today' : dateFormat.format(finishedDate), style: TextStyle(color: Colors.blue),), + ), + ), + ], + ) + ], + ), + actions: [ + MaterialButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text('No', style: TextStyle(color: Colors.red)), + ), + MaterialButton( + color: Colors.green, + onPressed: () { + User.UserOperations.CompleteProjectStep(project, step, finishedDate); + Navigator.of(context).pop(); + }, + child: Text('Complete', style: TextStyle(color: Colors.white)), + ), + ], + ); + }); + } + ); + }else{ + print('context is null'); + } + } + static hide(){ showing=false; Navigator.of(navigatorKey.currentContext!).popUntil((route){ diff --git a/lib/ProjectDetails.dart b/lib/ProjectDetails.dart index ee9f347..5c036e7 100644 --- a/lib/ProjectDetails.dart +++ b/lib/ProjectDetails.dart @@ -1,232 +1,412 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:tasktracker/NewProject.dart'; import 'package:tasktracker/Projects.dart'; import 'User.dart' as User; +import 'Dialogs.dart'; import 'Data.dart'; +import 'main.dart'; class ProjectDetails extends StatefulWidget { - ProjectDetails({Key? key, required this.project}) : super(key: key); + ProjectDetails({Key? key, required this.project}) : super(key: key); Project project; @override State createState() => _ProjectDetailsState(project); } -DateTime justDate(DateTime dateTime){ +DateTime justDate(DateTime dateTime) { return DateTime(dateTime.year, dateTime.month, dateTime.day); } class _ProjectDetailsState extends State { - _ProjectDetailsState(this.project); - Project project ; + Project project; int etaHours = 0; - List chartData = []; - + int totalTimeSpent = 0; + List timeSpentData = []; + List timeProgressionData = []; + String chartOption = 'Time Spent'; + DateTimeRange? chartRange; @override Widget build(BuildContext context) { - List stepsWidgets = printSteps(); - etaHours =0; + etaHours = (project.steps.length > 0) ? 0 : project.eta ?? 0; + totalTimeSpent = 0; project.steps.forEach((element) { - etaHours+=element.eta; + etaHours += element.eta; }); int lastIndex = -1; int lastDay = -1; - User.activities.forEach((element) { - if(element.taskType.relatedProject == project){ - if(lastDay != element.startTime.day){ - chartData.add(ProjectChartData(justDate(element.startTime), element.endTime.difference(element.startTime).inMinutes)); - lastIndex++; - lastDay = element.startTime.day; - }else{ - chartData[lastIndex].timeSpent += element.endTime.difference(element.startTime).inMinutes; + timeSpentData = []; + timeProgressionData = []; + DateTime? firstDateTime = null; + DateTime? lastDateTime = null; + User.activities.reversed.forEach((element) { + + bool withinRange = + (chartRange == null) || (chartRange != null && chartRange!.start.isBefore(element.startTime) && chartRange!.end.isAfter(element.endTime)); + if (element.taskType.relatedProject != null && element.taskType.relatedProject!.name == project.name) { + print('here'); + if (withinRange) { + if (firstDateTime == null) { + firstDateTime = element.startTime; + } + lastDateTime = element.startTime; + + if (lastDay != element.startTime.day) { + if (totalTimeSpent > 0) { + timeProgressionData.add(ProjectChartData(justDate(element.startTime), totalTimeSpent)); + } + timeSpentData.add(ProjectChartData(justDate(element.startTime), element.endTime.difference(element.startTime).inMinutes)); + lastIndex++; + lastDay = element.startTime.day; + print('new day : $lastDay'); + } else { + timeSpentData[lastIndex].timeSpent += element.endTime.difference(element.startTime).inMinutes; + } + } + totalTimeSpent += element.endTime.difference(element.startTime).inMinutes; } }); + chartRange ??= (lastDateTime != null && firstDateTime != null) ? DateTimeRange(end: lastDateTime!, start: firstDateTime!) : null; return Scaffold( - appBar: AppBar(title: Row( + appBar: AppBar( + title: Row( children: [ FaIcon(FontAwesomeIcons.projectDiagram), - SizedBox(width: 15,), + SizedBox( + width: 15, + ), Text(project.name), ], )), body: Padding( padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Card( + child: SingleChildScrollView( + child: Column( + children: [ + Card( + child: Padding( + padding: EdgeInsets.all(8), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + DropdownButton( + value: chartOption, + icon: const Icon(Icons.arrow_downward), + elevation: 16, + style: const TextStyle( + color: Colors.white, + ), + alignment: Alignment.center, + underline: Container( + height: 2, + color: Colors.deepPurpleAccent, + ), + onChanged: (String? newValue) { + setState(() { + chartOption = newValue!; + }); + }, + items: ['Time Spent', 'Time Progression', 'Steps Progression', 'Time & Steps Progression'] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + Row( + children: [ + FaIcon(FontAwesomeIcons.calendar), + SizedBox( + width: 8, + ), + InkWell( + onTap: () async { + DateTimeRange? value = await showDateRangePicker( + context: context, firstDate: firstDay ?? DateTime.now(), lastDate: lastDateTime ?? DateTime.now()); + if (value != null) { + chartRange = value; + } - ), - Card(child: Padding( - padding: EdgeInsets.all(8), - child: Column( - children: [ - Text("Time Management"), - SizedBox(height: 10,), - Padding( - padding: const EdgeInsets.all(5.0), - child: Row( - children: [ - Expanded(flex:1,child: Text('ETA :',textAlign: TextAlign.end,)), - Expanded(flex: 3,child: Text( '${etaHours} Hours',textAlign: TextAlign.center,),) - ], - ), - ), - Padding( - padding: const EdgeInsets.all(5.0), - child: Row( - children: [ - Expanded(flex:1,child: Text('Deadline :',textAlign: TextAlign.end,)), - Expanded(flex: 3,child: Text( '${DateFormat('yyyy-MM-dd').format(project.deadline)}',textAlign: TextAlign.center,),) - ], - ), - ), - Padding( - padding: const EdgeInsets.all(5.0), - child: Row( - children: [ - Expanded(flex:1,child: Text('Remaining :',textAlign: TextAlign.end,)), - Expanded(flex: 3,child: Text( '${durationToDays(project.deadline.difference(DateTime.now()))}',textAlign: TextAlign.center,),) - ], - ), - ), - - ], - ), - )), - Card(child:Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Text('Steps',), - LimitedBox( - maxHeight: 300, - child: Container( - - padding: EdgeInsets.all(10), - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: (stepsWidgets.isNotEmpty) ? Container( - child: Column( - children: stepsWidgets, + setState(() {}); + }, + child: Text((chartRange != null) + ? (DateFormat("MM/dd").format(chartRange!.start) + " - " + DateFormat("MM/dd").format(chartRange!.end)) + : 'n/a'), + ), + ], + ) + ], ), - ) : Container( - - height: 20, - child: Text('Click on + to add steps') - )), + ), + SfCartesianChart( + // Initialize category axis + primaryXAxis: CategoryAxis(), + series: >[ + if (chartOption == 'Time Spent') + LineSeries( + // Bind data source + dataSource: timeSpentData, + xValueMapper: (ProjectChartData sales, _) => DateFormat("MM/dd").format(sales.day), + yValueMapper: (ProjectChartData sales, _) => sales.timeSpent / 60, + dataLabelMapper: (ProjectChartData sales, _) => MinutesToTimeString(sales.timeSpent), + dataLabelSettings: DataLabelSettings(overflowMode: OverflowMode.hide, showZeroValue: false, isVisible: true), + onPointTap: (ChartPointDetails point) { + showAlertDialog(context, DateFormat("MM/dd").format(timeSpentData[point.pointIndex!].day), + "I'll show you detailed info about this day in future, When my master creates the feature"); + }, + color: Colors.blue), + if (chartOption == 'Time Progression') + LineSeries( + // Bind data source + dataSource: timeProgressionData, + xValueMapper: (ProjectChartData sales, _) => DateFormat("MM/dd").format(sales.day), + yValueMapper: (ProjectChartData sales, _) => sales.timeSpent / 60, + dataLabelMapper: (ProjectChartData sales, _) => MinutesToTimeString(sales.timeSpent), + dataLabelSettings: DataLabelSettings(overflowMode: OverflowMode.hide, showZeroValue: false, isVisible: true), + onPointTap: (ChartPointDetails point) { + showAlertDialog(context, DateFormat("MM/dd").format(timeSpentData[point.pointIndex!].day), + "I'll show you detailed info about this day in future, When my master creates the feature"); + }, + color: Colors.blue) + ]), + ], + ))), + Card( + child: Padding( + padding: EdgeInsets.all(8), + child: Column( + children: [ + Text("Time Management"), + SizedBox( + height: 10, ), - ), - Container( - padding: EdgeInsets.symmetric(horizontal: 40,vertical: 10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.black26, + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + Expanded( + flex: 1, + child: Text( + 'ETA :', + textAlign: TextAlign.end, + )), + Expanded( + flex: 3, + child: Text( + (etaHours > 0) ? '${etaHours} Hours' : 'You have no idea', + textAlign: TextAlign.center, + ), + ) + ], + ), ), + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + Expanded( + flex: 1, + child: Text( + 'Time Spent :', + textAlign: TextAlign.end, + )), + Expanded( + flex: 3, + child: Text( + (totalTimeSpent > 0) ? MinutesToTimeString(totalTimeSpent) : 'Not a single minute', + textAlign: TextAlign.center, + ), + ) + ], + ), + ), + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + Expanded( + flex: 1, + child: Text( + 'Deadline :', + textAlign: TextAlign.end, + )), + Expanded( + flex: 3, + child: Text( + '${DateFormat('yyyy-MM-dd').format(project.deadline)}', + textAlign: TextAlign.center, + ), + ) + ], + ), + ), + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + Expanded( + flex: 1, + child: Text( + 'Remaining :', + textAlign: TextAlign.end, + )), + Expanded( + flex: 3, + child: Text( + '${durationToDays(project.deadline.difference(DateTime.now()))}', + textAlign: TextAlign.center, + ), + ) + ], + ), + ), + ], + ), + )), + if (project.steps.length > 0) + Card( + child: Padding( + padding: const EdgeInsets.all(8.0), child: Column( children: [ - Text("Total time : $etaHours Hours"), + Text( + 'Steps', + ), + LimitedBox( + maxHeight: 300, + child: Container( + padding: EdgeInsets.all(10), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: (stepsWidgets.isNotEmpty) + ? Container( + child: Column( + children: stepsWidgets, + ), + ) + : Container(height: 20, child: Text('Click on + to add steps'))), + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 40, vertical: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.black26, + ), + child: Column( + children: [ + Text("Total time : $etaHours Hours"), + ], + ), + ), ], ), ), - ], - ), - ),), - - Card(child:Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Text('Actions'), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ElevatedButton(onPressed: (){}, child: Row( - children: [ - FaIcon(FontAwesomeIcons.edit), - SizedBox(width: 10,), - Text('Edit'), - ], - )), - ElevatedButton(style:ElevatedButton.styleFrom( - primary: Colors.red, - ),onPressed: (){}, child: Row( - children: [ - FaIcon(FontAwesomeIcons.deleteLeft), - SizedBox(width: 10,), - Text('Delete'), - ], - )) - ], - ), - ], - ), - )) - ], + ), + Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Text('Actions'), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () {}, + child: Row( + children: [ + FaIcon(FontAwesomeIcons.edit), + SizedBox( + width: 10, + ), + Text('Edit'), + ], + )), + ElevatedButton( + style: ElevatedButton.styleFrom( + primary: Colors.red, + ), + onPressed: () {}, + child: Row( + children: [ + FaIcon(FontAwesomeIcons.deleteLeft), + SizedBox( + width: 10, + ), + Text('Delete'), + ], + )) + ], + ), + ], + ), + )) + ], + ), ), - ) , + ), ); } + List printSteps() { List _steps = []; - - Widget title=Container( - - height: 30, - decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: Colors.white10), - padding: EdgeInsets.symmetric(horizontal: 10, vertical: 0), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded(flex: 4, child: Text('Name')), - // Expanded(flex:1,child: Icon(Icons.timelapse)), - Expanded(flex: 2, child: Text("Progress")), - Expanded( - flex: 3, - child: Text('ETA',textAlign: TextAlign.end,)) - ], - )); - + //Checkbox(value: false, onChanged: null), // _steps.add(title); // _steps.add(Divider()); for (int i = 0; i < project.steps.length; i++) { ProjectStep value = project.steps[i]; - _steps.add(InkWell( - child: Container( - height: 30, - decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color:Colors.black26), - padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded(flex: 4, child: Text(value.stepName)), - // Expanded(flex:1,child: Icon(Icons.timelapse)), - Expanded(flex: 1, child: Text("${value.progress}%")), - Expanded( - flex: 3, - child: Text( - value.eta.toString() + " Hours", - textAlign: TextAlign.end, - )) - ], - )))); + _steps.add(Row( + children: [ + InkWell(onTap: (){ + Dialogs.completeStepDialog(project,(date) { + User.UserOperations.CompleteProjectStep(project, project.steps[i], date); + }, project.steps[i]); + },child: Container(margin:EdgeInsets.all(2),child: Icon((project.steps[i].finishedDate!= null) ?Icons.check_box : Icons.check_box_outline_blank))), + Expanded( + child: Container( + height: 34, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Colors.black26), + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(flex: 4, child: Text(value.stepName)), + // Expanded(flex:1,child: Icon(Icons.timelapse)), + Expanded(flex: 1, child: Text("${value.progress}%")), + Expanded( + flex: 3, + child: Text( + value.eta.toString() + " Hours", + textAlign: TextAlign.end, + )) + ], + )), + ), + ], + )); } return _steps; } } - -class ProjectChartData{ - +class ProjectChartData { ProjectChartData(this.day, this.timeSpent); DateTime day; int timeSpent; -} \ No newline at end of file +} diff --git a/lib/User.dart b/lib/User.dart index abbb2ea..4d33380 100644 --- a/lib/User.dart +++ b/lib/User.dart @@ -649,14 +649,14 @@ Future> GetProjects(bool forceOffline) async { } List cats = await cacheDb.rawQuery('SELECT * FROM Projects'); - print(cats.length); + print('projects : ${cats.length}'); for (Map element in cats) { String? name = element[Project.colName]; String? category = element[Project.colCat]; String? stepsJson = element[Project.colSteps]; String? deadline = element[Project.colDeadline]; int? eta= element[Project.colEta]; - + print(name); if (name == null || category == null || stepsJson == null || deadline == null || eta==null) { print("Something is null!\nname:${name == null}, cat:${category == null}, steps:${stepsJson == null}, deadline${deadline == null}"); @@ -1139,6 +1139,47 @@ class UserOperations { await executeQueries(); } + static Future CompleteProjectStep(Project project, ProjectStep step, DateTime finishedDate) async { + + project.steps.forEach((element) { + if(element.stepName == step.stepName){ + element.finishedDate = finishedDate; + } + }); + + Map queryBody = { + 'name': username+project.name, + 'username': username, + 'steps': jsonEncode(project.steps), + }; + + if (cacheEnabled) { + //Add Query + Map query = {Queries.colLink: 'update_projectSteps', Queries.colData: jsonEncode(queryBody)}; + + print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); + + await cacheDb.insert('Queries', query); + await cacheDb.rawUpdate("UPDATE Projects SET steps='${jsonEncode(project.steps)}' WHERE ${Project.colName}='${project.name}'"); + //update Cache + // Map data = {Project.colName: username+name, Project.colCat: category, Project.colSteps: jsonEncode(steps),Project.colEta: eta, Project.colDeadline: deadline.toString()}; + // await cacheDb.insert('Projects', data); + await refreshUserData(forceOffline: true); + } else { + try { + http.Response queryResponse = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/update_projectSteps.php'), body: queryBody)); + print("Query executed : Results{${queryResponse.body}"); + if (queryResponse.body.toLowerCase().contains("success")) { + //Success + } + } catch (e) { + print('NC: Error completing prjctStep $e}'); + } + } + + await executeQueries(); + } + static Future deleteTask(String name, {bulk = false}) async { Map queryBody = { 'id': username + name,