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

Boards board #41

Merged
merged 13 commits into from
Mar 4, 2024
80 changes: 49 additions & 31 deletions corn-frontend/src/app/pages/boards/board/board.component.html
Original file line number Diff line number Diff line change
@@ -1,40 +1,58 @@
<h1>Board</h1>
<div class="flex flex-col gap-[16px]">
<h1 class="text-3xl">{{ modelService.sprintName }}</h1>

<div class="w-400 max-w-full mr-0 mb-25 inline-block align-top ml-[24px] min-w-[300px]">
<h2 class="text-xl font-semibold mb-4">To do</h2>
<div class="flex items-center gap-4">
<mat-form-field subscriptSizing="dynamic">
<mat-label>Search</mat-label>
<input #filterField matInput type="text" (input)="updateFilterString(filterField.value)" />
</mat-form-field>

<div cdkDropList #todoList="cdkDropList" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList, inprogressList]"
class="border border-solid border-gray-900 min-h-[200px] container-background rounded-md overflow-hidden block"
(cdkDropListDropped)="drop2($event)">
@for (item of todo; track item) {
<app-taskcard cdkDrag [content]="item.content" [taskid]="item.taskid" [assigneeAvatarUrl]="item.assigneeAvatar" [assigneeName]="item.assigneeName">
</app-taskcard>
}
<div class="flex flex-col gap-[1px]">
<span class="text-xs">Group by</span>
<button mat-raised-button [matMenuTriggerFor]="groupingMenu" color="primary">
<mat-icon>expand_more</mat-icon>
{{ taskGrouping }}
</button>
</div>
</div>
</div>

<div class="w-400 max-w-full mb-25 inline-block align-top ml-[24px] min-w-[300px]">
<h2 class="text-xl font-semibold mb-4">Done</h2>
<div class="flex flex-col gap-[8px]">
<columnset>
<h2 todo class="font-bold m-auto p-1">TODO ({{ modelService.todoSize() }}/{{ modelService.allTasksSize() }})</h2>
<h2 inprogress class="font-bold m-auto p-1">INPROGRESS ({{ modelService.inprogressSize() }}/{{ modelService.allTasksSize() }})</h2>
<h2 done class="font-bold m-auto p-1">DONE ({{ modelService.doneSize() }}/{{ modelService.allTasksSize() }})</h2>
</columnset>

<div cdkDropList #inprogressList="cdkDropList" [cdkDropListData]="inprogress" [cdkDropListConnectedTo]="[doneList, todoList]"
class="border border-solid border-gray-900 min-h-[200px] container-background rounded-md overflow-hidden block"
(cdkDropListDropped)="drop2($event)">
@for (item of inprogress; track item) {
<app-taskcard cdkDrag [content]="item.content" [taskid]="item.taskid" [assigneeAvatarUrl]="item.assigneeAvatar" [assigneeName]="item.assigneeName">
</app-taskcard>
}
</div>
</div>

