Skip to content

Commit

Permalink
Merge pull request #278 from IDEMSInternational/feat/habit-tracker
Browse files Browse the repository at this point in the history
implement habit history and tracker
  • Loading branch information
chrismclarke authored Dec 23, 2020
2 parents 1f46038 + 91de207 commit 4c6f3c7
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 49 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "parenting-app-ui",
"version": "0.6.5",
"version": "0.7.0",
"author": "IDEMS International",
"homepage": "https://idems.international/",
"description": "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { ITask, ITaskWithMeta } from "../../models/goals.model";
*ngIf="showTask"
button
[class.task-complete]="isComplete"
(click)="handleTaskClicked()"
>
<span>{{ _task.label }}</span>
<ion-checkbox slot="end" [checked]="isComplete" *ngIf="!_task.start_action"></ion-checkbox>
Expand Down Expand Up @@ -54,32 +53,4 @@ export class TaskReminderItemComponent {
}
return false;
}

/**
* When a task is clicked create database entry to log task start, and then
* run any associated task actions.
* On action completion log to database completion status.
*/
async handleTaskClicked() {
// const { start_action, start_action_args } = this._task;
// const task_id = this._task.id;
// const timestamp = new Date().toISOString();
// const id = generateRandomId();
// if (start_action) {
// console.log("starting action", start_action);
// // TODO - possibly add field for grouping start/complete,
// // or keeping as single entry and updating summary data (e.g. abandoned)
// await this.taskActions.addTaskAction({ id, task_id, timestamp, status: "STARTED" });
// // TODO - add action commands and handle callbacks
// const status = await this.taskActions.runAction(start_action, start_action_args);
// if (status === "COMPLETED") {
// await this.taskActions.addTaskAction({ id, task_id, timestamp, status: "COMPLETED" });
// // TODO - calculate local state updates instead of refreshing full data set
// location.reload();
// }
// } else {
// this.taskActions.addTaskAction({ id, task_id, timestamp, status: "COMPLETED" });
// this.isComplete = true;
// }
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ModalController } from "@ionic/angular";
import { FlowTypes } from "scripts/types";
import { HABIT_LIST } from "src/app/shared/services/data/data.service";
import { TaskActionService } from "src/app/shared/services/task/task-action.service";
import { TaskService } from "src/app/shared/services/task/task.service";
import { generateTimestamp } from "src/app/shared/utils";

