WIP - Persist collapsed columns and start Task addition
This commit is contained in:
parent
f28ef733d6
commit
ac6808ffa7
@ -206,6 +206,12 @@ class Auth extends BaseController {
|
||||
|
||||
$user = R::load('user', $payload->uid);
|
||||
$opts = R::load('useroption', $user->user_option_id);
|
||||
$collapsed = R::find('collapsed', ' user_id = ? ', [ $user->id ]);
|
||||
|
||||
$user->collapsed = [];
|
||||
foreach ($collapsed as $collapse) {
|
||||
$user->collapsed[] = $collapse->column_id;
|
||||
}
|
||||
|
||||
$this->apiJson->setSuccess();
|
||||
$this->apiJson->addData($jwt);
|
||||
|
@ -237,6 +237,43 @@ class Users extends BaseController {
|
||||
return $this->jsonResponse($response);
|
||||
}
|
||||
|
||||
public function toggleCollapsed($request, $response, $args) {
|
||||
$status = $this->secureRoute($request, $response, SecurityLevel::USER);
|
||||
if ($status !== 200) {
|
||||
return $this->jsonResponse($response, $status);
|
||||
}
|
||||
|
||||
$user = R::load('user', (int)$args['id']);
|
||||
$actor = R::load('user', Auth::GetUserId($request));
|
||||
|
||||
if ($actor->id !== $user->id) {
|
||||
$this->apiJson->addAlert('error', 'Access restricted.');
|
||||
|
||||
return $this->jsonResponse($response, 403);
|
||||
}
|
||||
|
||||
$data = json_decode($request->getBody());
|
||||
$collapsed = R::findOne('collapsed', ' user_id = ? AND column_id = ? ',
|
||||
[ $user->id, $data->id ]);
|
||||
|
||||
if (!is_null($collapsed)) {
|
||||
R::trash($collapsed);
|
||||
} else {
|
||||
$collapsed = R::dispense('collapsed');
|
||||
$collapsed->user_id = $user->id;
|
||||
$collapsed->column_id = $data->id;
|
||||
|
||||
R::store($collapsed);
|
||||
}
|
||||
|
||||
$allCollapsed = R::find('collapsed', ' user_id = ? ', [ $user->id ]);
|
||||
|
||||
$this->apiJson->setSuccess();
|
||||
$this->apiJson->addData(R::exportAll($allCollapsed));
|
||||
|
||||
return $this->jsonResponse($response);
|
||||
}
|
||||
|
||||
public function removeUser($request, $response, $args) {
|
||||
$status = $this->secureRoute($request, $response, SecurityLevel::ADMIN);
|
||||
if ($status !== 200) {
|
||||
|
@ -46,17 +46,17 @@ class BeanLoader {
|
||||
$board->is_active = isset($data->is_active) ? $data->is_active : '';
|
||||
|
||||
if (isset($data->categories)) {
|
||||
self::updateBoardList('category', 'LoadCategory',
|
||||
self::updateObjectList('category', 'LoadCategory',
|
||||
$board->xownCategoryList, $data->categories);
|
||||
}
|
||||
|
||||
if (isset($data->columns)) {
|
||||
self::updateBoardList('column', 'LoadColumn',
|
||||
self::updateObjectList('column', 'LoadColumn',
|
||||
$board->xownColumnList, $data->columns);
|
||||
}
|
||||
|
||||
if (isset($data->issue_trackers)) {
|
||||
self::updateBoardList('issuetracker', 'LoadIssueTracker',
|
||||
self::updateObjectList('issuetracker', 'LoadIssueTracker',
|
||||
$board->xownIssueTrackerList,
|
||||
$data->issue_trackers);
|
||||
}
|
||||
@ -106,6 +106,11 @@ class BeanLoader {
|
||||
$column->position = isset($data->position) ? $data->position : '';
|
||||
$column->board_id = isset($data->board_id) ? $data->board_id : '';
|
||||
|
||||
if (isset($data->tasks)) {
|
||||
self::updateObjectList('task', 'LoadTask',
|
||||
$column->xownTaskList, $data->tasks);
|
||||
}
|
||||
|
||||
if (!isset($data->name) || !isset($data->position) ||
|
||||
!isset($data->board_id)) {
|
||||
return false;
|
||||
@ -150,15 +155,32 @@ class BeanLoader {
|
||||
$task->title = isset($data->title) ? $data->title : '';
|
||||
$task->description = isset($data->description)
|
||||
? $data->description : '';
|
||||
$task->assignee = isset($data->assignee) ? $data->assignee : '';
|
||||
$task->category_id = isset($data->category_id)
|
||||
? $data->category_id : '';
|
||||
$task->color = isset($data->color) ? $data->color : '';
|
||||
$task->due_date = isset($data->due_date) ? $data->due_date : '';
|
||||
$task->points = isset($data->points) ? $data->points : '';
|
||||
$task->position = isset($data->position) ? $data->position : '';
|
||||
$task->column_id = isset($data->column_id) ? $data->column_id : '';
|
||||
|
||||
if (isset($data->comments)) {
|
||||
self::updateObjectList('comment', 'LoadComment',
|
||||
$column->xownCommentList, $data->comments);
|
||||
}
|
||||
|
||||
if (isset($data->attachments)) {
|
||||
self::updateObjectList('attachment', 'LoadAttachment',
|
||||
$column->xownAttachmentList, $data->attachments);
|
||||
}
|
||||
|
||||
if (isset($data->assignees)) {
|
||||
self::updateObjectList('user', 'LoadUser',
|
||||
$column->xownAssigneeList, $data->assignees);
|
||||
}
|
||||
|
||||
if (isset($data->categories)) {
|
||||
self::updateObjectList('category', 'LoadCategory',
|
||||
$column->xownCategoryList, $data->categories);
|
||||
}
|
||||
|
||||
if (!isset($data->title) || !isset($data->position) ||
|
||||
!isset($data->column_id)) {
|
||||
return false;
|
||||
@ -210,7 +232,7 @@ class BeanLoader {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function removeObjectsNotInData($type, &$dataList, &$boardList) {
|
||||
private static function removeObjectsNotInData($type, &$dataList, &$objectList) {
|
||||
$dataIds = [];
|
||||
|
||||
foreach ($dataList as $data) {
|
||||
@ -219,7 +241,7 @@ class BeanLoader {
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($boardList as $existing) {
|
||||
foreach ($objectList as $existing) {
|
||||
if (!in_array((int)$existing->id, $dataIds)) {
|
||||
$remove = R::load($type, $existing->id);
|
||||
R::trash($remove);
|
||||
@ -228,36 +250,36 @@ class BeanLoader {
|
||||
}
|
||||
|
||||
private static function loadObjectsFromData($type, $loadFunc, &$dataList,
|
||||
&$boardList) {
|
||||
&$objectList) {
|
||||
foreach ($dataList as $obj) {
|
||||
$object = R::load($type, (isset($obj->id) ? $obj->id : 0));
|
||||
|
||||
if ((int)$object->id === 0) {
|
||||
call_user_func_array(array(__CLASS__, $loadFunc),
|
||||
array(&$object, json_encode($obj)));
|
||||
$boardList[] = $object;
|
||||
$objectList[] = $object;
|
||||
continue;
|
||||
}
|
||||
|
||||
call_user_func_array(array(__CLASS__, $loadFunc),
|
||||
array(&$boardList[$object->id],
|
||||
array(&$objectList[$object->id],
|
||||
json_encode($obj)));
|
||||
}
|
||||
}
|
||||
|
||||
private static function updateBoardList($type, $loadFunc,
|
||||
&$boardList = [], &$dataList = []) {
|
||||
if (count($boardList) && count($dataList)) {
|
||||
self::removeObjectsNotInData($type, $dataList, $boardList);
|
||||
private static function updateObjectList($type, $loadFunc,
|
||||
&$objectList = [], &$dataList = []) {
|
||||
if (count($objectList) && count($dataList)) {
|
||||
self::removeObjectsNotInData($type, $dataList, $objectList);
|
||||
}
|
||||
|
||||
if (count($dataList)) {
|
||||
self::loadObjectsFromData($type, $loadFunc, $dataList, $boardList);
|
||||
self::loadObjectsFromData($type, $loadFunc, $dataList, $objectList);
|
||||
}
|
||||
|
||||
// Remove all objects from existing boardlist when none in datalist
|
||||
if (!count($dataList) && count($boardList)) {
|
||||
foreach ($boardList as $obj) {
|
||||
if (!count($dataList) && count($objectList)) {
|
||||
foreach ($objectList as $obj) {
|
||||
R::trash($obj);
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ $app->get('/users/{id}', 'Users:getUser'); // User (by board access)
|
||||
$app->post('/users', 'Users:addUser'); // Admin
|
||||
$app->post('/users/{id}', 'Users:updateUser'); // User (limited to self - Higher can edit any)
|
||||
$app->post('/users/{id}/opts', 'Users:updateUserOptions'); // User (limited to self)
|
||||
$app->post('/users/{id}/cols', 'Users:toggleCollapsed'); // User (limited to self)
|
||||
$app->delete('/users/{id}', 'Users:removeUser'); // Admin
|
||||
|
||||
$app->post('/login', 'Auth:login'); // Unsecured (creates JWT)
|
||||
|
@ -60,7 +60,6 @@
|
||||
<div class="board" *ngIf="activeBoard">
|
||||
<tb-column class="column"
|
||||
*ngFor="let column of activeBoard.columns"
|
||||
[column]="column"
|
||||
[sideBySide]="userOptions.multiple_tasks_per_row"></tb-column>
|
||||
[column]="column"></tb-column>
|
||||
</div>
|
||||
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
Board,
|
||||
Column,
|
||||
User,
|
||||
UserOptions,
|
||||
InlineEdit,
|
||||
Modal,
|
||||
Notification,
|
||||
@ -26,7 +25,6 @@ import { BoardService } from './board.service';
|
||||
})
|
||||
export class BoardDisplay implements OnInit {
|
||||
private activeUser: User;
|
||||
private userOptions: UserOptions;
|
||||
private activeBoard: Board;
|
||||
private boards: Array<Board>;
|
||||
|
||||
@ -60,7 +58,6 @@ export class BoardDisplay implements OnInit {
|
||||
|
||||
auth.userChanged.subscribe((user: User) => {
|
||||
this.updateActiveUser(user);
|
||||
this.userOptions = auth.userOptions;
|
||||
});
|
||||
|
||||
active.params.subscribe(params => {
|
||||
@ -122,6 +119,7 @@ export class BoardDisplay implements OnInit {
|
||||
this.boards.forEach(board => {
|
||||
if (board.id === this.boardNavId) {
|
||||
this.activeBoard = board;
|
||||
this.boardService.updateActiveBoard(board);
|
||||
this.pageName = board.name;
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http } from '@angular/http';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/operator/map';
|
||||
@ -14,9 +15,17 @@ import {
|
||||
|
||||
@Injectable()
|
||||
export class BoardService {
|
||||
private activeBoard = new BehaviorSubject<Board>(null);
|
||||
|
||||
public activeBoardChanged = this.activeBoard.asObservable();
|
||||
|
||||
constructor(private http: Http) {
|
||||
}
|
||||
|
||||
updateActiveBoard(board: Board): void {
|
||||
this.activeBoard.next(board);
|
||||
}
|
||||
|
||||
getBoards(): Observable<ApiResponse> {
|
||||
return this.http.get('api/boards')
|
||||
.map(res => {
|
||||
@ -29,6 +38,20 @@ export class BoardService {
|
||||
});
|
||||
}
|
||||
|
||||
toggleCollapsed(userId: number, columnId: number): Observable<ApiResponse> {
|
||||
return this.http.post('api/users/' + userId + '/cols',
|
||||
{ id: columnId })
|
||||
.map(res => {
|
||||
let response: ApiResponse = res.json();
|
||||
return response;
|
||||
})
|
||||
.catch((res, caught) => {
|
||||
let response: ApiResponse = res.json();
|
||||
return Observable.of(response);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Determine when to use this
|
||||
refreshToken(): void {
|
||||
this.http.post('api/refresh', {}).subscribe();
|
||||
}
|
||||
|
@ -67,8 +67,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<tb-modal modal-title="{{ modalProps.title }} Task" modal-id="{{ MODAL_ID }}">
|
||||
<!-- TODO Move to Task HTML once created -->
|
||||
<!--<tb-modal modal-title="Confirm Task Removal" blocking="true">-->
|
||||
<!-- <div class="center">Removing a task cannot be undone.<br>Continue?</div>-->
|
||||
<!-- <div class="buttons">-->
|
||||
<!-- <button class="flat"-->
|
||||
<!-- (click)="removeUser()">Yes</button>-->
|
||||
<!-- <button #defaultAction-->
|
||||
<!-- (click)="modal.close(MODAL_CONFIRM_ID)">No</button>-->
|
||||
<!-- </div>-->
|
||||
<!--</tb-modal>-->
|
||||
|
||||
<tb-modal modal-title="Add Task" modal-id="{{ MODAL_ID }}">
|
||||
<label>
|
||||
Title
|
||||
<input type="text" name="title" placeholder="Task Title"
|
||||
[(ngModel)]="modalProps.title">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Description
|
||||
<textarea name="description" rows="5"
|
||||
placeholder="What needs to get done?"
|
||||
[(ngModel)]="modalProps.description"></textarea>
|
||||
</label>
|
||||
|
||||
<label *ngIf="activeBoard">
|
||||
Assignees
|
||||
<select name="assignees" multiple [(ngModel)]="modalProps.assignees">
|
||||
<option *ngFor="let user of activeBoard.users"
|
||||
[ngValue]="user">{{ user.username }}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<pre>{{ modalProps | json }}</pre>
|
||||
<div class="buttons">
|
||||
<button #defaultAction
|
||||
(click)="addEditTask()" [disabled]="saving">
|
||||
|
@ -6,13 +6,19 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ApiResponse,
|
||||
Board,
|
||||
Column,
|
||||
Modal,
|
||||
Notification,
|
||||
// Task, // TODO: Create Task model
|
||||
Task,
|
||||
User,
|
||||
UserOptions,
|
||||
AuthService,
|
||||
ModalService,
|
||||
NotificationsService
|
||||
} from '../../shared/index';
|
||||
import { BoardService } from '../board.service';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-column',
|
||||
@ -20,38 +26,77 @@ import {
|
||||
})
|
||||
export class ColumnDisplay implements OnInit {
|
||||
private templateElement: any;
|
||||
private tasks: Array<any>; // TODO: Use Task model
|
||||
private collapseTasks: boolean;
|
||||
private modalProps: any; // TODO: Create ModalProperties model
|
||||
|
||||
private activeUser: User;
|
||||
private activeBoard: Board;
|
||||
private userOptions: UserOptions;
|
||||
private tasks: Array<Task>;
|
||||
private modalProps: Task;
|
||||
|
||||
private MODAL_ID: string;
|
||||
private MODAL_CONFIRM_ID: string;
|
||||
|
||||
@Input('column') columnData: Column;
|
||||
@Input('sideBySide') sideBySide: boolean;
|
||||
|
||||
constructor(private elRef: ElementRef,
|
||||
private auth: AuthService,
|
||||
private notes: NotificationsService,
|
||||
private modal: ModalService) {
|
||||
private modal: ModalService,
|
||||
private boardService: BoardService) {
|
||||
this.MODAL_ID = 'task-addEdit-form';
|
||||
this.MODAL_CONFIRM_ID = 'task-remove-confirm';
|
||||
|
||||
this.templateElement = elRef.nativeElement;
|
||||
this.tasks = [];
|
||||
this.collapseTasks = false;
|
||||
this.modalProps = { title: '' }; // TODO: Use model
|
||||
this.modalProps = new Task();
|
||||
|
||||
boardService.activeBoardChanged.subscribe((board: Board) => {
|
||||
this.activeBoard = board;
|
||||
});
|
||||
|
||||
auth.userChanged.subscribe((user: User) => {
|
||||
this.activeUser = new User(+user.default_board_id,
|
||||
user.email,
|
||||
+user.id,
|
||||
user.last_login,
|
||||
+user.security_level,
|
||||
+user.user_option_id,
|
||||
user.username,
|
||||
user.board_access,
|
||||
user.collapsed);
|
||||
this.userOptions = auth.userOptions;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.templateElement.classList.remove('double');
|
||||
|
||||
if (this.sideBySide) {
|
||||
if (this.userOptions.multiple_tasks_per_row) {
|
||||
this.templateElement.classList.add('double');
|
||||
}
|
||||
|
||||
let isCollapsed = false;
|
||||
|
||||
this.activeUser.collapsed.forEach(id => {
|
||||
if (+id === +this.columnData.id) {
|
||||
isCollapsed = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (isCollapsed) {
|
||||
this.templateElement.classList.add('collapsed');
|
||||
}
|
||||
}
|
||||
|
||||
toggleCollapsed() {
|
||||
this.templateElement.classList.toggle('collapsed');
|
||||
|
||||
this.boardService.toggleCollapsed(this.activeUser.id, this.columnData.id)
|
||||
.subscribe((apiResponse: ApiResponse) => {
|
||||
this.activeUser.collapsed = apiResponse.data[1];
|
||||
});
|
||||
}
|
||||
|
||||
toggleTaskCollapse() {
|
||||
@ -62,15 +107,8 @@ export class ColumnDisplay implements OnInit {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private showModal(title: string, task?: any): void { // TODO: Use Task model
|
||||
let isAdd = (title === 'Add');
|
||||
|
||||
this.modalProps = {
|
||||
title,
|
||||
prefix: isAdd ? '' : 'Edit',
|
||||
task: isAdd ? task /*new Task()*/ : task
|
||||
};
|
||||
|
||||
private showModal(): void {
|
||||
this.modalProps = new Task();
|
||||
this.modal.open(this.MODAL_ID);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { Task } from './task.model';
|
||||
|
||||
export class Column {
|
||||
constructor(public id: number = 0,
|
||||
public name: string = '',
|
||||
public position: number = 0,
|
||||
public board_id: number = 0, // tslint:disable-line
|
||||
public tasks: Array<any> = []) { // TODO: Use Task model
|
||||
public tasks: Array<Task> = []) {
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,4 +7,5 @@ export * from './issue-tracker.model';
|
||||
export * from './notification.model';
|
||||
export * from './user-options.model';
|
||||
export * from './user.model';
|
||||
export * from './task.model';
|
||||
|
||||
|
19
src/app/shared/models/task.model.ts
Normal file
19
src/app/shared/models/task.model.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Category } from './category.model';
|
||||
import { User } from './user.model';
|
||||
|
||||
export class Task {
|
||||
constructor(public id: number = 0,
|
||||
public title: string = '',
|
||||
public description: string = '',
|
||||
public color: string = '',
|
||||
public due_date: string = '', // tslint:disable-line
|
||||
public points: number = 0,
|
||||
public position: number = 0,
|
||||
public column_id: number = 0, // tslint:disable-line
|
||||
public comments: Array<any> = [], // TODO: Use model
|
||||
public attachments: Array<any> = [], // TODO: Use model
|
||||
public assignees: Array<User> = [],
|
||||
public categories: Array<Category> = []) {
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ export class User {
|
||||
public security_level: number = 3, // tslint:disable-line
|
||||
public user_option_id: number = 0, // tslint:disable-line
|
||||
public username: string = '',
|
||||
public board_access: Array<number> = []) { // tslint:disable-line
|
||||
public board_access: Array<number> = [], // tslint:disable-line
|
||||
public collapsed: Array<number> = []) {
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,6 +196,11 @@ class AuthTest extends PHPUnit_Framework_TestCase {
|
||||
$data->password = 'admin';
|
||||
$data->remember = false;
|
||||
|
||||
$collapsed = R::dispense('collapsed');
|
||||
$collapsed->user_id = 1;
|
||||
$collapsed->column_id = 1;
|
||||
R::store($collapsed);
|
||||
|
||||
$request = new RequestMock();
|
||||
$request->payload = $data;
|
||||
|
||||
|
@ -257,6 +257,7 @@ class ColumnsTest extends PHPUnit_Framework_TestCase {
|
||||
$data->name = 'test';
|
||||
$data->position = 0;
|
||||
$data->board_id = 1;
|
||||
$data->tasks = [];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -248,13 +248,15 @@ class TasksTest extends PHPUnit_Framework_TestCase {
|
||||
|
||||
$data->title = 'task';
|
||||
$data->description = 'the words';
|
||||
$data->assignee = 0;
|
||||
$data->category_id = 0;
|
||||
$data->column_id = 1;
|
||||
$data->color = '';
|
||||
$data->due_date = null;
|
||||
$data->points = null;
|
||||
$data->position = 0;
|
||||
$data->comments = [];
|
||||
$data->attachments = [];
|
||||
$data->assignees = [];
|
||||
$data->categories = [];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -408,6 +408,52 @@ class UsersTest extends PHPUnit_Framework_TestCase {
|
||||
$this->assertEquals('failure', $response->status);
|
||||
}
|
||||
|
||||
public function testToggleCollapsed() {
|
||||
$this->createUser();
|
||||
|
||||
$data = new stdClass();
|
||||
$data->id = 1;
|
||||
|
||||
$args = [];
|
||||
$args['id'] = 2;
|
||||
|
||||
$request = new RequestMock();
|
||||
$request->payload = $data;
|
||||
$request->header = [DataMock::GetJwt(2)];
|
||||
|
||||
// Collapse the column
|
||||
$response = $this->users->toggleCollapsed($request,
|
||||
new ResponseMock(), $args);
|
||||
$this->assertEquals('success', $response->status);
|
||||
|
||||
// Expand the column
|
||||
$response = $this->users->toggleCollapsed($request,
|
||||
new ResponseMock(), $args);
|
||||
$this->assertEquals('success', $response->status);
|
||||
}
|
||||
|
||||
public function testToggleCollapsedNoAccess() {
|
||||
$response = $this->users->toggleCollapsed(new RequestMock(),
|
||||
new ResponseMock(), null);
|
||||
|
||||
$this->assertEquals('failure', $response->status);
|
||||
|
||||
$data = new stdClass();
|
||||
$data->id = 1;
|
||||
|
||||
$args = [];
|
||||
$args['id'] = 2;
|
||||
|
||||
$request = new RequestMock();
|
||||
$request->payload = $data;
|
||||
$request->header = [DataMock::GetJwt(1)];
|
||||
|
||||
$response = $this->users->toggleCollapsed($request,
|
||||
new ResponseMock(), $args);
|
||||
|
||||
$this->assertEquals('failure', $response->status);
|
||||
}
|
||||
|
||||
public function testRemoveUser() {
|
||||
$this->createUser();
|
||||
|
||||
|
Reference in New Issue
Block a user