<div class="w-400 max-w-full mb-25 inline-block align-top ml-[24px] min-w-[300px]">
<h2 class="text-xl font-semibold mb-4">In Progress</h2>
@for (slice of slicesModelService.slices; track slice) {
<div>
@if (taskGrouping == TaskGroupingEnum.NONE) {
<slice [sliceDescriptor]="slice"></slice>
} @else if(taskGrouping == TaskGroupingEnum.BY_ASSIGNEE) {
<button mat-button (click)="toggleHidden(sliceWrapper)">
<mat-icon>
{{ isHidden(sliceWrapper) ? "chevron_right" : "expand_more" }}
</mat-icon>
<div class="flex items-center space-x-1">
<img class="w-5 h-5 rounded-full" src="{{ slice.metadata.avatarUrl }}" />
Copy link
Owner

Choose a reason for hiding this comment

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

brak alt attribute

<span>
{{ slice.metadata.firstName + " " + slice.metadata.familyName }}
</span>
</div>
</button>

<div cdkDropList #doneList="cdkDropList" [cdkDropListData]="done" [cdkDropListConnectedTo]="[todoList, inprogressList]"
class="border border-solid border-gray-900 min-h-[200px] container-background rounded-md overflow-hidden block"
(cdkDropListDropped)="drop2($event)">
@for (item of done; track item) {
<app-taskcard cdkDrag [content]="item.content" [taskid]="item.taskid" [assigneeAvatarUrl]="item.assigneeAvatar" [assigneeName]="item.assigneeName">
</app-taskcard>
<div #sliceWrapper>
<slice [sliceDescriptor]="slice"></slice>
</div>
}
</div>
}
</div>
</div>

<mat-menu #groupingMenu="matMenu">
@for (taskGrouping of TASK_GROUPINGS; track taskGrouping) {
Copy link
Owner

Choose a reason for hiding this comment

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

błąd z typowaniem

<button mat-menu-item (click)="updateTaskGrouping(taskGrouping)">
{{ taskGrouping }}
</button>
}
</mat-menu>
174 changes: 105 additions & 69 deletions corn-frontend/src/app/pages/boards/board/board.component.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,120 @@
import { Component } from '@angular/core';
import {
CdkDragDrop,
moveItemInArray,
transferArrayItem,
CdkDrag,
CdkDropList,
} from '@angular/cdk/drag-drop';
import { TaskcardComponent } from './taskcard/taskcard.component';
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { SlicesModelService, TaskChangedColumnEvent, TaskChangedGroupEvent } from './slice/slices_model.service';
import { SliceService } from './slice/slice.service';
import { SliceComponent } from './slice/slice.component';
import { Assignee, SAMPLE_ASSIGNEES, SAMPLE_TASKS, Task } from './model/model';
import { ModelService } from './model/model.service';
import { ColumnSetLayout } from './layout.component';
import { MatIconModule } from '@angular/material/icon';

@Component({
selector: 'app-board',
standalone: true,
imports: [CdkDropList, CdkDrag, TaskcardComponent],
imports: [
CommonModule,
MatInputModule,
MatMenuModule,
MatButtonModule,
SliceComponent,
MatIconModule,
ColumnSetLayout,
],
providers: [
ModelService,
SlicesModelService,
SliceService,
],
templateUrl: './board.component.html',
})
export class BoardComponent {
export class BoardComponent implements OnInit {
Copy link
Owner

Choose a reason for hiding this comment

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

Poprawić return type w całej klasie


todo: Task[] = [
{
content: "Buy groceries",
taskid: "t1",
assigneeAvatar: "/assets/corn-icons/icon-72x72.png",
assigneeName: "John Doe"
},
{
content: "Read a book",
taskid: "t2",
assigneeAvatar: "/assets/corn-icons/icon-72x72.png",
assigneeName: "Jane Doe"
},
{
content: "Write code",
taskid: "t3",
assigneeAvatar: "/assets/corn-icons/icon-72x72.png",
assigneeName: "Alice Smith"
}
];

inprogress: Task[] = [
{
content: "Design a website",
taskid: "t4",
assigneeAvatar: "/assets/corn-icons/icon-72x72.png",
assigneeName: "Bob Johnson"
protected filterString = "";
Copy link
Owner

Choose a reason for hiding this comment

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

brak typowania

protected taskGrouping = TaskGrouping.BY_ASSIGNEE;
Copy link
Owner

Choose a reason for hiding this comment

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

brak typowania


constructor(
protected readonly modelService: ModelService,
Copy link
Owner

Choose a reason for hiding this comment

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

jeżeli service jest używany tylko w tej klasie to powinien być private

protected readonly slicesModelService: SlicesModelService,
) { }

ngOnInit() {
this.modelService.modelChangeHandler = () => {
this.slicesModelService.rebuildSlices();
}
];

done: Task[] = [
{
content: "Finish project",
taskid: "t5",
assigneeAvatar: "/assets/corn-icons/icon-72x72.png",
assigneeName: "Charlie Brown"
},
{
content: "Submit report",
taskid: "t6",
assigneeAvatar: "/assets/corn-icons/icon-72x72.png",
assigneeName: "Diana Miller"
this.modelService.assignees = Object.values(SAMPLE_ASSIGNEES);
this.modelService.todo = SAMPLE_TASKS.TODO;
this.modelService.inprogress = SAMPLE_TASKS.INPROGRESS;
this.modelService.done = SAMPLE_TASKS.DONE;
this.slicesModelService.groupChangedHandler = (event: TaskChangedGroupEvent) => {
if (this.taskGrouping == TaskGrouping.BY_ASSIGNEE &&
event.sourceGroupMetadata != event.destinationGroupMetadata
) {
this.modelService.setAssigneeForTask(event.task, event.destinationGroupMetadata);
}
}
];

drop2(event: CdkDragDrop<Task[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
transferArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex,
this.slicesModelService.columnChangedHandler = (event: TaskChangedColumnEvent) => {
this.modelService.moveTaskToArray(event.task,
event.sourceColumn, event.sourceColumnIndex,
event.destinationColumn, event.destinationColumnIndex
);
}
this.updateFilterString(this.filterString);
this.updateTaskGrouping(this.taskGrouping);
}

protected updateFilterString(filterString: string) {
Copy link
Owner

Choose a reason for hiding this comment

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

brak typowania

filterString = filterString.toLowerCase();
this.filterString = filterString;
const stringPredicate = (s: string) => s.toLowerCase().includes(filterString);
this.slicesModelService.filter = (t: Task) => {
return stringPredicate(t.content)
|| stringPredicate(t.assignee.firstName)
|| stringPredicate(t.assignee.firstName + " " + t.assignee.familyName)
|| stringPredicate(t.assignee.familyName)
|| stringPredicate(t.taskid);
};
this.slicesModelService.rebuildSlices();
}

protected updateTaskGrouping(taskGrouping: TaskGrouping) {
this.taskGrouping = taskGrouping;
this.slicesModelService.grouper = this.GROUPERS[taskGrouping];
this.slicesModelService.rebuildSlices();
}

protected toggleHidden(element: any) {
Copy link
Owner

Choose a reason for hiding this comment

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

czemu typ any

element.hidden = !element.hidden;
}

protected isHidden(element: any) {
return element.hidden;
}

protected readonly TaskGroupingEnum = TaskGrouping;
protected readonly TASK_GROUPINGS = Object.values(TaskGrouping);
protected readonly GROUPERS = {
[TaskGrouping.NONE]: (ungrouped: Task[]) => {
return [{ metadata: null, group: ungrouped }]
},
[TaskGrouping.BY_ASSIGNEE]: (ungrouped: Task[]) => {
return this.modelService.assignees.map((a: Assignee) => {
return {
metadata: a,
group: ungrouped.filter((t: Task) => {
return t.assignee.firstName == a.firstName
&& t.assignee.familyName == a.familyName
})
};
});
},
};

}

interface Task {
content: string;
taskid: string;
assigneeAvatar: string;
assigneeName: string;
}
enum TaskGrouping {
Copy link
Owner

Choose a reason for hiding this comment

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

czemu ten enum jest wewnątrz klasy. Powinien być w @enums

NONE = "None",
BY_ASSIGNEE = "By assignee",
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div [matMenuTriggerFor]="instance" (click)="update()">
<ng-content></ng-content>
</div>

<mat-menu #instance>
<mat-form-field (click)="$event.stopPropagation()" subscriptSizing="dynamic">
<mat-label>Search members</mat-label>
<input matInput #filterStringInput type="text" (click)="$event.stopPropagation()" (input)="update()" />
</mat-form-field>

<mat-divider></mat-divider>

@for(item of filteredAssignees; track item) {
<button mat-menu-item (click)="assigneeChanged(item)">
<div class="flex items-center space-x-3">
<ng-container *ngTemplateOutlet="zerowidthspace"></ng-container>
<img class="w-6 h-6 rounded-full" src="{{ item.avatarUrl }}" title="{{ item.firstName + ' ' + item.familyName }}" />
Copy link
Owner

Choose a reason for hiding this comment

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

brak alt attribute

<span>
{{ item.firstName + " " + item.familyName }}
</span>
</div>
</button>
}
</mat-menu>

<ng-template #zerowidthspace>​</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Input } from '@angular/core';
import { Assignee, Task } from '../model/model';
import { MatMenuModule } from '@angular/material/menu';
import { Component, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { ModelService } from '../model/model.service';

@Component({
selector: 'change-assignee-menu',
standalone: true,
imports: [
CommonModule,
MatMenuModule,
MatInputModule,
MatButtonModule,
MatDividerModule,
],
templateUrl: './change_assignee_menu.component.html',
})
export class ChangeAssigneeMenuComponent {
Copy link
Owner

Choose a reason for hiding this comment

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

dodać typowanie metod


protected myControl = new FormControl('');
Copy link
Owner

Choose a reason for hiding this comment

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

unused variable

protected filteredAssignees: Assignee[] = [];

@Input() associatedTask?: Task;

@ViewChild('filterStringInput') private input?: any;
Copy link
Owner

Choose a reason for hiding this comment

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

czemu any


constructor(
protected readonly modelService: ModelService,
Copy link
Owner

Choose a reason for hiding this comment

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

private ?

) { }

protected update() {
if (!this.input)
return;
const filterString = this.input.nativeElement.value.toLowerCase();
this.filteredAssignees = this.modelService.assignees.filter(a =>
a.firstName.toLowerCase().includes(filterString) ||
a.familyName.toLowerCase().includes(filterString) ||
(a.firstName + " " + a.familyName).toLowerCase().includes(filterString)
)
}

protected assigneeChanged(assignee: Assignee) {
if (!this.associatedTask)
return;
this.modelService.setAssigneeForTask(this.associatedTask, assignee);
}

}
Loading