Skip to content

Commit 6ab436e

Browse files
committed
feat: add project kaban board demo
1 parent c4584e9 commit 6ab436e

6 files changed

Lines changed: 259 additions & 1 deletion

File tree

src/app/app.routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { Routes } from '@angular/router';
22
import { HomeLoginComponent } from './features/login/components/home-login/home-login.component';
33
import { ProjectListComponent } from './features/projects/components/project-list/project-list.component';
44
import { DashboardComponent } from './features/dashboard/components/dashboard/dashboard.component';
5+
import { ProjectKanbanComponent } from './features/projects/components/project-kanban/project-kanban.component';
56

67
export const routes: Routes = [
78
{ path: '', component: DashboardComponent },
89
{ path: 'projects', component: ProjectListComponent },
10+
{ path: 'projects/kanban', component: ProjectKanbanComponent },
911
{ path: 'login', component: HomeLoginComponent },
1012
];
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<div class="flex gap-5">
2+
<ng-container *ngFor="let status of taskStatus">
3+
<div class="flex-1">
4+
<p-card class="custom-card">
5+
<ng-template pTemplate="header">
6+
<div class="flex justify-content-between align-items-center w-full column-header">
7+
<div>
8+
<span class="text-lg font-semibold">{{status}}</span>
9+
</div>
10+
<div>
11+
<span><p-chip [label]="getTasksByStatus(status).length.toString()"></p-chip></span>
12+
</div>
13+
</div>
14+
</ng-template>
15+
16+
<!-- Droppable Area -->
17+
<div class="grid gap-3 min-h-10rem drop-column" pDroppable (onDrop)="drop($event, status)">
18+
<ng-container *ngFor="let task of getTasksByStatus(status); let i = index">
19+
<div class="col-12" pDraggable
20+
(onDragStart)="dragStart($event, task)" (onDragEnd)="dragEnd()">
21+
<div class="drag-card" [ngClass]="{'opacity-50': draggedTask === task}">
22+
<div class="flex justify-content-between align-items-center">
23+
<span
24+
class="text-surface-900 dark:text-surface-0 font-semibold drag-handle cursor-move">{{
25+
task.name }}</span>
26+
<div>
27+
<p-button icon="pi pi-ellipsis-v" [rounded]="true" [text]="true"
28+
severity="secondary" />
29+
</div>
30+
</div>
31+
<div class="--surface-700 dark:--surface-100" style="word-break: break-word;">
32+
<span>{{ task.description }}</span>
33+
</div>
34+
<div
35+
class="flex align-items-center justify-content-between flex-column md:flex-row gap-6 md:gap-0">
36+
<div class="flex align-items-center gap-1">
37+
<p-avatar [label]="task.assignee.charAt(0)" styleClass="mr-2"
38+
[style]="{ 'background-color': '#dee9fc', color: '#1a2551' }" shape="circle" />
39+
<span>{{ task.assignee }}</span>
40+
</div>
41+
<div class="flex align-items-center gap-4">
42+
<span class="--surface-900 dark:--surface-0 font-semibold flex-shrink-0 flex align-items-center">
43+
<i class="pi pi-clock --surface-700 dark:--surface-100 mr-2"></i>
44+
{{ task.createdOn | date }}
45+
</span>
46+
</div>
47+
</div>
48+
</div>
49+
</div>
50+
</ng-container>
51+
</div>
52+
53+
<ng-template pTemplate="footer">
54+
<div class="flex mt-1">
55+
<p-button icon="pi pi-plus" label="New Task" class="w-full" styleClass="w-full"
56+
(click)="addNewTask(status)" />
57+
</div>
58+
</ng-template>
59+
</p-card>
60+
</div>
61+
</ng-container>
62+
</div>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
::ng-deep .custom-card .p-card-body {
2+
padding: 0 2rem 2rem 2rem;
3+
}
4+
5+
::ng-deep .custom-card .p-card-content {
6+
padding: 0;
7+
}
8+
9+
.card {
10+
background: var(--surface-card);
11+
border: 1px solid var(--surface-border);
12+
padding: 2rem;
13+
margin-bottom: 2rem;
14+
box-shadow: var(--card-shadow);
15+
}
16+
17+
.column-header {
18+
padding: 2rem;
19+
}
20+
21+
.drag-card {
22+
display: flex;
23+
border-radius: var(--border-radius);
24+
border: 1px solid lightgrey;
25+
padding: 1rem;
26+
--tw-bg-opacity: 1;
27+
background-color:
28+
color-mix(in srgb, var(--surface-0) calc(100%* var(--tw-bg-opacity, 1)), transparent);
29+
gap: 2rem;
30+
flex-direction: column;
31+
cursor: pointer;
32+
width: 100%;
33+
}
34+
35+
.text-surface-900 {
36+
--tw-text-opacity: 1;
37+
color: color-mix(in srgb, var(--surface-900) calc(100%* var(--tw-text-opacity, 1)), transparent);
38+
}
39+
40+
:host ::ng-deep {
41+
.drop-column {
42+
border: 1px solid transparent;
43+
transition: border-color .2s;
44+
45+
&.p-draggable-enter {
46+
border-color: var(--primary-color);
47+
}
48+
}
49+
50+
[pDraggable] {
51+
cursor: move;
52+
}
53+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { ProjectKanbanComponent } from './project-kanban.component';
4+
5+
describe('ProjectKanbanComponent', () => {
6+
let component: ProjectKanbanComponent;
7+
let fixture: ComponentFixture<ProjectKanbanComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [ProjectKanbanComponent]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(ProjectKanbanComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { CommonModule } from '@angular/common';
2+
import { Component } from '@angular/core';
3+
import { SharedModule } from '../../../../shared/shared.module';
4+
import { DragDropModule } from 'primeng/dragdrop';
5+
6+
@Component({
7+
selector: 'app-project-kanban',
8+
standalone: true,
9+
imports: [CommonModule, SharedModule, DragDropModule],
10+
templateUrl: './project-kanban.component.html',
11+
styleUrl: './project-kanban.component.scss'
12+
})
13+
export class ProjectKanbanComponent {
14+
taskStatus = ['To Do', 'In Progress', 'Done', 'Blocked'];
15+
public type = '';
16+
public draggedTask: any;
17+
public todos = [
18+
{
19+
id: 1,
20+
name: 'Set up theme',
21+
description: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book`,
22+
assignee: 'John Doe',
23+
createdOn: new Date(),
24+
status: 'Blocked',
25+
},
26+
{
27+
id: 2,
28+
name: 'Develop layout',
29+
description: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book`,
30+
assignee: 'Will Smith',
31+
createdOn: new Date(),
32+
status: 'To Do',
33+
},
34+
{
35+
id: 3,
36+
name: 'Develop Auth Module',
37+
description: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book`,
38+
assignee: 'James Aninston',
39+
createdOn: new Date(),
40+
status: 'To Do',
41+
},
42+
{
43+
id: 4,
44+
name: 'Develop layout',
45+
description: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book`,
46+
assignee: 'Will Smith',
47+
createdOn: new Date(),
48+
status: 'In Progress',
49+
},
50+
{
51+
id: 5,
52+
name: 'Develop layout',
53+
description: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book`,
54+
assignee: 'Will Smith',
55+
createdOn: new Date(),
56+
status: 'In Progress',
57+
},
58+
{
59+
id: 6,
60+
name: 'Develop layout',
61+
description: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book`,
62+
assignee: 'John Doe',
63+
createdOn: new Date(),
64+
status: 'Done',
65+
},
66+
];
67+
68+
constructor() { }
69+
70+
getTasksByStatus(status: string): any[] {
71+
return this.todos.filter(task => task.status === status);
72+
}
73+
74+
dragStart(event: DragEvent, task: any) {
75+
this.draggedTask = task;
76+
}
77+
78+
dragEnd() {
79+
this.draggedTask = null;
80+
}
81+
82+
drop(event: any, newStatus: string) {
83+
if (this.draggedTask) {
84+
const taskIndex = this.todos.findIndex(t => t.id === this.draggedTask!.id);
85+
86+
if (taskIndex !== -1) {
87+
this.todos[taskIndex] = {
88+
...this.todos[taskIndex],
89+
status: newStatus
90+
};
91+
92+
this.saveChanges();
93+
}
94+
95+
this.draggedTask = null;
96+
}
97+
}
98+
99+
saveChanges() {
100+
console.log('Tasks updated:', this.todos);
101+
}
102+
103+
addNewTask(status: string) {
104+
const newTask: any = {
105+
id: this.todos.length + 1,
106+
name: 'New Task',
107+
description: 'Add description here',
108+
status: status,
109+
createdOn: new Date()
110+
};
111+
112+
this.todos.push(newTask);
113+
}
114+
}

src/app/shared/shared.module.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { MultiSelectModule } from 'primeng/multiselect';
1616
import { ProgressBarModule } from 'primeng/progressbar';
1717
import { TagModule } from 'primeng/tag';
1818
import { SliderModule } from 'primeng/slider';
19+
import { ChipModule } from 'primeng/chip';
20+
import { DragDropModule } from 'primeng/dragdrop';
1921

2022
@NgModule({
2123
exports: [
@@ -40,7 +42,9 @@ import { SliderModule } from 'primeng/slider';
4042
SliderModule,
4143
IconFieldModule,
4244
InputIconModule,
43-
MultiSelectModule
45+
MultiSelectModule,
46+
ChipModule,
47+
DragDropModule
4448
],
4549
})
4650
export class SharedModule {}

0 commit comments

Comments
 (0)