Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(pre defined boards) #12

Merged
merged 2 commits into from
Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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