Skip to content

Commit a4fa273

Browse files
author
Diogo Ferraz
committed
feat(projects): add Project Details page with backend data, preview mode, and responsive dashboard layout
1 parent b4a57d1 commit a4fa273

5 files changed

Lines changed: 843 additions & 0 deletions

File tree

src/app/app.routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ProjectKanbanComponent } from './features/projects/components/project-k
66
import { TaskItemListComponent } from './features/task-item/components/task-item-list/task-item-list.component';
77
import { UserTaskItemsComponent } from './features/task-item/components/user-task-items/user-task-items.component';
88
import { ProjectCreateComponent } from './features/projects/components/project-create/project-create.component';
9+
import { ProjectDetailsComponent } from './features/projects/components/project-details/project-details.component';
910
import { TaskItemCreateComponent } from './features/task-item/components/task-item-create/task-item-create.component';
1011
import { authGuard } from './core/auth/guards/auth.guard';
1112
import { AuthCallbackComponent } from './features/login/components/auth-callback/auth-callback.component';
@@ -23,6 +24,7 @@ export const routes: Routes = [
2324
{ path: 'projects', component: ProjectListComponent, canActivate: [authGuard] },
2425
{ path: 'projects/kanban', component: ProjectKanbanComponent, canActivate: [authGuard] },
2526
{ path: 'projects/create', component: ProjectCreateComponent, canActivate: [authGuard] },
27+
{ path: 'projects/details', component: ProjectDetailsComponent, canActivate: [authGuard] },
2628
{ path: 'tasks', component: TaskItemListComponent, canActivate: [authGuard] },
2729
{ path: 'search', component: SearchFiltersComponent, canActivate: [authGuard] },
2830
{ path: 'docs', component: ProjectDocsComponent, canActivate: [authGuard] },

src/app/core/layout/component/app-menu/app-menu.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class AppMenuComponent {
2525
label: 'Projects',
2626
items: [
2727
{ label: 'All Projects', icon: 'pi pi-fw pi-list', routerLink: ['/projects'] },
28+
{ label: 'Project Details', icon: 'pi pi-fw pi-folder-open', routerLink: ['/projects/details'] },
2829
{ label: 'Create Project', icon: 'pi pi-fw pi-plus', routerLink: ['/projects/create'] },
2930
{ label: 'Kanban Board', icon: 'pi pi-fw pi-th-large', routerLink: ['/projects/kanban'] }
3031
]
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<div class="project-details-page">
2+
<p-card class="project-details-header-card">
3+
<div class="project-details-header">
4+
<div class="project-details-header__title">
5+
<h2>Project Details</h2>
6+
<p>Project metadata, member visibility, and recent delivery context in one view.</p>
7+
8+
<div class="project-details-header__meta" *ngIf="selectedProjectDetails as project">
9+
<span class="meta-pill">
10+
<i class="pi pi-folder"></i>
11+
{{ project.name }}
12+
</span>
13+
<span class="meta-pill">
14+
<i class="pi pi-user"></i>
15+
By {{ project.createdByUserName || 'Unknown' }}
16+
</span>
17+
<span class="meta-pill">
18+
<i class="pi pi-calendar"></i>
19+
Created {{ project.createdAt | date:'mediumDate' }}
20+
</span>
21+
<span class="meta-pill">
22+
<i class="pi pi-history"></i>
23+
Updated {{ project.lastModifiedAt | date:'mediumDate' }}
24+
</span>
25+
<span class="meta-pill" *ngIf="isPreviewMode">
26+
<i class="pi pi-eye"></i>
27+
Preview mode
28+
</span>
29+
</div>
30+
</div>
31+
32+
<div class="project-details-header__controls">
33+
<p-dropdown
34+
inputId="projectDetailsPicker"
35+
[options]="projects"
36+
optionLabel="name"
37+
optionValue="id"
38+
[(ngModel)]="selectedProjectId"
39+
[filter]="true"
40+
filterBy="name"
41+
[showClear]="false"
42+
[disabled]="isLoadingProjects || projects.length === 0 || isLoadingDetails"
43+
placeholder="Select project"
44+
(onChange)="onProjectChange()">
45+
</p-dropdown>
46+
47+
<button pButton type="button" icon="pi pi-refresh" class="p-button-outlined" (click)="refresh()" [loading]="isLoadingProjects || isLoadingDetails"></button>
48+
<button pButton type="button" label="Open Kanban" icon="pi pi-th-large" class="p-button-outlined" (click)="openKanban()" [disabled]="!selectedProjectId"></button>
49+
<button pButton type="button" label="Open Tasks" icon="pi pi-list" class="p-button-outlined" (click)="openTasks()"></button>
50+
</div>
51+
</div>
52+
</p-card>
53+
54+
<div class="project-kpis">
55+
<p-card class="kpi-card"><span class="kpi-card__label">Team Members</span><span class="kpi-card__value">{{ projectMembers.length }}</span></p-card>
56+
<p-card class="kpi-card"><span class="kpi-card__label">Recent Tasks</span><span class="kpi-card__value">{{ totalTasks }}</span></p-card>
57+
<p-card class="kpi-card"><span class="kpi-card__label">To Do</span><span class="kpi-card__value">{{ todoTasks }}</span></p-card>
58+
<p-card class="kpi-card"><span class="kpi-card__label">In Progress</span><span class="kpi-card__value">{{ inProgressTasks }}</span></p-card>
59+
<p-card class="kpi-card"><span class="kpi-card__label">Done</span><span class="kpi-card__value">{{ doneTasks }}</span></p-card>
60+
<p-card class="kpi-card"><span class="kpi-card__label">Overdue</span><span class="kpi-card__value">{{ overdueTasks }}</span></p-card>
61+
</div>
62+
63+
<div class="project-details-grid">
64+
<div class="project-details-grid__col project-details-grid__col--overview">
65+
<p-card header="Project Overview" styleClass="details-card">
66+
<div class="project-overview" *ngIf="selectedProjectDetails as project; else emptyProjectState">
67+
<p>{{ project.description || 'No project description provided yet.' }}</p>
68+
<div class="project-overview__meta">
69+
<span><strong>Created by:</strong> {{ project.createdByUserName || 'Unknown' }}</span>
70+
<span><strong>Last modified by:</strong> {{ project.lastModifiedByUserName || 'Unknown' }}</span>
71+
<span><strong>Unassigned tasks:</strong> {{ unassignedTasks }}</span>
72+
</div>
73+
</div>
74+
</p-card>
75+
</div>
76+
77+
<div class="project-details-grid__col project-details-grid__col--members">
78+
<p-card header="Project Members" styleClass="details-card">
79+
<div class="members-list" *ngIf="projectMembers.length > 0; else emptyMembersState">
80+
<div class="member-item" *ngFor="let member of projectMembers">
81+
<div class="member-item__identity">
82+
<p-avatar [label]="getInitials(member.displayName)" shape="circle"></p-avatar>
83+
<span>{{ member.displayName }}</span>
84+
</div>
85+
<p-tag [value]="member.isOwner ? 'Owner' : 'Member'" [severity]="member.isOwner ? 'info' : 'contrast'"></p-tag>
86+
</div>
87+
</div>
88+
</p-card>
89+
</div>
90+
91+
<div class="project-details-grid__col project-details-grid__col--tasks">
92+
<p-card header="Recent Tasks" styleClass="details-card">
93+
<div class="tasks-inline-error" *ngIf="errors.length > 0">
94+
<i class="pi pi-exclamation-triangle"></i>
95+
{{ errors[0].detail }}
96+
<span *ngIf="previewDetail">· {{ previewDetail }}</span>
97+
</div>
98+
99+
<p-table [value]="recentTasks" [loading]="isLoadingDetails" [rowHover]="true" [tableStyle]="{ 'min-width': '42rem' }">
100+
<ng-template pTemplate="header">
101+
<tr>
102+
<th style="min-width: 16rem;">Task</th>
103+
<th style="min-width: 8rem;">Status</th>
104+
<th style="min-width: 10rem;">Assignee</th>
105+
<th style="min-width: 8rem;">Due</th>
106+
<th style="min-width: 10rem;">Updated</th>
107+
</tr>
108+
</ng-template>
109+
110+
<ng-template pTemplate="body" let-task>
111+
<tr>
112+
<td>
113+
<div class="task-cell">
114+
<span class="task-cell__title">{{ task.title }}</span>
115+
<span class="task-cell__description">{{ task.description || 'No description.' }}</span>
116+
</div>
117+
</td>
118+
<td>
119+
<p-tag [value]="getStatusName(task.status)" [severity]="getStatusSeverity(task.status)"></p-tag>
120+
</td>
121+
<td>{{ task.assignedUserName || 'Unassigned' }}</td>
122+
<td>
123+
<span [class.task-date--overdue]="isOverdue(task)">
124+
{{ task.dueDate ? (task.dueDate | date:'MMM d, y') : '-' }}
125+
</span>
126+
</td>
127+
<td>{{ task.lastModifiedAt | date:'MMM d, y' }}</td>
128+
</tr>
129+
</ng-template>
130+
131+
<ng-template pTemplate="emptymessage">
132+
<tr>
133+
<td colspan="5" class="empty-cell">
134+
<div class="empty-state">
135+
<i class="pi pi-inbox"></i>
136+
<p>No tasks found for this project.</p>
137+
</div>
138+
</td>
139+
</tr>
140+
</ng-template>
141+
</p-table>
142+
</p-card>
143+
</div>
144+
</div>
145+
</div>
146+
147+
<ng-template #emptyProjectState>
148+
<div class="empty-state">
149+
<i class="pi pi-folder-open"></i>
150+
<p>Select a project to view details.</p>
151+
</div>
152+
</ng-template>
153+
154+
<ng-template #emptyMembersState>
155+
<div class="empty-state">
156+
<i class="pi pi-users"></i>
157+
<p>No members found for this project.</p>
158+
</div>
159+
</ng-template>

0 commit comments

Comments
 (0)