diff --git a/images/project.png b/images/project.png new file mode 100644 index 0000000..bf22297 Binary files /dev/null and b/images/project.png differ diff --git a/lib/Activities.dart b/lib/Activities.dart index 68e4c0d..66f31a2 100644 --- a/lib/Activities.dart +++ b/lib/Activities.dart @@ -78,30 +78,37 @@ class _ActivitiesState extends State { label: Text("New Activity"), icon: Icon(Icons.add)), appBar: AppBar( + toolbarHeight: (searching) ? 90 : null, title: (searching) - ? Row( + ? Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: TextField(onChanged: (text){setState(() { + children: [ + Expanded( + child: TextField(onChanged: (text){setState(() { - });},controller: searchController, focusNode: _focus, decoration: InputDecoration( - filled: true, - ),), + });},controller: searchController, focusNode: _focus, decoration: InputDecoration( + filled: true, + ),), + ), + InkWell( + onTap: (){searching=false; + searchController.clear(); + setState(() { + + });}, + child: Container( + margin: EdgeInsets.all( 10), + child: Icon(Icons.cancel), + ), + ) + ], ), - InkWell( - onTap: (){searching=false; - searchController.clear(); - setState(() { - - });}, - child: Container( - margin: EdgeInsets.all( 10), - child: Icon(Icons.cancel), - ), - ) - ], - ) + Text('searched time : ${Main.MinutesToTimeString(searchTime)}',style: TextStyle(fontSize: 15),) + ], + ) : Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -175,7 +182,7 @@ class _ActivitiesState extends State { )); } - + int searchTime = 0; List PrintTasks() { List _tasks = []; print('Priting cats : ' + User.taskTypes.length.toString()); @@ -211,6 +218,7 @@ class _ActivitiesState extends State { } print(productivtyActs); //for (var element in User.activities) { + searchTime=0; for(int i =0; i < User.activities.length; i++){ Activity element =User.activities[i]; if(searching){ @@ -219,6 +227,7 @@ class _ActivitiesState extends State { bool matchCategory = element.taskType.cat!.name.toLowerCase().contains(searchController.text.toLowerCase()); if(matchMetadata || matchTaskType || matchCategory){ //Good to go + searchTime += element.endTime.difference(element.startTime).inMinutes; }else{ continue; } @@ -464,10 +473,19 @@ class _ActivitiesState extends State { SizedBox( width: 20, ), - Container( - padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), - decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: (productive) ? Colors.green : Colors.red), - child: Text(activity.taskType.cat?.name ?? 'n/a')) + Row( + children: [ + (activity.taskType.relatedProject!= null) ?Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: Colors.black26), + child: Text(activity.taskType.relatedProject!.name ?? 'n/a')) : Container(), + SizedBox(width: 10,), + Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: (productive) ? Colors.green : Colors.red), + child: Text(activity.taskType.cat?.name ?? 'n/a')), + ], + ) // Icon(Icons.circle, // color: (productive) // ? Colors.green diff --git a/lib/CustomWidgets.dart b/lib/CustomWidgets.dart index 3aa1da0..3edc5f9 100644 --- a/lib/CustomWidgets.dart +++ b/lib/CustomWidgets.dart @@ -103,7 +103,7 @@ class HourglassPainter extends CustomPainter{ ..strokeCap=StrokeCap.round ..strokeWidth=1; - print('bottom: $bottomStartHeight, top: $bottomEndHeight'); + // print('bottom: $bottomStartHeight, top: $bottomEndHeight'); canvas.drawPath(fallingSand, contentPainter); canvas.drawPath(topContent, contentPainter); diff --git a/lib/NewProject.dart b/lib/NewProject.dart index f08f315..484b27a 100644 --- a/lib/NewProject.dart +++ b/lib/NewProject.dart @@ -225,6 +225,7 @@ class _NewProject2State extends State { Widget build(BuildContext context) { List stepsWidgets = printSteps(); + etaHours =0; steps.forEach((element) { etaHours+=element.eta; }); diff --git a/lib/NewTask.dart b/lib/NewTask.dart index 590fadd..414fb35 100644 --- a/lib/NewTask.dart +++ b/lib/NewTask.dart @@ -3,16 +3,19 @@ import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; import 'package:intl/intl.dart'; import 'package:tasktracker/NewCategory.dart'; import 'package:tasktracker/NewProject.dart'; +import 'Data.dart'; import 'User.dart' as User; DateFormat dateFormat = DateFormat("yyyy-MM-dd HH:mm:ss"); DateFormat durationFormat = DateFormat("HH:mm:ss"); class NewTask extends StatefulWidget { - const NewTask({Key? key}) : super(key: key); - + NewTask({Key? key, this.t_cat, this.t_name, this.t_relProj}) : super(key: key); + late String? t_name; + late String? t_cat; + late String? t_relProj; @override - _NewTaskState createState() => _NewTaskState(); + _NewTaskState createState() => _NewTaskState(t_cat: t_cat, t_name: t_name, t_relProj: t_relProj); } List getCategoryNames(){ @@ -39,6 +42,19 @@ List getProjectNames(){ String selectedCat = User.categories[0].name; String selectedProj = "None"; class _NewTaskState extends State { + bool editing = false; + String? oldName; + _NewTaskState({String? t_name, String? t_cat, String? t_relProj}){ + if(t_name !=null && t_cat != null){ + editing = true; + oldName = t_name; + } + + nameController.text = t_name ?? ''; + selectedCat = t_cat ?? User.categories[0].name; + selectedProj = t_relProj ?? 'None'; + } + TextEditingController nameController = TextEditingController(); bool productive = true; @@ -46,7 +62,7 @@ class _NewTaskState extends State { Widget build(BuildContext context) { List cats = getCategoryNames(); return Scaffold( - appBar: AppBar(title: Text('New Task Type')), + appBar: AppBar(title: Text('${(editing) ? 'Edit ' : 'New '} Task Type')), body: Container( height: MediaQuery.of(context).size.height, child: Column( @@ -99,7 +115,7 @@ class _NewTaskState extends State { child: Text(value), ); }).toList(), - onChanged: (String? _value) { + onChanged:(selectedProj == 'None') ? (String? _value) { if(_value != null) { if (_value.contains("+Add New Category")) { Navigator.of(context).push(MaterialPageRoute(builder: (context)=>NewCategory())); @@ -110,7 +126,7 @@ class _NewTaskState extends State { setState(() { }); - })), + } : null)), Padding( padding: const EdgeInsets.fromLTRB(0, 20, 0, 10), child: Text('Related Project'), @@ -138,7 +154,7 @@ class _NewTaskState extends State { child: Text(value), ); }).toList(), - onChanged: (String? _value) { + onChanged: (String? _value) async{ if(_value != null) { if (_value.contains("+Add New Project")) { Navigator.of(context).push(MaterialPageRoute(builder: (context)=>NewProject())); @@ -146,6 +162,13 @@ class _NewTaskState extends State { selectedProj = _value!; if(_value.contains("None")){ + }else{ + Project? relProj = await User.getProjectFromId(selectedProj); + if(relProj==null){ + + }else{ + selectedCat = relProj.cat!.name; + } } } } @@ -195,10 +218,14 @@ class _NewTaskState extends State { ), onPressed: () { setState(() { - add_action(); + if(editing){ + edit_action(); + }else { + add_action(); + } }); }, - child: Text('Add Task Type', + child: Text((editing) ? 'Apply' : 'Add Task Type', style: TextStyle(fontSize: 20))))), ], )) @@ -217,6 +244,19 @@ class _NewTaskState extends State { return route.isFirst; }); } + + void edit_action() async{ + String catName = nameController.value.text; + print('editing Task Type $oldName => : $catName, $selectedCat'); + if(catName.length< 2){ + showAlertDialog(context, 'Category needs a name', 'Please enter a name for this category'); + return; + } + await User.UserOperations.editTaskType(oldName! ,catName,selectedCat,relatedProject: (selectedProj == 'None') ? '' : selectedProj); + Navigator.of(context).popUntil((route){ + return route.isFirst; + }); + } } String _printDuration(Duration duration) { diff --git a/lib/ProjectDetails.dart b/lib/ProjectDetails.dart new file mode 100644 index 0000000..82c8fb1 --- /dev/null +++ b/lib/ProjectDetails.dart @@ -0,0 +1,142 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +import 'Data.dart'; + +class ProjectDetails extends StatefulWidget { + const ProjectDetails({Key? key}) : super(key: key); + + @override + State createState() => _ProjectDetailsState(); +} + +class _ProjectDetailsState extends State { + @override + Widget build(BuildContext context) { + + List stepsWidgets = printSteps(); + etaHours =0; + steps.forEach((element) { + etaHours+=element.eta; + }); + + + return Scaffold( + appBar: AppBar(title: Row( + children: [ + FaIcon(FontAwesomeIcons.projectDiagram), + SizedBox(width: 15,), + Text('This app'), + ], + )), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + + Card(child:LimitedBox( + maxHeight: 300, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.white10, + ), + 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') + )), + ), + ),), + + 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,)) + ], + )); + + // _steps.add(title); + // _steps.add(Divider()); + for (int i = 0; i < steps.length; i++) { + ProjectStep value = 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, + )) + ], + )))); + } + + return _steps; + } +} diff --git a/lib/Projects.dart b/lib/Projects.dart index e616edd..8ca0876 100644 --- a/lib/Projects.dart +++ b/lib/Projects.dart @@ -1,8 +1,11 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; import 'package:tasktracker/Data.dart'; import 'package:tasktracker/NewProject.dart'; +import 'package:tasktracker/ProjectDetails.dart'; import 'Dialogs.dart'; import 'User.dart' as User; import 'main.dart'; @@ -53,6 +56,7 @@ class _ProjectsState extends State { BottomNavigationBarItem(icon: FaIcon(FontAwesomeIcons.newspaper),label:'Summary'), BottomNavigationBarItem(icon: FaIcon(FontAwesomeIcons.info),label:'Details'), ], + currentIndex:selectedPage, onTap: (val){ selectedPage= val; setState(() { @@ -94,13 +98,97 @@ class _ProjectsState extends State { ], )), drawer: navDrawer(context, 7), - body: (selectedPage == 0) ? - Container(child: Column( + body: + (User.projects.isEmpty)? Container(child: Image.asset(('images/project.png'),color: Colors.white.withOpacity(0.6), colorBlendMode: BlendMode.modulate,)) : + (selectedPage == 0) ? + Container( + padding: EdgeInsets.all(15), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, children: [ + Card(child: + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Row( + children: [ + FaIcon(FontAwesomeIcons.cogs, color: Colors.yellow), + SizedBox(width: 15,), + Text('Active Projects (${User.projects.length})',style:TextStyle(fontSize: 18)), + ], + ), + Divider(), + Container( + height: 100, + child: ListView.builder( + itemCount: User.projects.length, + itemBuilder: (context,index){ + return InkWell( + onTap: (){ + Navigator.of(context).push(MaterialPageRoute(builder: (context)=> ProjectDetails())); + }, + child: Container( + decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(10)), + padding:EdgeInsets.all(8), + margin: EdgeInsets.all(1), + child: + Row(mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(flex:3,child: Text(User.projects[index].name)), + Expanded(flex:2,child: Text('20% [200h]')), + Expanded( + flex:2, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: User.projects[index].cat!.productive ? Colors.green : Colors.redAccent, + ), + padding:EdgeInsets.symmetric(horizontal: 8), + child:Text(User.projects[index].cat!.name) + ) + ],), + ) + ], + ) + ), + ); + } + ), + ) + ], + ), + )), + SizedBox(height: 30,), + Card(child: + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Row( + children: [ + FaIcon(FontAwesomeIcons.check,color: Colors.green,), + SizedBox(width: 15,), + Text('Finished Projects (0)',style:TextStyle(fontSize: 18)), + ], + ), + Divider(), + Container( + height: 50, + ) + + ], + ), + )), ], )) - :Container( + : + Container( padding: EdgeInsets.all(10), child: Column( children: [ diff --git a/lib/Tasks.dart b/lib/Tasks.dart index 65ddf62..e33f6ee 100644 --- a/lib/Tasks.dart +++ b/lib/Tasks.dart @@ -167,6 +167,7 @@ class _TasksState extends State { children: [ (relatedProjects.isNotEmpty) ? Container( + margin: EdgeInsets.symmetric(horizontal: 5), padding: EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: Colors.black26), @@ -185,6 +186,18 @@ class _TasksState extends State { Container(margin: EdgeInsets.fromLTRB(15, 0, 15, 10), height: 2, color: color) ]), ), + (selecting) + ? InkWell( + child:Container(margin:EdgeInsets.all(8),child: Icon(Icons.edit)), + onTap: () { + print('edit $name : $catName : $relatedProjects'); + + Navigator.of(context).push(MaterialPageRoute(builder: (context)=> NewTask(t_name: name, t_cat: catName, t_relProj: (relatedProjects.isEmpty)? null : relatedProjects,))).then((value){setState(() { + + });}); + + }) + : Container(), ]); } diff --git a/lib/User.dart b/lib/User.dart index 5c610c6..abbb2ea 100644 --- a/lib/User.dart +++ b/lib/User.dart @@ -898,6 +898,43 @@ class UserOperations { } } + static Future editTaskType(String oldName, String name, String category, {bool bulk = false, String? relatedProject = null}) async { + Map queryBody = { + 'id': username + oldName, + 'username': username, + 'name': name, + 'category': username + category, + 'related_project' :(relatedProject==null) ? '' : (username + relatedProject) + }; + + if (cacheEnabled) { + //Add Query + Map query = {Queries.colLink: 'edit_taskType', Queries.colData: jsonEncode(queryBody)}; + + print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); + + await cacheDb.insert('Queries', query); + + //update Cache + await cacheDb.rawUpdate("UPDATE TaskTypes SET ${TaskType.colId}='${username+name}', ${TaskType.colName}='$name', ${TaskType.colCategory}='${username+category}', ${TaskType.colRelatedProject}='${(relatedProject == 'None') ? '' : relatedProject}' WHERE id='${username+oldName}'"); + await refreshUserData(forceOffline: true); + } else { + try { + http.Response queryResponse = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/add_taskType.php'), body: queryBody)); + print("Query executed : Results{${queryResponse.body}"); + if (queryResponse.body.toLowerCase().contains("success")) { + //Success + } + } catch (e) { + print('NC: Error editing task $e}'); + } + } + if (!bulk) { + //Add to server and refresh Cache + await executeQueries(); + } + } + static Future addActivity(String type, DateTime sTime, DateTime eTime, {String metadata = 'null', bool bulk = false, Function(int)? onOverlap}) async { Map queryBody = { @@ -1098,7 +1135,7 @@ class UserOperations { print('NC: Error adding prjct $e}'); } } - + UserOperations.addTaskType(name, category, relatedProject: name); await executeQueries(); } diff --git a/lib/main.dart b/lib/main.dart index 6a43a7c..86f32eb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -208,14 +208,23 @@ class _MyHomePageState extends State { hourglassStops = []; hourglassTotalTime=0; hourglassCatData.forEach((element) { - if(element.time > hourglassTotalTime){ - hourglassTotalTime=element.time; - } + // if(element.time > hourglassTotalTime){ + hourglassTotalTime+=element.time; + // } }); - hourglassCatData.forEach((element) { + print('hourglass cat data'); + double stopsTotal = 0; + for(int i =0 ; i < hourglassCatData.length; i++) { + CatMapData element = hourglassCatData[i]; + print('${element.name} : ${element.time} / $hourglassTotalTime = ${element.time / hourglassTotalTime}'); + double thisStop = ( element.time/hourglassTotalTime); hourglassColors.add(element.color); - hourglassStops.add(element.time/hourglassTotalTime); - }); + hourglassStops.add(stopsTotal+thisStop); + stopsTotal += thisStop; + if(i < hourglassCatData.length-1){hourglassColors.add(hourglassCatData[i+1].color); + hourglassStops.add(stopsTotal+thisStop + 0.001);} + } + print('total Stops ${stopsTotal}'); print('maxT: $hourglassTotalTime'); if(hourglassColors.isEmpty){ hourglassColors.add(Colors.black); @@ -272,15 +281,23 @@ class _MyHomePageState extends State { ongoingActName = ""; } - setState(() { + if(mounted) { + setState(() { }); + } } - + bool loadingStats = false; void LoadStats() async { // return; // await User.refreshUserData(); + if(loadingStats){ + print('loading stats already'); + return; + }else { + loadingStats=true; + } await Refresh(); DateFormat dFormat = DateFormat("MM/dd"); @@ -434,8 +451,9 @@ class _MyHomePageState extends State { int prodActs = (productivtyActs[element] ?? 0); int unprodActs = (unproductivtyActs[element] ?? 0); double prod = (untrackedUnprod) ? ((prodActs / 1440) * 100) : ((prodActs / unprodActs)*100); - if(prod>0){ - productivityData.add(ProductivityMapData(element, prod)); + var newProdData = ProductivityMapData(element, prod); + if(prod>0 && !productivityData.contains(newProdData)){ + productivityData.add(newProdData); } // } } @@ -462,7 +480,11 @@ class _MyHomePageState extends State { }); } - print('prodData : $productivityData'); + // print('productivity data'); + // productivityData.forEach((element) { + // print(element.day); + // }); + loadingStats=false; // loadingStats=false; } diff --git a/lib/newActivity.dart b/lib/newActivity.dart index aa51853..7ac2da4 100644 --- a/lib/newActivity.dart +++ b/lib/newActivity.dart @@ -58,7 +58,8 @@ class _NewActivity extends State { if(_cats.contains(element.name)){ }else{ - _cats.add(name);} + _cats.add(name + ((element.relatedProject !=null) ? ' [${element.relatedProject!.name}]' :'')); + } }); return _cats; } @@ -112,7 +113,9 @@ class _NewActivity extends State { });}); }else{ + selectedCat = _value ?? 'n/a'; + } setState(() { }); @@ -398,9 +401,14 @@ class _NewActivity extends State { if(startTime.isAfter(endTime)){ showAlertDialog(context, 'Really?', 'Start time and end time doesnt make any sense'); } - print('adding Task Type : $selectedCat at $startTime - $endTime'); + String selectedTasks = selectedCat; + if(selectedTasks.contains('[') && selectedTasks.contains(']')){ + selectedTasks = selectedTasks.substring(0, selectedTasks.indexOf('[')-1); + print('Project task : $selectedTasks'); + } + print('adding Task Type : $selectedTasks at $startTime - $endTime'); bool failed=false; - await User.UserOperations.addActivity(selectedCat,startTime, endTime,metadata:metadataController.text, onOverlap: (overlapCount){ + await User.UserOperations.addActivity(selectedTasks,startTime, endTime,metadata:metadataController.text, onOverlap: (overlapCount){ showAlertDialog(context, 'Error adding activity', 'Cannot add activity between ${dateFormat.format(startTime)} - ${dateFormat.format(endTime)}, $overlapCount activities are already added within this time range'); failed=true; }); @@ -416,9 +424,14 @@ class _NewActivity extends State { } void edit_action() async{ - print('adding Task Type : $selectedCat at $startTime - $endTime'); + String selectedTasks = selectedCat; + if(selectedTasks.contains('[') && selectedTasks.contains(']')){ + selectedTasks = selectedTasks.substring(0, selectedTasks.indexOf('[')-1); + print('Project task : $selectedTasks'); + } + print('adding Task Type : $selectedTasks at $startTime - $endTime'); bool failed=false; - await User.UserOperations.editActivity(init_sTime,init_eTime,selectedCat,startTime, endTime,metadata:metadataController.text, onOverlap: (overlapCount){ + await User.UserOperations.editActivity(init_sTime,init_eTime,selectedTasks,startTime, endTime,metadata:metadataController.text, onOverlap: (overlapCount){ showAlertDialog(context, 'Error editing activity', 'Cannot add activity between ${dateFormat.format(startTime)} - ${dateFormat.format(endTime)}, $overlapCount activities are already added within this time range'); failed=true; });