@Component({
selector: "plh-habit-add",
Expand All @@ -20,7 +24,7 @@ import { HABIT_LIST } from "src/app/shared/services/data/data.service";
<div
*ngFor="let habit of habits; index as i"
class="habit-card"
(click)="toggleHabit(i)"
(click)="selectHabit(habit)"
[class.complete]="habit._complete"
>
<div class="habit-image-container">
Expand Down Expand Up @@ -77,11 +81,47 @@ import { HABIT_LIST } from "src/app/shared/services/data/data.service";
`,
],
})
export class HabitAddComponent {
export class HabitAddComponent implements OnInit {
habits = HABIT_LIST[0].rows;
constructor(public modalCtrl: ModalController) {}
constructor(
public modalCtrl: ModalController,
private taskService: TaskService,
private taskActionService: TaskActionService
) {}

toggleHabit(index: number) {
this.habits[index]._complete = this.habits[index]._complete ? false : true;
async ngOnInit() {
await this.evaluateCompletedHabits();
}

async selectHabit(habit: FlowTypes.Habit_listRow) {
const { task_id, id, _complete } = habit;
if (!_complete) {
// TODO - ideally habit tasks will be refactor to start flow from service and automate completion tracking,
// (uncomment line below to enable) but for nowlog task completion manually

// this.taskService.startTask(task_id);
await this.taskActionService.recordTaskAction({
task_id,
type: "completed",
meta: { habit_id: id },
});
// TODO - add more efficient bindings for evaluating tasks
await this.evaluateCompletedHabits();
}
}

/**
* Check what habits have been marked as completed in the current calendar day
* and marke as completed in the list
*/
private async evaluateCompletedHabits() {
const timestamp = generateTimestamp();
const todayString = timestamp.substring(0, 12);
const completedTasks = await this.taskActionService.table
.where("_created")
.startsWith(todayString)
.toArray();
const uniqueTaskIds = [...new Set(completedTasks.map((t) => t.task_id))];
this.habits = this.habits.map((h) => ({ ...h, _complete: uniqueTaskIds.includes(h.task_id) }));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ModalController } from "@ionic/angular";
import { FlowTypes } from "scripts/types";
import { HABIT_LIST } from "src/app/shared/services/data/data.service";
import { TaskActionService } from "src/app/shared/services/task/task-action.service";
import { arrayToHashmap } from "src/app/shared/utils";
import { HabitAddComponent } from "./habit-add";

@Component({
Expand All @@ -11,6 +15,11 @@ import { HabitAddComponent } from "./habit-add";
<ion-button class="add-button" fill="outline" (click)="addHabit()">
<ion-icon slot="icon-only" name="add"></ion-icon>
</ion-button>
<div class="habit-history-container">
<div *ngFor="let habit of habitHistory" class="habit-history-item">
<img class="habit-history-icon" [src]="habit.icon_asset" />
</div>
</div>
</div>
</div>
`,
Expand All @@ -29,6 +38,24 @@ import { HabitAddComponent } from "./habit-add";
.habit-list {
display: flex;
align-items: center;
overflow: auto;
}
.habit-history-container {
flex: 1;
overflow: auto;
overflow-y: hidden;
white-space: nowrap;
}
.habit-history-item {
margin-left: 5px;
width: 50px;
height: 50px;
display: inline-block;
}
.habit-history-icon {
object-fit: cover;
width: 100%;
height: 100%;
}
ion-button {
Expand All @@ -47,15 +74,40 @@ import { HabitAddComponent } from "./habit-add";
`,
],
})
export class HabitTrackerComponent {
constructor(private modalCtrl: ModalController) {}
export class HabitTrackerComponent implements OnInit {
habitHistory: (FlowTypes.Habit_listRow & { _created: string })[] = [];
constructor(private modalCtrl: ModalController, private taskActionService: TaskActionService) {}

async ngOnInit() {
await this.loadHabitHistory();
}
/** Load a modal to view the habit add modal. On close refresh data */
async addHabit() {
const modal = await this.modalCtrl.create({
component: HabitAddComponent,
backdropDismiss: false,
});
await modal.present();
const { data, role } = await modal.onDidDismiss();
console.log("habit dismissed");
await this.loadHabitHistory();
}

/**
* Look up all completed tasks and find those which correspond to the task
* launched from a habit. Return as an array of habit instances
*/
private async loadHabitHistory() {
const history = [];
const habitListByTaskID = arrayToHashmap(HABIT_LIST[0].rows, "task_id");
const completedTasks = await this.taskActionService.table
.orderBy("_created")
.reverse()
.toArray();
completedTasks.forEach((t) => {
if (t._completed && habitListByTaskID.hasOwnProperty(t.task_id)) {
history.push({ ...habitListByTaskID[t.task_id], _created: t._created });
}
});
this.habitHistory = history;
}
}
14 changes: 5 additions & 9 deletions src/app/shared/services/task/task-action.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Injectable } from "@angular/core";
import { format } from "date-fns";

import { Subject } from "scripts/node_modules/rxjs";
import { environment } from "src/environments/environment";
import { generateTimestamp } from "../../utils";
import { DbService } from "../db/db.service";

@Injectable({ providedIn: "root" })
Expand Down Expand Up @@ -81,7 +82,7 @@ export class TaskActionService {
default:
// task_id no longer required as tracked in parent
delete action.task_id;
const actionEntry: ITaskActionEntry = { _created: this._generateTimestamp(), ...action };
const actionEntry: ITaskActionEntry = { _created: generateTimestamp(), ...action };
if (meta) {
action.meta = meta;
}
Expand All @@ -101,7 +102,7 @@ export class TaskActionService {
dbEntry._completed = true;
dbEntry._duration = this._calculateTaskDuration(dbEntry);
}
dbEntry.actions.push({ _created: this._generateTimestamp(), ...action });
dbEntry.actions.push({ _created: generateTimestamp(), ...action });
await this.db.table("session_actions").put(dbEntry, dbEntry.id);
}

Expand All @@ -122,7 +123,7 @@ export class TaskActionService {
}

private createNewEntry(task_id: string) {
const timestamp = this._generateTimestamp();
const timestamp = generateTimestamp();
const entry: ITaskEntry = {
id: `${task_id}_${timestamp}`,
task_id,
Expand All @@ -135,11 +136,6 @@ export class TaskActionService {
return entry;
}

/** generate a string representation of the current datetime in local timezone */
private _generateTimestamp() {
return format(new Date(), "yyyy-MM-dd'T'HH:mm:ss");
}

/**
*
* Note 1 - we will not create these using hostlistners or unload them as it is expected they will
Expand Down
10 changes: 10 additions & 0 deletions src/app/shared/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { format } from "date-fns";

/**
* Generate a random string of characters in base-36 (a-z and 0-9 characters)
* @returns uxokjq8co1n
Expand All @@ -7,6 +9,14 @@ export function generateRandomId() {
return Math.random().toString(36).substring(2);
}

/**
* generate a string representation of the current datetime in local timezone
* @returns 2020-12-22T18:15:20
*/
export function generateTimestamp() {
return format(new Date(), "yyyy-MM-dd'T'HH:mm:ss");
}

/**
* Convert an object array into a json object, with keys corresponding to array entries
* @param keyfield any unique field which all array objects contain to use as hash keys (e.g. 'id')
Expand Down

0 comments on commit 4c6f3c7

Please sign in to comment.