diff --git a/package-lock.json b/package-lock.json index 36b52200..b39fb74e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1183,7 +1183,6 @@ "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "dev": true, - "optional": true, "requires": { "align-text": "0.1.4", "lazy-cache": "1.0.4" @@ -1193,8 +1192,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true + "dev": true } } }, @@ -5919,6 +5917,11 @@ "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", "dev": true }, + "md2": { + "version": "0.0.28", + "resolved": "https://registry.npmjs.org/md2/-/md2-0.0.28.tgz", + "integrity": "sha512-XQ71eTVKG3oRsGBj3lMLqL8p2inueqDXn++a2EntzWkUPlBZXPCPtlpfI9ER/LAlBKwJZQSqTzFItw7q9+vgvw==" + }, "md5.js": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", @@ -7922,7 +7925,6 @@ "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", "dev": true, - "optional": true, "requires": { "align-text": "0.1.4" } @@ -9174,28 +9176,48 @@ "camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=" + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + } }, "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true + "dev": true, + "requires": { + "source-map": "0.5.6", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + } }, "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true }, "yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=" + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } } } }, @@ -9908,8 +9930,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true + "dev": true }, "wordwrap": { "version": "0.0.3", diff --git a/package.json b/package.json index c2ccd5e0..8fc81828 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "hammerjs": "2.0.8", "highlight.js": "9.12.0", "intl": "1.2.5", + "md2": "0.0.28", "moment": "2.18.1", "ngx-color-picker": "4.3.1", "ngx-cookie-service": "1.0.7", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4550dd78..45056275 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -45,6 +45,10 @@ const appRoutes: Routes = [ path : 'apps/contacts', loadChildren: './main/content/apps/contacts/contacts.module#FuseContactsModule' }, + { + path : 'apps/scrumboard', + loadChildren: './main/content/apps/scrumboard/scrumboard.module#FuseScrumboardModule' + }, { path : '**', redirectTo: 'apps/dashboards/project' diff --git a/src/app/core/components/material-color-picker/material-color-picker.component.html b/src/app/core/components/material-color-picker/material-color-picker.component.html index 152dc617..982968b4 100644 --- a/src/app/core/components/material-color-picker/material-color-picker.component.html +++ b/src/app/core/components/material-color-picker/material-color-picker.component.html @@ -1,4 +1,5 @@ + +
+ + + + + +
+ + diff --git a/src/app/main/content/apps/scrumboard/board/add-list/add-list.component.scss b/src/app/main/content/apps/scrumboard/board/add-list/add-list.component.scss new file mode 100644 index 00000000..9f53dafc --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/add-list/add-list.component.scss @@ -0,0 +1,32 @@ +:host { + .new-list { + border-radius: 2px; + background-color: #EEF0F2; + + .new-list-form-button { + text-transform: none; + font-size: 15px; + padding: 0 16px; + height: 64px; + margin: 0; + width: 100%; + + md-icon { + border-radius: 50%; + height: 40px; + width: 40px; + line-height: 40px; + margin-right: 16px; + } + } + + .new-list-form { + padding: 16px; + height: 64px; + + > input { + height: 100%; + } + } + } +} diff --git a/src/app/main/content/apps/scrumboard/board/add-list/add-list.component.ts b/src/app/main/content/apps/scrumboard/board/add-list/add-list.component.ts new file mode 100644 index 00000000..0d7f6713 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/add-list/add-list.component.ts @@ -0,0 +1,57 @@ +import { Component, EventEmitter, OnInit, Output, ViewChild} from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; + +@Component({ + selector : 'fuse-scrumboard-board-add-list', + templateUrl: './add-list.component.html', + styleUrls : ['./add-list.component.scss'] +}) +export class FuseScrumboardBoardAddListComponent implements OnInit +{ + formActive = false; + form: FormGroup; + @Output() onlistAdd = new EventEmitter(); + @ViewChild('nameInput') nameInputField; + + constructor( + private formBuilder: FormBuilder + ) + { + } + + ngOnInit() + { + + } + + openForm() + { + this.form = this.formBuilder.group({ + name: [''] + }); + this.formActive = true; + this.focusNameField(); + } + + closeForm() + { + this.formActive = false; + } + + focusNameField() + { + setTimeout(() => { + this.nameInputField.nativeElement.focus(); + }); + } + + onFormSubmit() + { + if ( this.form.valid ) + { + this.onlistAdd.next(this.form.getRawValue().name); + this.formActive = false; + } + } + +} diff --git a/src/app/main/content/apps/scrumboard/board/board.component.html b/src/app/main/content/apps/scrumboard/board/board.component.html new file mode 100644 index 00000000..6283d982 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/board.component.html @@ -0,0 +1,86 @@ + + +
+ + +
+ +
+ + +
+ +
+ + + +
+
+ remove_red_eye + + +
+
+ + + +
+ + + + +
+ + +
+ +
+ + +
+ + +
+ + + + + + + + + + +
+ +
+ +
+ + + + + +
diff --git a/src/app/main/content/apps/scrumboard/board/board.component.scss b/src/app/main/content/apps/scrumboard/board/board.component.scss new file mode 100644 index 00000000..9d1ccb1c --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/board.component.scss @@ -0,0 +1,132 @@ +@import "src/app/core/scss/fuse"; + +:host { + + md-sidenav-container { + width: 100%; + height: 100%; + + md-sidenav { + width: 320px !important; + min-width: 320px !important; + max-width: 320px !important; + } + + #board { + flex-direction: column; + display: flex; + height: 100%; + + > .header { + position: relative; + min-height: 96px; + background-image: none; + z-index: 49; + + .header-content { + + .header-boards-button { + margin: 0; + } + + .header-board-name { + font-size: 16px; + + .board-subscribe { + margin-right: 8px; + } + + .editable-buttons { + + md-icon { + color: #FFFFFF !important; + } + } + } + + .right-side { + + > .md-button:last-child { + margin-right: 0; + } + } + + } + } + + #board-selector { + position: absolute; + top: 96px; + right: 0; + left: 0; + height: 192px; + z-index: 48; + padding: 24px; + opacity: 1; + + .board-list-item { + width: 128px; + height: 192px; + padding: 16px; + cursor: pointer; + position: relative; + + .board-name { + text-align: center; + padding: 16px 0; + } + + .selected-icon { + position: absolute; + top: 0; + left: 50%; + width: 32px; + height: 32px; + margin-left: -16px; + border-radius: 50%; + text-align: center; + color: white; + + i { + line-height: 32px !important; + } + } + + &.add-new-board { + opacity: 0.6; + } + } + } + + .board-content-wrapper { + position: relative; + + .board-content { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + height: 100%; + background: #E5E7E8; + overflow-y: hidden; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + + .list-sortable-placeholder { + background: rgba(0, 0, 0, 0.06); + margin-right: 24px; + } + + .new-list-wrapper { + width: 344px; + min-width: 344px; + max-width: 344px; + padding-right: 24px; + } + + } + } + } + } +} diff --git a/src/app/main/content/apps/scrumboard/board/board.component.ts b/src/app/main/content/apps/scrumboard/board/board.component.ts new file mode 100644 index 00000000..4eaa55d7 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/board.component.ts @@ -0,0 +1,60 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ScrumboardService } from '../scrumboard.service'; +import { Subscription } from 'rxjs/Subscription'; +import { Location } from '@angular/common'; +import { List } from '../list.model'; + +@Component({ + selector : 'fuse-scrumboard-board', + templateUrl: './board.component.html', + styleUrls : ['./board.component.scss'] +}) +export class FuseScrumboardBoardComponent implements OnInit, OnDestroy +{ + board: any; + onBoardChanged: Subscription; + + constructor( + private route: ActivatedRoute, + private location: Location, + private scrumboardService: ScrumboardService + ) + { + } + + ngOnInit() + { + this.onBoardChanged = + this.scrumboardService.onBoardChanged + .subscribe(board => { + this.board = board; + }); + } + + onListAdd(newListName) + { + if ( newListName === '' ) + { + return; + } + + this.scrumboardService.addList(new List({name: newListName})); + } + + onBoardNameChanged(newName) + { + this.scrumboardService.updateBoard(); + this.location.go('/apps/scrumboard/boards/' + this.board.id + '/' + this.board.uri); + } + + onDrop(ev) + { + this.scrumboardService.updateBoard(); + } + + ngOnDestroy() + { + this.onBoardChanged.unsubscribe(); + } +} diff --git a/src/app/main/content/apps/scrumboard/board/dialogs/card/card.component.html b/src/app/main/content/apps/scrumboard/board/dialogs/card/card.component.html new file mode 100644 index 00000000..d713ac37 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/dialogs/card/card.component.html @@ -0,0 +1,452 @@ + + +
+ +
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + + +
+ + + +
+ + + + +
+ +
+ +

{{ member.name }}

+
+
+
+
+
+ + + + + + + +
+ + + + + +
+ + + + + + +
+ +
+
+ + + + + + + +
+ + + + +
+ +
+ + + + +
+
+ +
+ +
+ + +
+ {{board.name}} + chevron_right + {{list.name}} +
+ + + +
+ + + + +
+ + +
+ + +
+ remove_red_eye +
+ + + +
+
+ + + +
+ + + +
+ + + +
+ + +
+ +
+
+ label + Labels +
+
+ + + {{board.labels|getById:labelId:'name'}} + close + + +
+
+ +
+
+ supervisor_account + Members +
+
+ + + + close + + +
+
+
+ + + +
+ +
+ +
+ attachment + Attachments +
+ +
+ +
+ +
+ +
+
+ +
+ +
+ {{item.name}} + star + +
+ + {{item.time}} + +
+ + + + + +
+ +
+
+ +
+
+ LINK +
+
+ {{item.url}} + {{item.time}} +
+
+
+ + +
+
+
+ + + +
+ +
+ +
+ + check_box + + {{checklist.name}} + + +
+ + + + +
+ +
+ +
+ +
+ + + {{checklist.checkItemsChecked}} / {{checklist.checkItems.length}} + + + + +
+ +
+ +
+ +
+ + + + + +
+ + + +
+
+ +
+ +
+ add + + + + +
+ + +
+ +
+
+
+ + + +
+ +
+ +
+ comment + Comments +
+ +
+
+ +
+ + + + +
+ +
+ +
+
+ +
+ + + +
+
+ {{board.members | getById: comment.idMember:'name'}} +
+
{{comment.message}}
+
{{comment.time}}
+
+
+
+
+
+ + + +
+ +
+ +
+ list + Activity +
+ +
+
+ +
{{board.members| getById:activity.idMember:'name'}}
+
{{activity.message}}
+
{{activity.time}}
+
+
+ +
+
+ + +
+ +
diff --git a/src/app/main/content/apps/scrumboard/board/dialogs/card/card.component.scss b/src/app/main/content/apps/scrumboard/board/dialogs/card/card.component.scss new file mode 100644 index 00000000..4bf9dae0 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/dialogs/card/card.component.scss @@ -0,0 +1,437 @@ +@import "src/app/core/scss/fuse"; + +:host { + display: flex; + flex-direction: column; +} + +.scrumboard-card-dialog { + + .mat-dialog-container { + padding: 0; + width: 720px; + + .mat-toolbar { + + .due-date { + + md2-datepicker { + min-width: initial; + + .md2-datepicker-trigger { + padding: 0; + + .md2-datepicker-button { + display: block; + position: relative; + top: 0; + left: 0; + line-height: normal; + } + + .md2-datepicker-input { + display: none; + } + } + } + } + } + + .mat-dialog-content { + position: relative; + background-color: #F5F5F5; + + .card-breadcrumb { + font-weight: 500; + font-size: 14px; + } + + .card-subscribe { + margin-right: 8px; + color: rgba(0, 0, 0, 0.6); + } + + .picker { + width: 140px; + min-width: 140px; + + } + + .card-name { + width: 100%; + font-size: 22px; + + @include media-breakpoint(xs) { + font-size: 14px; + } + } + + .due-date { + + md2-datepicker { + width: 180px; + min-width: 180px; + + .md2-datepicker-trigger { + padding-top: 5px; + padding-bottom: 5px; + + .md2-datepicker-button { + top: 0; + } + + .md2-datepicker-input { + min-width: initial; + } + } + } + + .remove-due-date { + } + } + + .description { + padding-bottom: 16px; + } + + .sections { + + .section { + border-bottom: 1px solid rgba(0, 0, 0, 0.12); + margin-bottom: 32px; + + &:last-child { + border-bottom: none; + margin-bottom: 0; + + .section-content { + padding-bottom: 0; + } + } + + .section-header { + font-size: 16px; + + md-icon { + margin-right: 8px; + color: rgba(0, 0, 0, 0.6); + } + + .section-title { + font-weight: 500; + } + } + + .section-content { + padding: 24px 0 32px 0; + } + + .labels { + + .section-content { + padding: 8px 0 32px 0; + } + + .label-chips { + + box-shadow: none; + padding: 0; + + .label-chip { + display: block; + + .chip-remove { + cursor: pointer; + } + } + } + } + + .members { + + .section-content { + padding: 8px 0 32px 0; + } + + .member-chips { + box-shadow: none; + padding: 0; + + .member-chip { + padding: 4px 12px 4px 4px; + + .member-chip-avatar { + width: 32px; + border-radius: 50%; + } + + .chip-remove { + cursor: pointer; + } + } + } + } + + .attachments { + + .attachment { + margin-bottom: 16px; + + .attachment-preview { + background-color: #EEF0F2; + width: 160px; + height: 128px; + background-size: contain; + background-position: 50% 50%; + background-repeat: no-repeat; + margin-right: 24px; + font-weight: 500; + color: rgba(0, 0, 0, 0.6); + } + + .attachment-content { + + .attachment-url, + .attachment-name { + font-weight: 500; + font-size: 16px; + } + + .attachment-is-cover { + margin-left: 6px; + } + + .attachment-time { + color: rgba(0, 0, 0, 0.6); + } + + .attachment-actions-button { + background-color: white; + text-transform: capitalize; + margin: 12px 0 0 0; + padding-left: 12px; + + md-icon { + margin-left: 8px; + color: rgba(0, 0, 0, 0.6); + } + } + } + } + + .add-attachment-button { + margin: 0; + + md-icon { + color: rgba(0, 0, 0, 0.6); + margin-right: 8px; + } + + span { + font-weight: 500; + text-transform: capitalize; + } + } + } + + .checklist { + + .checklist-progress { + margin-bottom: 16px; + + .checklist-progress-value { + margin-right: 12px; + font-weight: 500; + white-space: nowrap; + font-size: 14px; + } + + .checklist-progressbar { + } + } + + .editable-wrap { + flex: 1 + } + + .check-items { + + .check-item { + + md-checkbox { + margin-bottom: 0; + + .md-label { + font-size: 14px; + } + + &.md-checked { + + .md-label { + text-decoration: line-through; + color: rgba(0, 0, 0, 0.6); + } + } + } + } + } + + .new-check-item-form { + padding-top: 16px; + + md-input-container { + margin: 0; + } + + .md-button { + margin: 0 0 0 16px; + } + } + } + + .comments { + + .comment { + margin-bottom: 16px; + + .comment-member-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + margin-right: 16px; + } + + .comment-member-name { + font-size: 14px; + font-weight: 500; + } + + .comment-time { + font-size: 12px; + } + + .comment-bubble { + position: relative; + padding: 8px; + background-color: white; + border: 1px solid rgb(220, 223, 225); + font-size: 14px; + margin: 4px 0; + + &:after, + &:before { + content: ' '; + position: absolute; + width: 0; + height: 0; + } + + &:after { + left: -7px; + right: auto; + top: 0px; + bottom: auto; + border: 11px solid; + border-color: white transparent transparent transparent; + } + + &:before { + left: -9px; + right: auto; + top: -1px; + bottom: auto; + border: 8px solid; + border-color: rgb(220, 223, 225) transparent transparent transparent; + } + } + + &.new-comment { + + md-input-container { + margin: 0; + } + } + } + } + + .activities { + + .activity { + margin-bottom: 12px; + + .activity-member-avatar { + width: 24px; + height: 24px; + border-radius: 50%; + margin-right: 16px; + } + + .activity-member-name { + font-size: 14px; + font-weight: 500; + margin-right: 8px; + } + + .activity-message { + font-size: 14px; + margin-right: 8px; + } + + .activity-time { + font-size: 12px; + } + } + } + } + } + } + + } +} + +.scrumboard-members-menu { + width: 240px; + .mat-checkbox-layout, + .mat-checkbox-label { + display: flex; + flex: 1; + } +} + +.scrumboard-labels-menu { + + .mat-menu-content { + padding-bottom: 0; + + .mat-checkbox-layout, + .mat-checkbox-label { + display: flex; + flex: 1; + } + + .views { + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; + width: 240px; + min-width: 240px; + max-width: 240px; + min-height: 240px; + + .view { + position: absolute; + width: 240px; + height: 100%; + bottom: 0; + left: 0; + right: 0; + top: 0; + + > .header { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + } + } + } + } +} diff --git a/src/app/main/content/apps/scrumboard/board/dialogs/card/card.component.ts b/src/app/main/content/apps/scrumboard/board/dialogs/card/card.component.ts new file mode 100644 index 00000000..1951a62e --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/dialogs/card/card.component.ts @@ -0,0 +1,277 @@ +import { Component, Inject, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { MD_DIALOG_DATA, MdDialog, MdDialogRef, MdMenuTrigger } from '@angular/material'; +import { Subscription } from 'rxjs/Subscription'; +import { ScrumboardService } from '../../../scrumboard.service'; +import { NgForm } from '@angular/forms/src/forms'; +import { FuseUtils } from '../../../../../../../core/fuseUtils'; +import { FuseConfirmDialogComponent } from '../../../../../../../core/components/confirm-dialog/confirm-dialog.component'; + +@Component({ + selector : 'fuse-scrumboard-board-card-dialog', + templateUrl : './card.component.html', + styleUrls : ['./card.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class FuseScrumboardCardDialogComponent implements OnInit, OnDestroy +{ + card: any; + board: any; + list: any; + + onBoardChanged: Subscription; + toggleInArray = FuseUtils.toggleInArray; + + @ViewChild('checklistMenuTrigger') checklistMenu: MdMenuTrigger; + @ViewChild('newCheckListTitleField') newCheckListTitleField; + + confirmDialogRef: MdDialogRef; + + constructor( + public dialogRef: MdDialogRef, + @Inject(MD_DIALOG_DATA) private data: any, + public dialog: MdDialog, + private scrumboardService: ScrumboardService + ) + { + + } + + ngOnInit() + { + this.onBoardChanged = + this.scrumboardService.onBoardChanged + .subscribe(board => { + this.board = board; + + this.card = this.board.cards.find((_card) => { + return this.data.cardId === _card.id; + }); + + this.list = this.board.lists.find((_list) => { + return this.data.listId === _list.id; + }); + }); + } + + /** + * Remove Due date + */ + removeDueDate() + { + this.card.due = ''; + this.updateCard(); + } + + /** + * Toggle Subscribe + */ + toggleSubscribe() + { + this.card.subscribed = !this.card.subscribed; + + this.updateCard(); + } + + /** + * Toggle Cover Image + * @param attachmentId + */ + toggleCoverImage(attachmentId) + { + if ( this.card.idAttachmentCover === attachmentId ) + { + this.card.idAttachmentCover = ''; + } + else + { + this.card.idAttachmentCover = attachmentId; + } + + this.updateCard(); + } + + /** + * Remove Attachment + * @param attachment + */ + removeAttachment(attachment) + { + if ( attachment.id === this.card.idAttachmentCover ) + { + this.card.idAttachmentCover = ''; + } + + this.card.attachments.splice(this.card.attachments.indexOf(attachment), 1); + + this.updateCard(); + } + + /** + * Remove Checklist + * @param checklist + */ + removeChecklist(checklist) + { + this.card.checklists.splice(this.card.checklists.indexOf(checklist), 1); + + this.updateCard(); + } + + /** + * Update Checked Count + * @param list + */ + updateCheckedCount(list) + { + const checkItems = list.checkItems; + let checkedItems = 0; + let allCheckedItems = 0; + let allCheckItems = 0; + + for ( const checkItem of checkItems ) + { + if ( checkItem.checked ) + { + checkedItems++; + } + } + + list.checkItemsChecked = checkedItems; + + for ( const item of this.card.checklists ) + { + allCheckItems += item.checkItems.length; + allCheckedItems += item.checkItemsChecked; + } + + this.card.checkItems = allCheckItems; + this.card.checkItemsChecked = allCheckedItems; + + this.updateCard(); + } + + /** + * Remove Checklist Item + * @param checkItem + * @param checklist + */ + removeChecklistItem(checkItem, checklist) + { + checklist.checkItems.splice(checklist.checkItems.indexOf(checkItem), 1); + + this.updateCheckedCount(checklist); + + this.updateCard(); + } + + /** + * Add Check Item + * @param {NgForm} form + * @param checkList + */ + addCheckItem(form: NgForm, checkList) + { + const checkItemVal = form.value.checkItem; + + if ( !checkItemVal || checkItemVal === '' ) + { + return; + } + + const newCheckItem = { + 'name' : checkItemVal, + 'checked': false + }; + + checkList.checkItems.push(newCheckItem); + + this.updateCheckedCount(checkList); + + form.setValue({checkItem: ''}); + + this.updateCard(); + } + + /** + * Add Checklist + * @param {NgForm} form + */ + addChecklist(form: NgForm) + { + this.card.checklists.push({ + id : FuseUtils.generateGUID(), + name : form.value.checklistTitle, + checkItemsChecked: 0, + checkItems : [] + }); + + form.setValue({checklistTitle: ''}); + form.resetForm(); + this.checklistMenu.closeMenu(); + this.updateCard(); + } + + /** + * On Checklist Menu Open + */ + onChecklistMenuOpen() + { + setTimeout(() => { + this.newCheckListTitleField.nativeElement.focus(); + }); + } + + /** + * Add New Comment + * @param {NgForm} form + */ + addNewComment(form: NgForm) + { + const newCommentText = form.value.newComment; + + const newComment = { + idMember: '36027j1930450d8bf7b10158', + message : newCommentText, + time : 'now' + }; + + this.card.comments.unshift(newComment); + + form.setValue({newComment: ''}); + + this.updateCard(); + } + + /** + * Remove Card + */ + removeCard() + { + this.confirmDialogRef = this.dialog.open(FuseConfirmDialogComponent, { + disableClose: false + }); + + this.confirmDialogRef.componentInstance.confirmMessage = 'Are you sure you want to delete the card?'; + + this.confirmDialogRef.afterClosed().subscribe(result => { + if ( result ) + { + this.dialogRef.close(); + this.scrumboardService.removeCard(this.card.id, this.list.id); + } + }); + } + + /** + * Update Card + */ + updateCard() + { + this.scrumboardService.updateCard(this.card); + } + + ngOnDestroy() + { + this.onBoardChanged.unsubscribe(); + } +} diff --git a/src/app/main/content/apps/scrumboard/board/dialogs/card/label-selector/label-selector.component.html b/src/app/main/content/apps/scrumboard/board/dialogs/card/label-selector/label-selector.component.html new file mode 100644 index 00000000..8011f485 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/dialogs/card/label-selector/label-selector.component.html @@ -0,0 +1,71 @@ +
+ +
+ +
+
Labels
+ +
+ +
+ +
+ + + {{label.name}} + + + +
+
+
+ +
+ +
+
Edit Label
+ +
+ +
+ + + + + +
+ +
+ +
+ +
+
Add Label
+ +
+ +
+ +
+ + + + + +
+ + +
+
+
diff --git a/src/app/main/content/apps/scrumboard/board/dialogs/card/label-selector/label-selector.component.scss b/src/app/main/content/apps/scrumboard/board/dialogs/card/label-selector/label-selector.component.scss new file mode 100644 index 00000000..ff96a72e --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/dialogs/card/label-selector/label-selector.component.scss @@ -0,0 +1,37 @@ +.scrumboard-labels-menu { + + .mat-menu-content { + padding-bottom: 0; + + .mat-checkbox-layout, + .mat-checkbox-label { + display: flex; + flex: 1; + } + + .views { + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; + width: 240px; + min-width: 240px; + max-width: 240px; + min-height: 240px; + + .view { + position: absolute; + width: 240px; + height: 100%; + bottom: 0; + left: 0; + right: 0; + top: 0; + + > .header { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + } + } + } + } +} diff --git a/src/app/main/content/apps/scrumboard/board/dialogs/card/label-selector/label-selector.component.ts b/src/app/main/content/apps/scrumboard/board/dialogs/card/label-selector/label-selector.component.ts new file mode 100644 index 00000000..9eda6022 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/dialogs/card/label-selector/label-selector.component.ts @@ -0,0 +1,70 @@ +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { Subscription } from 'rxjs/Subscription'; +import { ScrumboardService } from '../../../../scrumboard.service'; +import { FuseUtils } from '../../../../../../../../core/fuseUtils'; +import { Animations } from '../../../../../../../../core/animations'; + +@Component({ + selector : 'fuse-scrumboard-label-selector', + templateUrl : './label-selector.component.html', + styleUrls : ['./label-selector.component.scss'], + encapsulation: ViewEncapsulation.None, + animations : [Animations.slideInLeft, Animations.slideInRight] +}) + +export class FuseScrumboardLabelSelectorComponent implements OnInit, OnDestroy +{ + board: any; + @Input('card') card: any; + @Output() onCardLabelsChange = new EventEmitter(); + + labelsMenuView = 'labels'; + selectedLabel: any; + newLabel = { + 'id' : '', + 'name' : '', + 'color': 'md-blue-400-bg' + }; + toggleInArray = FuseUtils.toggleInArray; + + onBoardChanged: Subscription; + + constructor( + private scrumboardService: ScrumboardService + ) + { + } + + ngOnInit() + { + this.onBoardChanged = + this.scrumboardService.onBoardChanged + .subscribe(board => { + this.board = board; + }); + } + + cardLabelsChanged() + { + this.onCardLabelsChange.next(); + } + + onLabelChange() + { + this.scrumboardService.updateBoard(); + } + + addNewLabel() + { + this.newLabel.id = FuseUtils.generateGUID(); + this.board.labels.push(Object.assign({}, this.newLabel)); + this.newLabel.name = ''; + this.labelsMenuView = 'labels'; + } + + ngOnDestroy() + { + this.onBoardChanged.unsubscribe(); + } + +} diff --git a/src/app/main/content/apps/scrumboard/board/edit-board-name/edit-board-name.component.html b/src/app/main/content/apps/scrumboard/board/edit-board-name/edit-board-name.component.html new file mode 100644 index 00000000..e07254d1 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/edit-board-name/edit-board-name.component.html @@ -0,0 +1,21 @@ +
+ {{board.name}} + +
+ + +
+ + + + + +
diff --git a/src/app/main/content/apps/scrumboard/board/edit-board-name/edit-board-name.component.scss b/src/app/main/content/apps/scrumboard/board/edit-board-name/edit-board-name.component.scss new file mode 100644 index 00000000..36f8f043 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/edit-board-name/edit-board-name.component.scss @@ -0,0 +1,8 @@ +:host { + .board-name { + text-overflow: ellipsis; + overflow: hidden; + font-size: 15px; + font-weight: 500; + } +} diff --git a/src/app/main/content/apps/scrumboard/board/edit-board-name/edit-board-name.component.ts b/src/app/main/content/apps/scrumboard/board/edit-board-name/edit-board-name.component.ts new file mode 100644 index 00000000..d35face2 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/edit-board-name/edit-board-name.component.ts @@ -0,0 +1,61 @@ +import { Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; + +@Component({ + selector : 'fuse-scrumboard-edit-board-name', + templateUrl: './edit-board-name.component.html', + styleUrls : ['./edit-board-name.component.scss'] +}) +export class FuseScrumboardEditBoardNameComponent implements OnInit +{ + formActive = false; + form: FormGroup; + @Input() board; + @Output() onNameChanged = new EventEmitter(); + @ViewChild('nameInput') nameInputField; + + constructor( + private formBuilder: FormBuilder + ) + { + } + + ngOnInit() + { + + } + + openForm() + { + this.form = this.formBuilder.group({ + name: [this.board.name] + }); + this.formActive = true; + this.focusNameField(); + } + + closeForm() + { + this.formActive = false; + } + + focusNameField() + { + setTimeout(() => { + this.nameInputField.nativeElement.focus(); + }); + } + + onFormSubmit() + { + if ( this.form.valid ) + { + this.board.name = this.form.getRawValue().name; + this.board.uri = encodeURIComponent(this.board.name).replace(/%20/g, '-').toLowerCase(); + + this.onNameChanged.next(this.board.name); + this.formActive = false; + } + } + +} diff --git a/src/app/main/content/apps/scrumboard/board/list/add-card/add-card.component.html b/src/app/main/content/apps/scrumboard/board/list/add-card/add-card.component.html new file mode 100644 index 00000000..82c4bdbd --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/add-card/add-card.component.html @@ -0,0 +1,30 @@ +
+
+ add +
+ Add a card +
+ +
+ +
+ + + + + +
+ + + + +
+
+
diff --git a/src/app/main/content/apps/scrumboard/board/list/add-card/add-card.component.scss b/src/app/main/content/apps/scrumboard/board/list/add-card/add-card.component.scss new file mode 100644 index 00000000..80fd08b3 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/add-card/add-card.component.scss @@ -0,0 +1,39 @@ +:host { + .add-card-button { + position: relative; + height: 48px; + min-height: 48px; + padding: 0 16px; + text-align: left; + text-transform: none; + font-weight: 500; + font-size: 14px; + background-color: #DCDFE2; + cursor: pointer; + border-radius: 2px; + + md-icon { + margin-right: 8px; + color: rgba(0, 0, 0, 0.6); + } + } + + .add-card-form-wrapper { + background-color: #DCDFE2; + + .add-card-form { + z-index: 999; + background: white; + display: block; + position: relative; + padding: 8px; + border-top: 1px solid rgba(0, 0, 0, 0.12); + + md-input-container { + width: 100%; + margin: 0; + padding: 12px 8px; + } + } + } +} diff --git a/src/app/main/content/apps/scrumboard/board/list/add-card/add-card.component.ts b/src/app/main/content/apps/scrumboard/board/list/add-card/add-card.component.ts new file mode 100644 index 00000000..053b80bc --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/add-card/add-card.component.ts @@ -0,0 +1,57 @@ +import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; + +@Component({ + selector : 'fuse-scrumboard-board-add-card', + templateUrl: './add-card.component.html', + styleUrls : ['./add-card.component.scss'] +}) +export class FuseScrumboardBoardAddCardComponent implements OnInit +{ + formActive = false; + form: FormGroup; + @Output() onCardAdd = new EventEmitter(); + @ViewChild('nameInput') nameInputField; + + constructor( + private formBuilder: FormBuilder + ) + { + } + + ngOnInit() + { + } + + openForm() + { + this.form = this.formBuilder.group({ + name: '' + }); + this.formActive = true; + this.focusNameField(); + } + + closeForm() + { + this.formActive = false; + } + + focusNameField() + { + setTimeout(() => { + this.nameInputField.nativeElement.focus(); + }); + } + + onFormSubmit() + { + if ( this.form.valid ) + { + const cardName = this.form.getRawValue().name; + this.onCardAdd.next(cardName); + this.formActive = false; + } + } +} + diff --git a/src/app/main/content/apps/scrumboard/board/list/card/card.component.html b/src/app/main/content/apps/scrumboard/board/list/card/card.component.html new file mode 100644 index 00000000..fd772ed0 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/card/card.component.html @@ -0,0 +1,124 @@ + +
+ +
+ + + +
+ + +
+ +
+ + + +
+ + + + +
+ + + +
{{card.name}}
+ + +
+ + + + access_time + {{card.due | date:'mediumDate'}} + + + + + + + check_circle + {{card.checkItemsChecked}} + / + {{card.checkItems}} + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ + + + + diff --git a/src/app/main/content/apps/scrumboard/board/list/card/card.component.scss b/src/app/main/content/apps/scrumboard/board/list/card/card.component.scss new file mode 100644 index 00000000..7673454c --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/card/card.component.scss @@ -0,0 +1,147 @@ +@import "src/app/core/scss/fuse"; + +.scrumboard-board-card { + position: relative; + display: block; + width: 100%; + margin: 16px 0; + background-color: white; + color: #000; + border-radius: 2px; + transition: box-shadow 150ms ease; + cursor: pointer; + + .list-card-sort-handle { + display: none; + position: absolute; + top: 0; + right: 0; + padding: 4px; + background: rgba(255, 255, 255, 0.8); + } + + .list-card-cover { + } + + .list-card-details { + padding: 16px 16px 0 16px; + + .list-card-labels { + margin-bottom: 6px; + + .list-card-label { + width: 32px; + height: 6px; + border-radius: 6px; + margin: 0 6px 6px 0; + } + } + + .list-card-name { + font-size: 14px; + font-weight: 500; + margin-bottom: 12px; + } + + .list-card-badges { + margin-bottom: 12px; + + .badge { + margin-right: 8px; + padding: 4px 8px; + border-radius: 2px; + background-color: rgba(0, 0, 0, 0.4); + color: #FFFFFF; + + md-icon { + margin-right: 4px; + } + + &.due-date { + background-color: mat-color(mat-palette($mat-green));; + + &.overdue { + background-color: mat-color(mat-palette($mat-red)); + } + } + + &.check-items { + + &.completed { + background-color: mat-color(mat-palette($mat-green)); + } + } + } + } + + .list-card-members { + margin-bottom: 12px; + + .list-card-member { + margin-right: 8px; + + .list-card-member-avatar { + border-radius: 50%; + width: 32px; + height: 32px; + } + } + } + } + + .list-card-footer { + border-top: 1px solid rgba(0, 0, 0, 0.12); + padding: 0 16px; + + .list-card-footer-item { + height: 48px; + margin-right: 12px; + color: rgba(0, 0, 0, 0.66); + + .value { + padding-left: 8px; + } + + &:last-of-type { + margin-right: 0; + } + } + } + + &:not(.has-handle):not(.move-disabled), + &.has-handle [ngxdraghandle], + &.has-handle [ngxDragHandle] { + //cursor: move; + } + + .ngx-dnd-content { + user-select: none; + } + + &.gu-mirror { + position: fixed !important; + margin: 0 !important; + z-index: 9999 !important; + opacity: 0.8; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; + filter: alpha(opacity=80); + @include mat-elevation(7); + } + + &.gu-hide { + display: none !important; + } + + &.gu-unselectable { + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important; + } + + &.gu-transit { + opacity: 0.2; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)"; + filter: alpha(opacity=20); + } +} diff --git a/src/app/main/content/apps/scrumboard/board/list/card/card.component.ts b/src/app/main/content/apps/scrumboard/board/list/card/card.component.ts new file mode 100644 index 00000000..61aaf9ac --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/card/card.component.ts @@ -0,0 +1,44 @@ +import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ScrumboardService } from '../../../scrumboard.service'; +import * as moment from 'moment'; + +@Component({ + selector : 'fuse-scrumboard-board-card', + templateUrl : './card.component.html', + styleUrls : ['./card.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class FuseScrumboardBoardCardComponent implements OnInit +{ + @Input() cardId; + card: any; + board: any; + + constructor( + private route: ActivatedRoute, + private scrumboardService: ScrumboardService + ) + { + } + + ngOnInit() + { + this.board = this.route.snapshot.data.board; + this.card = this.board.cards.filter((card) => { + return this.cardId === card.id; + })[0]; + } + + /** + * Is the card overdue? + * + * @param cardDate + * @returns {boolean} + */ + isOverdue(cardDate) + { + return moment() > moment(new Date(cardDate)); + } + +} diff --git a/src/app/main/content/apps/scrumboard/board/list/edit-list-name/edit-list-name.component.html b/src/app/main/content/apps/scrumboard/board/list/edit-list-name/edit-list-name.component.html new file mode 100644 index 00000000..c8218cad --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/edit-list-name/edit-list-name.component.html @@ -0,0 +1,17 @@ +
+ {{list.name}} +
+ +
+ + + + + +
diff --git a/src/app/main/content/apps/scrumboard/board/list/edit-list-name/edit-list-name.component.scss b/src/app/main/content/apps/scrumboard/board/list/edit-list-name/edit-list-name.component.scss new file mode 100644 index 00000000..aee46891 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/edit-list-name/edit-list-name.component.scss @@ -0,0 +1,9 @@ +:host { + .list-header-name { + text-overflow: ellipsis; + overflow: hidden; + font-size: 15px; + font-weight: 500; + cursor: pointer; + } +} diff --git a/src/app/main/content/apps/scrumboard/board/list/edit-list-name/edit-list-name.component.ts b/src/app/main/content/apps/scrumboard/board/list/edit-list-name/edit-list-name.component.ts new file mode 100644 index 00000000..f480c7cd --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/edit-list-name/edit-list-name.component.ts @@ -0,0 +1,59 @@ +import { Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; + +@Component({ + selector : 'fuse-scrumboard-board-edit-list-name', + templateUrl: './edit-list-name.component.html', + styleUrls : ['./edit-list-name.component.scss'] +}) +export class FuseScrumboardBoardEditListNameComponent implements OnInit +{ + formActive = false; + form: FormGroup; + @Input() list; + @Output() onNameChanged = new EventEmitter(); + @ViewChild('nameInput') nameInputField; + + constructor( + private formBuilder: FormBuilder + ) + { + } + + ngOnInit() + { + + } + + openForm() + { + this.form = this.formBuilder.group({ + name: [this.list.name] + }); + this.formActive = true; + this.focusNameField(); + } + + closeForm() + { + this.formActive = false; + } + + focusNameField() + { + setTimeout(() => { + this.nameInputField.nativeElement.focus(); + }); + } + + onFormSubmit() + { + if ( this.form.valid ) + { + this.list.name = this.form.getRawValue().name; + this.onNameChanged.next(this.list.name); + this.formActive = false; + } + } + +} diff --git a/src/app/main/content/apps/scrumboard/board/list/list.component.html b/src/app/main/content/apps/scrumboard/board/list/list.component.html new file mode 100644 index 00000000..62b1b29a --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/list.component.html @@ -0,0 +1,48 @@ +
+ + +
+ + + + +
+ + + + +
+ +
+ + + +
+ +
+ + +
+
+ + + + + + +
diff --git a/src/app/main/content/apps/scrumboard/board/list/list.component.scss b/src/app/main/content/apps/scrumboard/board/list/list.component.scss new file mode 100644 index 00000000..5c19900b --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/list.component.scss @@ -0,0 +1,90 @@ +@import "src/app/core/scss/fuse"; + +.scrumboard-board-list { + width: 344px; + min-width: 344px; + max-width: 344px; + padding-right: 24px; + height: 100%; + + .list { + max-height: 100%; + background-color: #EEF0F2; + color: #000; + border-radius: 2px; + transition: box-shadow 150ms ease; + + .list-header { + height: 64px; + min-height: 64px; + padding: 0 8px 0 16px; + border-bottom: 1px solid rgba(0, 0, 0, 0.12); + + @include media-breakpoint(xs) { + height: 48px; + min-height: 48px; + } + } + + .list-content { + position: relative; + overflow: hidden; + overflow-y: auto; + min-height: 0; + + .list-cards { + position: relative; + min-height: 32px; + padding: 0 16px; + } + } + + .list-footer { + display: flex; + flex-direction: column; + flex: 1 0 auto; + min-height: 48px; + } + + } + + &:not(.has-handle):not(.move-disabled), + &.has-handle [ngxdraghandle], + &.has-handle [ngxDragHandle] { + //cursor: move; + } + + .ngx-dnd-content { + user-select: none; + } + + &.gu-mirror { + position: fixed !important; + margin: 0 !important; + z-index: 9999 !important; + opacity: 0.8; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; + filter: alpha(opacity=80); + + > .list { + @include mat-elevation(7); + } + } + + &.gu-hide { + display: none !important; + } + + &.gu-unselectable { + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important; + } + + &.gu-transit { + opacity: 0.2; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)"; + filter: alpha(opacity=20); + } +} diff --git a/src/app/main/content/apps/scrumboard/board/list/list.component.ts b/src/app/main/content/apps/scrumboard/board/list/list.component.ts new file mode 100644 index 00000000..af0f398f --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/list/list.component.ts @@ -0,0 +1,108 @@ +import { Component, Input, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { FuseUtils } from '../../../../../../core/fuseUtils'; +import { ScrumboardService } from 'app/main/content/apps/scrumboard/scrumboard.service'; +import { ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs/Subscription'; +import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar'; +import { MdDialog, MdDialogRef } from '@angular/material'; +import { FuseScrumboardCardDialogComponent } from '../dialogs/card/card.component'; +import { FuseConfirmDialogComponent } from '../../../../../../core/components/confirm-dialog/confirm-dialog.component'; +import { Card } from '../../card.model'; + +@Component({ + selector : 'fuse-scrumboard-board-list', + templateUrl : './list.component.html', + styleUrls : ['./list.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class FuseScrumboardBoardListComponent implements OnInit, OnDestroy +{ + board: any; + dialogRef: any; + + @Input() list; + @ViewChild(PerfectScrollbarDirective) listScroll: PerfectScrollbarDirective; + + onBoardChanged: Subscription; + + confirmDialogRef: MdDialogRef; + + constructor( + private route: ActivatedRoute, + private scrumboardService: ScrumboardService, + public dialog: MdDialog + ) + { + } + + ngOnInit() + { + this.onBoardChanged = + this.scrumboardService.onBoardChanged + .subscribe(board => { + this.board = board; + }); + + } + + onListNameChanged(newListName) + { + this.list.name = newListName; + } + + onCardAdd(newCardName) + { + if ( newCardName === '' ) + { + return; + } + + this.scrumboardService.addCard(this.list.id, new Card({name: newCardName})); + + setTimeout(() => { + this.listScroll.scrollToBottom(0, 400); + }); + + } + + removeList(listId) + { + this.confirmDialogRef = this.dialog.open(FuseConfirmDialogComponent, { + disableClose: false + }); + + this.confirmDialogRef.componentInstance.confirmMessage = 'Are you sure you want to delete the list and it\'s all cards?'; + + this.confirmDialogRef.afterClosed().subscribe(result => { + if ( result ) + { + this.scrumboardService.removeList(listId); + } + }); + } + + openCardDialog(cardId) + { + this.dialogRef = this.dialog.open(FuseScrumboardCardDialogComponent, { + panelClass: 'scrumboard-card-dialog', + data : { + cardId: cardId, + listId: this.list.id + } + }); + this.dialogRef.afterClosed() + .subscribe(response => { + + }); + } + + onDrop(ev) + { + this.scrumboardService.updateBoard(); + } + + ngOnDestroy() + { + this.onBoardChanged.unsubscribe(); + } +} diff --git a/src/app/main/content/apps/scrumboard/board/sidenavs/settings/board-color-selector/board-color-selector.component.html b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/board-color-selector/board-color-selector.component.html new file mode 100644 index 00000000..678dbf0d --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/board-color-selector/board-color-selector.component.html @@ -0,0 +1,15 @@ + + + +

{{color.key}}

+ check + +
+ +
diff --git a/src/app/main/content/apps/scrumboard/board/sidenavs/settings/board-color-selector/board-color-selector.component.scss b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/board-color-selector/board-color-selector.component.scss new file mode 100644 index 00000000..d6b3d042 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/board-color-selector/board-color-selector.component.scss @@ -0,0 +1,10 @@ +:host { + + .colors { + + .color { + position: relative; + cursor: pointer; + } + } +} diff --git a/src/app/main/content/apps/scrumboard/board/sidenavs/settings/board-color-selector/board-color-selector.component.ts b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/board-color-selector/board-color-selector.component.ts new file mode 100644 index 00000000..c4a4195d --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/board-color-selector/board-color-selector.component.ts @@ -0,0 +1,43 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { MatColors } from '../../../../../../../../core/matColors'; +import { ScrumboardService } from '../../../../scrumboard.service'; +import { Subscription } from 'rxjs/Subscription'; + +@Component({ + selector : 'fuse-scrumboard-board-color-selector', + templateUrl: './board-color-selector.component.html', + styleUrls : ['./board-color-selector.component.scss'] +}) +export class FuseScrumboardBoardColorSelectorComponent implements OnInit, OnDestroy +{ + colors: any; + board: any; + onBoardChanged: Subscription; + + constructor( + private scrumboardService: ScrumboardService + ) + { + this.colors = MatColors.all; + } + + ngOnInit() + { + this.onBoardChanged = + this.scrumboardService.onBoardChanged + .subscribe(board => { + this.board = board; + }); + } + + setColor(color) + { + this.board.settings.color = color; + this.scrumboardService.updateBoard(); + } + + ngOnDestroy() + { + this.onBoardChanged.unsubscribe(); + } +} diff --git a/src/app/main/content/apps/scrumboard/board/sidenavs/settings/settings.component.html b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/settings.component.html new file mode 100644 index 00000000..43dd7966 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/settings.component.html @@ -0,0 +1,76 @@ +
+ +
+ + +
+
Settings
+
+ + + +
+ + +
+ +
+ +
+ + +
+
Background Color
+ +
+ + + +
+ +
+ + +
+
diff --git a/src/app/main/content/apps/scrumboard/board/sidenavs/settings/settings.component.scss b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/settings.component.scss new file mode 100644 index 00000000..61470199 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/settings.component.scss @@ -0,0 +1,38 @@ +:host { + display: flex; + flex-direction: column; + flex: 1 0 auto; + height: 100%; + + .views { + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; + height: 100%; + + .view { + position: absolute; + width: 100%; + height: 100%; + bottom: 0; + left: 0; + right: 0; + top: 0; + display: flex; + flex-direction: column; + + > .header { + flex: 0 1 auto; + height: 64px; + min-height: 64px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + } + + > .content { + flex: 1 1 auto; + overflow-y: auto; + } + } + } +} diff --git a/src/app/main/content/apps/scrumboard/board/sidenavs/settings/settings.component.ts b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/settings.component.ts new file mode 100644 index 00000000..b23eb83c --- /dev/null +++ b/src/app/main/content/apps/scrumboard/board/sidenavs/settings/settings.component.ts @@ -0,0 +1,50 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs/Subscription'; +import { ScrumboardService } from '../../../scrumboard.service'; +import { Animations } from '../../../../../../../core/animations'; + +@Component({ + selector : 'fuse-scrumboard-board-settings', + templateUrl: './settings.component.html', + styleUrls : ['./settings.component.scss'], + animations : [Animations.slideInLeft, Animations.slideInRight] +}) +export class FuseScrumboardBoardSettingsSidenavComponent implements OnInit, OnDestroy +{ + board: any; + view = 'main'; + onBoardChanged: Subscription; + + constructor( + private scrumboardService: ScrumboardService + ) + { + + } + + ngOnInit() + { + this.onBoardChanged = + this.scrumboardService.onBoardChanged + .subscribe(board => { + this.board = board; + }); + } + + toggleCardCover() + { + this.board.settings.cardCoverImages = !this.board.settings.cardCoverImages; + this.scrumboardService.updateBoard(); + } + + toggleSubcription() + { + this.board.settings.subscribed = !this.board.settings.subscribed; + this.scrumboardService.updateBoard(); + } + + ngOnDestroy() + { + this.onBoardChanged.unsubscribe(); + } +} diff --git a/src/app/main/content/apps/scrumboard/card.model.ts b/src/app/main/content/apps/scrumboard/card.model.ts new file mode 100644 index 00000000..5f8ea248 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/card.model.ts @@ -0,0 +1,37 @@ +import { FuseUtils } from '../../../../core/fuseUtils'; + +export class Card +{ + id: string; + name: string; + description: string; + idAttachmentCover: string; + idMembers: string[]; + idLabels: string[]; + attachments: any[]; + subscribed: boolean; + checklists: any[]; + checkItems: number; + checkItemsChecked: number; + comments: any[]; + activities: any[]; + due: string; + + constructor(card) + { + this.id = card.id || FuseUtils.generateGUID(); + this.name = card.name || ''; + this.description = card.description || ''; + this.idAttachmentCover = card.idAttachmentCover || ''; + this.idMembers = card.idMembers || []; + this.idLabels = card.idLabels || []; + this.attachments = card.attachments || []; + this.subscribed = card.subscribed || true; + this.checklists = card.checklists || []; + this.checkItems = card.checkItems || 0; + this.checkItemsChecked = card.checkItemsChecked || 0; + this.comments = card.comments || []; + this.activities = card.activities || []; + this.due = card.due || ''; + } +} diff --git a/src/app/main/content/apps/scrumboard/list.model.ts b/src/app/main/content/apps/scrumboard/list.model.ts new file mode 100644 index 00000000..4cf0fac9 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/list.model.ts @@ -0,0 +1,15 @@ +import { FuseUtils } from '../../../../core/fuseUtils'; + +export class List +{ + id: string; + name: string; + idCards: string[]; + + constructor(list) + { + this.id = list.id || FuseUtils.generateGUID(); + this.name = list.name || ''; + this.idCards = []; + } +} diff --git a/src/app/main/content/apps/scrumboard/scrumboard.component.html b/src/app/main/content/apps/scrumboard/scrumboard.component.html new file mode 100644 index 00000000..f617a2d9 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/scrumboard.component.html @@ -0,0 +1,36 @@ + +
+ + +
+ +

Scrumboard App

+ + +
+ + +
+ assessment +
{{board.name}}
+
+ + + +
+ add_circle +
Add new board
+
+ + +
+ + +
+ + +
+ diff --git a/src/app/main/content/apps/scrumboard/scrumboard.component.scss b/src/app/main/content/apps/scrumboard/scrumboard.component.scss new file mode 100644 index 00000000..13eb32d4 --- /dev/null +++ b/src/app/main/content/apps/scrumboard/scrumboard.component.scss @@ -0,0 +1,187 @@ +@import "src/app/core/scss/fuse"; + +:host { + min-height: 100%; + + #board-selector { + margin-top: 88px; + + .board-list { + padding: 32px 0; + + .board-list-item { + min-width: 210px; + width: 210px; + padding: 24px 0; + margin: 16px; + border-radius: 2px; + background: rgba(0, 0, 0, 0.12); + cursor: pointer; + + &:hover { + @include mat-elevation(4); + } + + .board-name { + padding-top: 16px; + font-weight: 500; + } + } + } + } +} + +#scrumboard { + height: 100%; + + > .header { + position: relative; + height: 96px; + min-height: 96px; + max-height: 96px; + background-image: none; + z-index: 49; + + .header-content { + + .header-boards-button { + margin: 0; + } + + .header-board-name { + font-size: 16px; + + .board-subscribe { + margin-right: 8px; + } + + .editable-buttons { + + md-icon { + color: #FFFFFF !important; + } + } + } + + .right-side { + + > .md-button:last-child { + margin-right: 0; + } + } + + } + } + + #board-selector { + position: absolute; + top: 96px; + right: 0; + left: 0; + height: 192px; + z-index: 48; + padding: 24px; + opacity: 1; + + .board-list-item { + width: 128px; + height: 192px; + padding: 16px; + cursor: pointer; + position: relative; + + .board-name { + text-align: center; + padding: 16px 0; + } + + .selected-icon { + position: absolute; + top: 0; + left: 50%; + width: 32px; + height: 32px; + margin-left: -16px; + border-radius: 50%; + text-align: center; + color: white; + + i { + line-height: 32px !important; + } + } + + &.add-new-board { + opacity: 0.6; + } + } + } + + .content { + padding: 0; + background: transparent; + } + + .editable-click { + cursor: pointer; + text-decoration: none; + color: inherit; + border-bottom: none; + } + + .editable-wrap { + display: block; + position: relative; + + .editable-controls { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + + .editable-input { + width: inherit; + background-color: white; + padding: 8px; + border: 1px solid rgba(0, 0, 0, 0.12); + } + + .editable-buttons { + display: inherit; + + .md-button { + margin: 0; + + &:first-of-type { + padding-right: 0; + } + + .icon-cancel { + color: rgba(0, 0, 0, 0.32); + } + } + } + } + } + + .board-selector-backdrop { + z-index: 47; + } +} + +// RESPONSIVE +@include media-breakpoint(xs) { + #scrumboard { + + .header { + height: 120px; + max-height: 120px; + min-height: 120px; + + } + + #board-selector { + top: 120px; + } + } +} diff --git a/src/app/main/content/apps/scrumboard/scrumboard.component.ts b/src/app/main/content/apps/scrumboard/scrumboard.component.ts new file mode 100644 index 00000000..1b17dd5d --- /dev/null +++ b/src/app/main/content/apps/scrumboard/scrumboard.component.ts @@ -0,0 +1,47 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ScrumboardService } from './scrumboard.service'; +import { Subscription } from 'rxjs/Subscription'; +import { Router } from '@angular/router'; +import { Board } from './board.model'; + +@Component({ + selector : 'fuse-scrumboard', + templateUrl: './scrumboard.component.html', + styleUrls : ['./scrumboard.component.scss'] +}) +export class FuseScrumboardComponent implements OnInit, OnDestroy +{ + boards: any[]; + onBoardsChanged: Subscription; + + constructor( + private router: Router, + private scrumboardService: ScrumboardService + ) + { + + } + + ngOnInit() + { + this.onBoardsChanged = + this.scrumboardService.onBoardsChanged + .subscribe(boards => { + this.boards = boards; + }); + + } + + newBoard() + { + const newBoard = new Board({}); + this.scrumboardService.createNewBoard(newBoard).then(() => { + this.router.navigate(['/apps/scrumboard/boards/' + newBoard.id + '/' + newBoard.uri]); + }); + } + + ngOnDestroy() + { + this.onBoardsChanged.unsubscribe(); + } +} diff --git a/src/app/main/content/apps/scrumboard/scrumboard.module.ts b/src/app/main/content/apps/scrumboard/scrumboard.module.ts new file mode 100644 index 00000000..8601f55e --- /dev/null +++ b/src/app/main/content/apps/scrumboard/scrumboard.module.ts @@ -0,0 +1,66 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from '../../../../core/modules/shared.module'; +import { RouterModule, Routes } from '@angular/router'; +import { FuseScrumboardComponent } from './scrumboard.component'; +import { BoardResolve, ScrumboardService } from './scrumboard.service'; +import { FuseScrumboardBoardComponent } from './board/board.component'; +import { FuseScrumboardBoardListComponent } from './board/list/list.component'; +import { FuseScrumboardBoardCardComponent } from './board/list/card/card.component'; +import { FuseScrumboardBoardEditListNameComponent } from './board/list/edit-list-name/edit-list-name.component'; +import { FuseScrumboardBoardAddCardComponent } from './board/list/add-card/add-card.component'; +import { FuseScrumboardBoardAddListComponent } from './board/add-list/add-list.component'; +import { FuseScrumboardCardDialogComponent } from './board/dialogs/card/card.component'; +import { FuseScrumboardLabelSelectorComponent } from './board/dialogs/card/label-selector/label-selector.component'; +import { FuseScrumboardEditBoardNameComponent } from './board/edit-board-name/edit-board-name.component'; +import { FuseScrumboardBoardSettingsSidenavComponent } from './board/sidenavs/settings/settings.component'; +import { FuseScrumboardBoardColorSelectorComponent } from './board/sidenavs/settings/board-color-selector/board-color-selector.component'; + +const routes: Routes = [ + { + path : 'boards', + component: FuseScrumboardComponent, + resolve : { + scrumboard: ScrumboardService + } + }, + { + path : 'boards/:boardId/:boardUri', + component: FuseScrumboardBoardComponent, + resolve : { + board: BoardResolve + } + }, + { + path : '**', + redirectTo: 'boards' + } +]; + +@NgModule({ + declarations : [ + FuseScrumboardComponent, + FuseScrumboardBoardComponent, + FuseScrumboardBoardListComponent, + FuseScrumboardBoardCardComponent, + FuseScrumboardBoardEditListNameComponent, + FuseScrumboardBoardAddCardComponent, + FuseScrumboardBoardAddListComponent, + FuseScrumboardCardDialogComponent, + FuseScrumboardLabelSelectorComponent, + FuseScrumboardEditBoardNameComponent, + FuseScrumboardBoardSettingsSidenavComponent, + FuseScrumboardBoardColorSelectorComponent + ], + imports : [ + SharedModule, + RouterModule.forChild(routes) + ], + providers : [ + ScrumboardService, + BoardResolve + ], + entryComponents: [FuseScrumboardCardDialogComponent] +}) +export class FuseScrumboardModule +{ +} diff --git a/src/app/main/content/apps/scrumboard/scrumboard.service.ts b/src/app/main/content/apps/scrumboard/scrumboard.service.ts new file mode 100644 index 00000000..f26c3e9e --- /dev/null +++ b/src/app/main/content/apps/scrumboard/scrumboard.service.ts @@ -0,0 +1,174 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; +import { Http } from '@angular/http'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +@Injectable() +export class ScrumboardService implements Resolve +{ + boards: any[]; + routeParams: any; + board: any; + + onBoardsChanged: BehaviorSubject = new BehaviorSubject([]); + onBoardChanged: BehaviorSubject = new BehaviorSubject([]); + + constructor(private http: Http) + { + } + + /** + * Resolve + * @param {ActivatedRouteSnapshot} route + * @param {RouterStateSnapshot} state + * @returns {Observable | Promise | any} + */ + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | any + { + this.routeParams = route.params; + + return new Promise((resolve, reject) => { + Promise.all([ + this.getBoards() + ]).then( + () => { + resolve(); + }, + reject + ); + }); + } + + getBoards(): Promise + { + return new Promise((resolve, reject) => { + this.http.get('api/scrumboard-boards') + .subscribe(response => { + this.boards = response.json().data; + this.onBoardsChanged.next(this.boards); + resolve(this.boards); + }, reject); + }); + } + + getBoard(boardId): Promise + { + return new Promise((resolve, reject) => { + this.http.get('api/scrumboard-boards/' + boardId) + .subscribe(response => { + this.board = response.json().data; + this.onBoardChanged.next(this.board); + resolve(this.board); + }, reject); + }); + } + + addCard(listId, newCard) + { + this.board.lists.map((list) => { + if ( list.id === listId ) + { + return list.idCards.push(newCard.id); + } + }); + + this.board.cards.push(newCard); + + return this.updateBoard(); + } + + addList(newList) + { + + this.board.lists.push(newList); + + return this.updateBoard(); + + } + + removeList(listId) + { + const list = this.board.lists.find((_list) => { + return _list.id === listId; + }); + + for ( const cardId of list.idCards ) + { + this.removeCard(cardId); + } + + const index = this.board.lists.indexOf(list); + + this.board.lists.splice(index, 1); + + return this.updateBoard(); + } + + removeCard(cardId, listId?) + { + + const card = this.board.cards.find((_card) => { + return _card.id === cardId; + }); + + if ( listId ) + { + const list = this.board.lists.find((_list) => { + return listId === _list.id; + }); + list.idCards.splice(list.idCards.indexOf(cardId), 1); + } + + this.board.cards.splice(this.board.cards.indexOf(card), 1); + + this.updateBoard(); + } + + updateBoard() + { + return new Promise((resolve, reject) => { + this.http.post('api/scrumboard-boards/' + this.board.id, this.board) + .subscribe(response => { + this.onBoardChanged.next(this.board); + resolve(this.board); + }, reject); + }); + } + + updateCard(newCard) + { + this.board.cards.map((_card) => { + if ( _card.id === newCard.id ) + { + return newCard; + } + }); + + this.updateBoard(); + } + + createNewBoard(board) + { + return new Promise((resolve, reject) => { + this.http.post('api/scrumboard-boards/' + board.id, board) + .subscribe(response => { + resolve(board); + }, reject); + }); + } +} + +@Injectable() +export class BoardResolve implements Resolve +{ + + constructor(private scrumboardService: ScrumboardService) + { + } + + resolve(route: ActivatedRouteSnapshot) + { + return this.scrumboardService.getBoard(route.paramMap.get('boardId')); + } +} diff --git a/src/app/main/content/apps/todo/todo-details/todo-details.component.ts b/src/app/main/content/apps/todo/todo-details/todo-details.component.ts index 9d9666b8..dc736847 100644 --- a/src/app/main/content/apps/todo/todo-details/todo-details.component.ts +++ b/src/app/main/content/apps/todo/todo-details/todo-details.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit, ViewChildren } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { TodoService } from '../todo.service'; import { Todo } from '../todo.model'; import { Subscription } from 'rxjs/Subscription'; @@ -16,7 +16,7 @@ export class FuseTodoDetailsComponent implements OnInit, OnDestroy tags: any[]; formType: string; todoForm: FormGroup; - @ViewChildren('titleInput') titleInputField; + @ViewChild('titleInput') titleInputField; onFormChange: any; onCurrentTodoChanged: Subscription; @@ -77,7 +77,7 @@ export class FuseTodoDetailsComponent implements OnInit, OnDestroy focusTitleField() { setTimeout(() => { - this.titleInputField.first.nativeElement.focus(); + this.titleInputField.nativeElement.focus(); }); } diff --git a/src/app/navigation.model.ts b/src/app/navigation.model.ts index 51ff9521..41f4eb11 100644 --- a/src/app/navigation.model.ts +++ b/src/app/navigation.model.ts @@ -72,6 +72,12 @@ export class FuseNavigation 'fg' : '#FFFFFF' } }, + { + 'title': 'Scrumboard', + 'type' : 'nav-item', + 'icon' : 'assessment', + 'url' : '/apps/scrumboard' + }, { 'title': 'PAGES', 'type' : 'subheader'