Skip to content

Commit

Permalink
Merge pull request #12 from cyberpirate92/feat(pre-defined-boards)
Browse files Browse the repository at this point in the history
Feat(pre defined boards)
  • Loading branch information
cyberpirate92 authored Oct 23, 2020
2 parents 978d7c9 + 98540da commit a3047d6
Show file tree
Hide file tree
Showing 22 changed files with 473 additions and 172 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
60 changes: 44 additions & 16 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,24 +1,52 @@
<div class="container-fluid h-100">
<ng-container *ngIf="!isLoading; else LoadingScreen">
<div class="row mt-4">
<div class="offset-md-8"></div>
<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: <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="h5 clickable dropdown-item">{{ taskGroup.displayName }}</span>
</li>
</ul>
</div>
</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>
<div class="row mt-4">
<ng-container *ngFor="let board of boards">
<app-task-board
class="col-md-4"
[title]="board.title"
[priority]="board.priority"
[filterTerm]="searchTerm">
</app-task-board>
</ng-container>
</div>
</ng-container>
<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 board-container justify-content-md-center">
<ng-container *ngFor="let value of selectedTaskGroup.values; index as i">
<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>
</ng-container>
</div>
<ng-template #LoadingScreen>
<div class="col-12 text-center text-light my-auto mx-auto">
Expand Down
71 changes: 48 additions & 23 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { BrowserTransferStateModule } from '@angular/platform-browser';
import { forkJoin, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { TaskBoard, TaskDropEvent } from './models';
import { TaskGroup } from './models';
import { User } from './models/user';
import { TaskManagerService } from './services/task-manager.service';
import { UsersService } from './services/users.service';
Expand All @@ -13,33 +12,60 @@ import { UsersService } from './services/users.service';
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {
public users: User[];

private _users: User[];

public searchTerm: string;
public isLoading: boolean;
public destroy$: Subject<any>;
public selectedTaskGroup: TaskGroup;
public filterDateRange: Date[];

public boards: TaskBoard[] = [{
title: 'High Priority',
priority: 1,
},{
title: 'Medium Priority',
priority: 2,
/**
* Predefined task grouping configurations
*/
public readonly taskGroups: TaskGroup[] = [{
displayName: 'Priority',
propertyName: 'priority',
values: this.taskService.PRIORITIES.map(x => x.value),
displayLabels: this.taskService.PRIORITIES.map(x => x.label),
},{
title: 'Low Priority',
priority: 3,
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');
if (taskGroup) {
taskGroup.values = [...list.map(x => x.id), false];
taskGroup.displayLabels = [...list.map(x => x.name), 'Up for Grabs'];
taskGroup.displayPictures = [...list.map(x => x.picture), '']
}
}

constructor(private taskService: TaskManagerService, private userService: UsersService) {
this.destroy$ = new Subject();
this.searchTerm = '';
this.users = [];
this._users = [];
this.isLoading = true;
this.selectedTaskGroup = this.taskGroups[0];
this.filterDateRange = [];
}

public ngOnInit(): void {
forkJoin([this.userService.fetchAll(), this.taskService.fetchAll()]).subscribe({
next: _ => {
// TODO: remove
this.userService.users$.pipe(takeUntil(this.destroy$)).subscribe({
next: users => this.users = users,
});
}, error: error => {
console.error('Initializion failed', error);
}, complete: () => {
Expand All @@ -48,20 +74,19 @@ export class AppComponent implements OnInit, OnDestroy {
});
}

public handleDrop(event: TaskDropEvent) {
if (event.task.priority !== event.board.priority) {
console.log('Now this is interesting');
event.task.priority = event.board.priority;
this.taskService.updateTask(event.task).subscribe({
next: response => {
console.log(response);
}
});
public onDateRangeChange(change: Date[]) {
if (change && typeof change === 'object' && change instanceof Array) {
this.filterDateRange = [...change];
console.log(this.filterDateRange);
} else {
console.log('Dropped on the same board, how boring!');
this.filterDateRange = [];
}
}

public updateGrouping(taskGroup: TaskGroup) {
this.selectedTaskGroup = taskGroup;
}

public ngOnDestroy(): void {
this.destroy$.complete();
}
Expand Down
19 changes: 17 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
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';
import { TaskItemComponent } from './task-item/task-item.component';
Expand All @@ -10,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 @@ -23,11 +28,21 @@ import { TaskItemEditComponent } from './task-item-edit/task-item-edit.component
],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
BsDropdownModule.forRoot(),
BsDatepickerModule.forRoot(),
],
providers: [
TaskFilterPipe,
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
],
providers: [TaskFilterPipe],
bootstrap: [AppComponent]
})
export class AppModule { }
2 changes: 1 addition & 1 deletion src/app/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { TaskBoard } from './task-board';
export { TaskGroup } from './task-group';
export { TaskItem } from './task-item';
export { GenericResponse } from './generic-response';
export { TaskListResponse } from './task-list-response';
Expand Down
6 changes: 0 additions & 6 deletions src/app/models/task-board.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/app/models/task-drop-event.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TaskBoard } from './task-board';
import { TaskGroup } from './task-group';
import { TaskItem } from './task-item';

export interface TaskDropEvent {
Expand All @@ -10,5 +10,5 @@ export interface TaskDropEvent {
/**
* The board it has been dropped to
*/
board: TaskBoard;
board: TaskGroup;
}
33 changes: 33 additions & 0 deletions src/app/models/task-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Descibe a property based task grouping model
*/
export interface TaskGroup {
/**
* Name of this task grouping model
*/
displayName: string;

/**
* The property name which is the basis
* for this grouping model
*/
propertyName: string;

/**
* The list of values for `propertyName` the
* tasks should be divided into
*/
values: any[];

/**
* Display labels to be used as board titles.
* List should have the same number of elements as `values`
*/
displayLabels: string[];

/**
* Optional image urls.
* Should have the same number of elements as `values`
*/
displayPictures?: string[];
}
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,
}
}));;
}
}
Loading

1 comment on commit a3047d6

@vercel
Copy link

@vercel vercel bot commented on a3047d6 Oct 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.