diff --git a/package.json b/package.json index 255966a..2e4ca37 100755 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "push-release": "git push --follow-tags origin main", "prepare": "husky install", "container:run": "docker compose -f docker-compose.yml up", - "container:build": "docker build -t mfaouzi ." + "container:build": "docker build -t trakz ." }, "private": true, "dependencies": { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4bba1b6..2044bd5 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -3,6 +3,7 @@ import { LayoutModule } from '@angular/cdk/layout'; import { DatePipe, NgOptimizedImage } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatLineModule } from '@angular/material/core'; import { MatIconModule } from '@angular/material/icon'; @@ -93,6 +94,7 @@ import { TaskGroupListComponent } from './pages/tasks/task-group-list/task-group NgOptimizedImage, HttpClientModule, MatLineModule, + FormsModule, ], providers: [DatePipe], bootstrap: [AppComponent], diff --git a/src/app/layouts/main-layout/right-sidebar/right-sidebar.component.html b/src/app/layouts/main-layout/right-sidebar/right-sidebar.component.html index 8b07c5e..6b1cafe 100755 --- a/src/app/layouts/main-layout/right-sidebar/right-sidebar.component.html +++ b/src/app/layouts/main-layout/right-sidebar/right-sidebar.component.html @@ -90,7 +90,10 @@ > - +
{ - if (task) { - const taskId = taskGeneratedId(task); - // If clicked on the same task, close the right sidebar - if (taskId === this.currentTaskId) { - this.onRightSidebarClose(); - return; - } - this.task = task; - this.currentTaskId = taskId; + if (!task) { + this.shouldShowNoteTextarea = false; + return; + } + this.shouldShowNoteTextarea = false; + this.shouldShowNoteTextarea = true; + const taskId = taskGeneratedId(task); + if (taskId === this.currentTaskId) { + this.onRightSidebarClose(); + return; } + this.task = task; + this.currentTaskId = taskId; }); } @@ -85,6 +90,7 @@ export class RightSidebarComponent implements OnDestroy, OnInit { } onRightSidebarClose() { + this.shouldShowNoteTextarea = false; this._tasksService.setSelectedTask(null); this.task = undefined; this.currentTaskId = undefined; diff --git a/src/app/layouts/main-layout/right-sidebar/task-note/task-note.component.html b/src/app/layouts/main-layout/right-sidebar/task-note/task-note.component.html index 705062a..d734669 100755 --- a/src/app/layouts/main-layout/right-sidebar/task-note/task-note.component.html +++ b/src/app/layouts/main-layout/right-sidebar/task-note/task-note.component.html @@ -2,14 +2,25 @@ class="bg-inherit !flex border-gray-300 focus-within:border-gray-400 rounded border !flex-col" > {{ task.note?.content || '' }} - + - - Updated on {{ note.updatedAt | date : 'EEE, MMM d' }} + + Last saved {{ getLastUpdateDate() }} + + + Saving...
diff --git a/src/app/layouts/main-layout/right-sidebar/task-note/task-note.component.ts b/src/app/layouts/main-layout/right-sidebar/task-note/task-note.component.ts index e4d97aa..7d4ed3c 100755 --- a/src/app/layouts/main-layout/right-sidebar/task-note/task-note.component.ts +++ b/src/app/layouts/main-layout/right-sidebar/task-note/task-note.component.ts @@ -1,12 +1,49 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; +import { debounceTime, distinctUntilChanged, Subject, switchMap } from 'rxjs'; -import { TaskNote } from '@/models/task'; +import { Task } from '@/models/task'; +import { TaskService } from '@/services/tasks/task.service'; +import { passedTimeFormatted } from '@/utils/trakzUtils'; @Component({ selector: 'app-task-note', templateUrl: './task-note.component.html', styleUrls: ['./task-note.component.scss'], }) -export class TaskNoteComponent { - @Input() note: TaskNote | undefined; +export class TaskNoteComponent implements OnInit { + @Input() task: Task = {} as Task; + + withRefresh = false; + + isTaskBeingUpdate = false; + + private typedNoteText$ = new Subject(); + + constructor(private _tasksService: TaskService) {} + + ngOnInit() { + this.typedNoteText$ + .pipe( + debounceTime(1000), + distinctUntilChanged(), + switchMap((content) => { + this.task.note.content = content; + this.isTaskBeingUpdate = true; + return this._tasksService.updateTask(this.task); + }), + ) + .subscribe((task) => { + this.task = task; + this.isTaskBeingUpdate = false; + }); + } + + onUserTyping(event: Event) { + const content = (event.target as HTMLTextAreaElement).value; + this.typedNoteText$.next(content); + } + + getLastUpdateDate() { + return passedTimeFormatted(this.task.note.updatedAt); + } } diff --git a/src/app/models/task.ts b/src/app/models/task.ts index 1ef1891..d5e34c8 100755 --- a/src/app/models/task.ts +++ b/src/app/models/task.ts @@ -26,8 +26,8 @@ export interface TaskStep extends TimeStampsDate { export interface TaskNote { content: string | ''; - createdAt?: TimeStampsDate['createdAt']; - updatedAt?: TimeStampsDate['updatedAt']; + createdAt: TimeStampsDate['createdAt']; + updatedAt: TimeStampsDate['updatedAt']; } export interface Folder { @@ -48,7 +48,7 @@ export interface Task extends TimeStampsDate { isCompleted: boolean; steps: TaskStep[]; recurrence?: Recurrence | null; - note?: TaskNote; + note: TaskNote; } export enum TaskStatus { diff --git a/src/app/pages/my-day/add-task-input/add-task-input.component.ts b/src/app/pages/my-day/add-task-input/add-task-input.component.ts index b037ce1..4a90379 100755 --- a/src/app/pages/my-day/add-task-input/add-task-input.component.ts +++ b/src/app/pages/my-day/add-task-input/add-task-input.component.ts @@ -65,6 +65,7 @@ export class AddTaskInputComponent implements OnInit { .subscribe((task) => { // eslint-disable-next-line no-param-reassign taskInput.value = ''; + // scroll to the task that was just added to the list of tasks in the DOM setTimeout(() => { AddTaskInputComponent.scrollInToNewAddedTask(task); diff --git a/src/app/services/tasks/task.service.ts b/src/app/services/tasks/task.service.ts index 1d58491..11719c7 100755 --- a/src/app/services/tasks/task.service.ts +++ b/src/app/services/tasks/task.service.ts @@ -331,7 +331,11 @@ export class TaskService { isImportant: isImportant ?? false, steps: steps ?? [], recurrence: recurrence ?? 'ONCE', - note: note ?? { content: '' }, + note: note ?? { + content: '', + updatedAt: new Date(), + createdAt: new Date(), + }, }; } @@ -363,6 +367,12 @@ export class TaskService { ); }; + // private _updateTaskNote$ = (note: string, taskID: number) => { + // return this.http.patch(`${this.apiUrl}/tasks/${taskID}/note`, { + // content: note, + // }); + // }; + private _updateTask$ = (task: Task): Observable => { const { id } = task; const url = `${this.apiUrl}/tasks/${id}`; @@ -429,6 +439,8 @@ export class TaskService { res.data.createdAt = newTask.createdAt; res.data.updatedAt = newTask.updatedAt; this.updateTask(res.data); + newTask.id = res.data.id; + this._snackBar.open('Task added successfully', 'Dismiss', { duration: 2000, }); diff --git a/src/app/utils/trakzUtils.ts b/src/app/utils/trakzUtils.ts index 435cead..8a78301 100755 --- a/src/app/utils/trakzUtils.ts +++ b/src/app/utils/trakzUtils.ts @@ -11,17 +11,22 @@ export function isOverdue(date: Date) { export function isToday(date: Date | string) { const today = new Date(); - const dueDate = new Date(date); + const comparedDate = new Date(date); return ( - dueDate.getDate() === today.getDate() && - dueDate.getMonth() === today.getMonth() && - dueDate.getFullYear() === today.getFullYear() && - dueDate.getHours() <= 23 && - dueDate.getMinutes() <= 59 && - dueDate.getSeconds() <= 59 + comparedDate.getDate() === today.getDate() && + comparedDate.getMonth() === today.getMonth() && + comparedDate.getFullYear() === today.getFullYear() && + comparedDate.getHours() <= 23 && + comparedDate.getMinutes() <= 59 && + comparedDate.getSeconds() <= 59 ); } +export function isThisYear(date: Date) { + const today = new Date(); + return date.getFullYear() === today.getFullYear(); +} + export function capitalizeFirstLetter(string: string) { return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); } @@ -152,3 +157,43 @@ export function taskGeneratedId(task: Task): string { const d2 = toDateString(task.updatedAt); return `${d1}${d2}${task.folderName}`; } + +/** + * - If today show time only (h:mm a) else show date and time (EEE, MMM d y, h:mm a) + * - If yesterday show 'Yesterday at ' + time (h:mm a) + * - If this year show date and time (EEE, MMM d, h:mm a) + * - else show date only (EEE, MMM d y) + * @example: Today at 2:30 PM, Yesterday at 2:30 PM, Mon, Jan 1, 2021, 2:30 PM, Mon, Jan 1, 2021 + * @param date + * @param locale + * */ +export function passedTimeFormatted(date: string | Date, locale = 'en-US') { + const usedDate = new Date(date); + const formatOpt: Record< + 'today' | 'yesterday' | 'thisYear' | 'other', + Intl.DateTimeFormatOptions + > = { + today: { hour: 'numeric', minute: 'numeric' }, + yesterday: { hour: 'numeric', minute: 'numeric' }, + thisYear: { weekday: 'short', hour: 'numeric', minute: 'numeric' }, + other: { + weekday: 'short', + month: 'short', + day: 'numeric', + year: 'numeric', + }, + }; + const toDayYesterdayFmt = new Intl.DateTimeFormat(locale, formatOpt.today); + if (isToday(usedDate)) { + return `Today at ${toDayYesterdayFmt.format(usedDate)}`; + } + if (isYesterday(usedDate)) { + return `Yesterday at ${toDayYesterdayFmt.format(usedDate)}`; + } + const thisYearFmt = new Intl.DateTimeFormat(locale, formatOpt.thisYear); + if (isThisYear(usedDate)) { + return thisYearFmt.format(usedDate); + } + const otherFmt = new Intl.DateTimeFormat(locale, formatOpt.other); + return otherFmt.format(usedDate); +}