Skip to content

Commit

Permalink
Add date range filter; AuthToken added via interceptor; minor bug fixes;
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberpirate92 committed Oct 23, 2020
1 parent 38a7688 commit 98540da
Show file tree
Hide file tree
Showing 18 changed files with 283 additions and 96 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"name": "mini-task-manager",
"version": "0.0.0",
"repository": {
"url": "https://github.com/cyberpirate92/mini-task-manager"
},
"scripts": {
"ng": "ng",
"start": "ng serve",
Expand Down
45 changes: 30 additions & 15 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,33 +1,48 @@
<div class="container-fluid h-100">
<ng-container *ngIf="!isLoading; else LoadingScreen">
<div class="row mt-4">
<div class="col-sm-12 col-md-4">
<div class="row mt-4 justify-content-md-center">
<div class="col-sm-12 col-md-auto">
<div class="btn-group" dropdown>
<button id="button-animated" dropdownToggle type="button" class="btn btn-light btn-lg dropdown-toggle" aria-controls="dropdown-animated">
Group By: {{ selectedTaskGroup.displayName }} <span class="caret"></span>
Group By: <span class="font-weight-bold text-primary">{{ selectedTaskGroup.displayName }}</span> <span class="caret"></span>
</button>
<ul id="dropdown-animated" *dropdownMenu class="dropdown-menu" role="menu" aria-labelledby="button-animated">
<li role="menuitem" *ngFor="let taskGroup of taskGroups" (click)="updateGrouping(taskGroup)">
<span class="dropdown-item">{{ taskGroup.displayName }}</span>
<span class="h5 clickable dropdown-item">{{ taskGroup.displayName }}</span>
</li>
</ul>
</div>
</div>
<div class="offset-md-4"></div>
<div class="col-sm-12 col-md-4">
<div class="form-group">
<input type="text" spellcheck="false" autocomplete="false" [(ngModel)]="searchTerm" placeholder="Type to search" class="form-control form-control-lg"/>
<div class="input-group input-group-lg">
<div class="input-group-prepend">
<div class="input-group-text">
<i class="fa fa-search"></i>
</div>
</div>
<input type="search" spellcheck="false" autocomplete="false" [(ngModel)]="searchTerm" placeholder="Type to search" class="form-control"/>
</div>
</div>
<div class="col-sm-12 col-md-auto">
<div class="input-group input-group-lg">
<div class="input-group-prepend">
<div class="input-group-text">
<i class="fa fa-calendar"></i>
</div>
</div>
<input type="text" class="form-control" placeholder="Click to select date range" (bsValueChange)="onDateRangeChange($event)" [bsConfig]="{ showClearButton: true }" bsDaterangepicker>
</div>
</div>
</div>
<div class="row mt-4">
</div>
<div class="row mt-4 board-container justify-content-md-center">
<ng-container *ngFor="let value of selectedTaskGroup.values; index as i">
<app-task-board class="col-md-4"
[property]="selectedTaskGroup.propertyName"
[value]="value"
[title]="selectedTaskGroup.displayLabels[i]"
[displayPicture]="(selectedTaskGroup.displayPictures && selectedTaskGroup.displayPictures[i]) || ''"
[filterTerm]="searchTerm">
<app-task-board class="col-sm-12 col-md-auto restrict-width-md"
[property]="selectedTaskGroup.propertyName"
[value]="value"
[title]="selectedTaskGroup.displayLabels[i]"
[displayPicture]="(selectedTaskGroup.displayPictures && selectedTaskGroup.displayPictures[i]) || ''"
[filterDateRange]="filterDateRange"
[filterTerm]="searchTerm">
</app-task-board>
</ng-container>
</div>
Expand Down
29 changes: 20 additions & 9 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,35 @@ import { UsersService } from './services/users.service';
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {

private _users: User[];

public searchTerm: string;
public isLoading: boolean;
public destroy$: Subject<any>;
public selectedTaskGroup: TaskGroup;

public filterDateRange: Date[];

/**
* Predefined task grouping configurations
*/
* Predefined task grouping configurations
*/
public readonly taskGroups: TaskGroup[] = [{
displayName: 'Priority',
propertyName: 'priority',
values: [...this.taskService.PRIORITIES.map(x => x.value), false],
displayLabels: [...this.taskService.PRIORITIES.map(x => x.label), 'No Priority'],
values: this.taskService.PRIORITIES.map(x => x.value),
displayLabels: this.taskService.PRIORITIES.map(x => x.label),
},{
displayName: 'Assigned To',
propertyName: 'assigned_to',
values: [],
displayLabels: [],
displayPictures: [],
}];

public get users(): User[] {
return this._users;
}

public set users(list: User[]) {
this._users = list;
const taskGroup = this.taskGroups.find(g => g.propertyName === 'assigned_to');
Expand All @@ -56,6 +57,7 @@ export class AppComponent implements OnInit, OnDestroy {
this._users = [];
this.isLoading = true;
this.selectedTaskGroup = this.taskGroups[0];
this.filterDateRange = [];
}

public ngOnInit(): void {
Expand All @@ -72,6 +74,15 @@ export class AppComponent implements OnInit, OnDestroy {
});
}

public onDateRangeChange(change: Date[]) {
if (change && typeof change === 'object' && change instanceof Array) {
this.filterDateRange = [...change];
console.log(this.filterDateRange);
} else {
this.filterDateRange = [];
}
}

public updateGrouping(taskGroup: TaskGroup) {
this.selectedTaskGroup = taskGroup;
}
Expand Down
14 changes: 12 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { HttpClientModule } from '@angular/common/http'
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';

import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';

import { AppComponent } from './app.component';
import { TaskBoardComponent } from './task-board/task-board.component';
Expand All @@ -13,6 +14,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TaskFilterPipe } from './pipes/task-filter.pipe';
import { PadPipe } from './pipes/pad.pipe';
import { TaskItemEditComponent } from './task-item-edit/task-item-edit.component';
import { AuthInterceptor } from './services/auth-interceptor';

@NgModule({
declarations: [
Expand All @@ -31,8 +33,16 @@ import { TaskItemEditComponent } from './task-item-edit/task-item-edit.component
FormsModule,
ReactiveFormsModule,
BsDropdownModule.forRoot(),
BsDatepickerModule.forRoot(),
],
providers: [
TaskFilterPipe,
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
],
providers: [TaskFilterPipe],
bootstrap: [AppComponent]
})
export class AppModule { }
13 changes: 11 additions & 2 deletions src/app/pipes/task-filter.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { Pipe } from "@angular/core";
import { TaskItem } from '../models';
import { DateUtils } from '../utils/date-utils';

@Pipe({
name: 'taskFilter'
})
export class TaskFilterPipe {
public transform(tasks: TaskItem[], filterString: string): TaskItem[] {
public transform(tasks: TaskItem[], filterString: string, dateRange: Date[] = []): TaskItem[] {
const isValidDateRange = dateRange && dateRange.length >= 2 && DateUtils.isDate(dateRange[0]) && DateUtils.isDate(dateRange[1]);
console.log('is-valid-daterange', dateRange, isValidDateRange);
if (isValidDateRange) {
tasks = tasks.filter(x => x.due_date >= dateRange[0] && x.due_date <= dateRange[1]);
}
filterString = filterString.trim().toLowerCase();
return tasks.filter(task => this.combineStrings(task).indexOf(filterString) >= 0);
if (filterString) {
tasks = tasks.filter(task => this.combineStrings(task).indexOf(filterString) >= 0);
}
return tasks;
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/app/services/auth-interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpResponse, HttpRequest, HttpHandler } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(httpRequest.clone({
setHeaders: {
AuthToken: environment.apiKey,
}
}));;
}
}
36 changes: 9 additions & 27 deletions src/app/services/task-manager.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
import { tap } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { CreateTaskResponse, GenericResponse, TaskItem, TaskListResponse } from '../models';
Expand All @@ -11,28 +11,26 @@ import { DateUtils } from '../utils/date-utils';
})
export class TaskManagerService {
private API_URL: string;
private API_KEY: string;

public tasks$: BehaviorSubject<TaskItem[]>;
public error$: BehaviorSubject<string>;

public readonly PRIORITIES = [{
value: 1,
label: 'High Priority',
styleClass: 'priority-high',
label: 'Low Priority',
styleClass: 'priority-low',
}, {
value: 2,
label: 'Medium Priority',
styleClass: 'priority-medium',
}, {
value: 3,
label: 'Low Priority',
styleClass: 'priority-low',
label: 'High Priority',
styleClass: 'priority-high',
}];

constructor(private httpClient: HttpClient) {
this.API_URL = environment.apiUrl;
this.API_KEY = environment.apiKey;

this.tasks$ = new BehaviorSubject([]);
this.error$ = new BehaviorSubject("");
Expand All @@ -45,11 +43,7 @@ export class TaskManagerService {
* @returns Observable of the response object
*/
public fetchAll() {
return this.httpClient.get<TaskListResponse>(`${this.API_URL}/list`, {
headers: {
AuthToken: this.API_KEY,
}
}).pipe(tap({
return this.httpClient.get<TaskListResponse>(`${this.API_URL}/list`).pipe(tap({
next: response => {
if (response.status === 'success') {
this.tasks$.next(response.tasks.map(t => {
Expand Down Expand Up @@ -83,11 +77,7 @@ export class TaskManagerService {
requestBody.set('due_date', DateUtils.toRequestFormat(task.due_date) || '');
requestBody.set('assigned_to', task.assigned_to?.toString() || '');

return this.httpClient.post<CreateTaskResponse>(`${this.API_URL}/create`, requestBody, {
headers: {
AuthToken: this.API_KEY,
}
}).pipe(tap({
return this.httpClient.post<CreateTaskResponse>(`${this.API_URL}/create`, requestBody).pipe(tap({
next: response => {
if (response.status === 'success') {
task.id = response.taskid;
Expand All @@ -113,11 +103,7 @@ export class TaskManagerService {
requestBody.set('assigned_to', task.assigned_to?.toString() || '');
requestBody.set('taskid', task.id);

return this.httpClient.post<GenericResponse>(`${this.API_URL}/update`, requestBody, {
headers: {
AuthToken: this.API_KEY,
}
}).pipe(tap({
return this.httpClient.post<GenericResponse>(`${this.API_URL}/update`, requestBody).pipe(tap({
next: response => {
if (response.status === 'success') {
const taskList = [...this.tasks$.getValue()];
Expand Down Expand Up @@ -145,11 +131,7 @@ export class TaskManagerService {
public deleteTask(taskId: string) {
let requestBody = new FormData();
requestBody.set('taskid', taskId);
return this.httpClient.post<GenericResponse>(`${this.API_URL}/delete`, requestBody, {
headers: {
AuthToken: this.API_KEY,
}
}).pipe(tap({
return this.httpClient.post<GenericResponse>(`${this.API_URL}/delete`, requestBody).pipe(tap({
next: response => {
if (response.status === 'success') {
this.tasks$.next(this.tasks$.getValue().filter(task => task.id !== taskId));
Expand Down
14 changes: 5 additions & 9 deletions src/app/services/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ export class UsersService {
* @returns observable of the response object
*/
public fetchAll() {
return this.httpClient.get<UserResponse>(`${this.API_URL}/listusers`, {
headers: {
AuthToken: environment.apiKey,
}
}).pipe(tap({
return this.httpClient.get<UserResponse>(`${this.API_URL}/listusers`).pipe(tap({
next: response => {
if (response.status !== 'success') {
console.error(response.error || 'Fetching user list failed: Unknown error');
Expand All @@ -43,11 +39,11 @@ export class UsersService {
}
}));
}

/**
* Fetch user by User ID from cache
* @param id User ID
*/
* Fetch user by User ID from cache
* @param id User ID
*/
public getUserById(id: number): User {
return id ? this.users$.getValue().find(x => x.id == id.toString()) : null;
}
Expand Down
24 changes: 20 additions & 4 deletions src/app/task-board/task-board.component.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
<div class="row board-outline">
<div class="col-12">
<div class="card task-board mb-4">
<div class="card task-board mb-4"
(dragover)="onDragOver($event)"
(dragleave)="onDragLeave($event)"
(drop)="onDropped($event)">
<div class="card-header">
<img [src]="displayPicture" class="display-picture" *ngIf="displayPicture">
<span class="board-title h5">
{{ title }}
</span>
<span class="badge badge-dark monospaced-font" *ngIf="filteredTasks.length>0">
<span class="badge badge-dark monospaced-font item-count" *ngIf="filteredTasks.length>0">
{{ filteredTasks.length | pad }}
</span>
<div class="float-right action">
<i class="fa fa-plus" (click)="newTask()"></i>
</div>
</div>
<div class="card-body" (dragover)="$event.preventDefault()" (drop)="onDropped($event)">
<div class="card-body" *ngIf="showCreateTaskCard || filteredTasks.length > 0; else NoTasks">
<app-task-item-edit *ngIf="showCreateTaskCard"
(onSave)="createTask($event)"
(onCancel)="showCreateTaskCard = false"
Expand All @@ -26,4 +29,17 @@
</div>
</div>
</div>
</div>
</div>
<ng-template #NoTasks>
<div class="col-12 text-center mt-3 text-muted empty-state">
<p class="icon">
<i class="fa fa-folder-open-o fa-3x"></i>
</p>
<div class="message message-primary">
No tasks here
</div>
<div class="message message-secondary">
Click on the <i class="fa fa-plus"></i> icon to add tasks or drag one from another board
</div>
</div>
</ng-template>
Loading

0 comments on commit 98540da

Please sign in to comment.