Skip to content

Commit 738e43d

Browse files
authored
feat(ui): remove mailing lists and improve project card layout (#90)
- Remove mailing lists from project dashboard, navigation, and home page - Improve responsive grid layout with better breakpoints (sm:grid-cols-2, lg:grid-cols-4) - Enhance project card component with flex layout for consistent heights - Add server-side metrics fetching for meetings and committees count - Temporarily comment out settings menu item until implementation LFXV2-515 Signed-off-by: Asitha de Silva <asithade@gmail.com>
1 parent ce7de19 commit 738e43d

File tree

9 files changed

+74
-96
lines changed

9 files changed

+74
-96
lines changed

apps/lfx-one/e2e/homepage.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ test.describe('Homepage', () => {
113113
// Check for metrics in project cards using data-testids
114114
await expect(firstCard.getByTestId('metric-label').filter({ hasText: 'Meetings' })).toBeVisible();
115115
await expect(firstCard.getByTestId('metric-label').filter({ hasText: 'Committees' })).toBeVisible();
116-
await expect(firstCard.getByTestId('metric-label').filter({ hasText: 'Mailing Lists' })).toBeVisible();
117116
});
118117

119118
test('should filter projects when searching', async ({ page }) => {

apps/lfx-one/e2e/project-dashboard.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ test.describe('Project Dashboard', () => {
8181
await expect(page.getByTestId('menu-item').filter({ hasText: 'Dashboard' })).toBeVisible();
8282
await expect(page.getByTestId('menu-item').filter({ hasText: 'Meetings' })).toBeVisible();
8383
await expect(page.getByTestId('menu-item').filter({ hasText: 'Committees' })).toBeVisible();
84-
await expect(page.getByTestId('menu-item').filter({ hasText: 'Mailing Lists' })).toBeVisible();
85-
await expect(page.getByTestId('menu-item').filter({ hasText: 'Settings' })).toBeVisible();
84+
// await expect(page.getByTestId('menu-item').filter({ hasText: 'Mailing Lists' })).toBeVisible();
85+
// await expect(page.getByTestId('menu-item').filter({ hasText: 'Settings' })).toBeVisible();
8686
});
8787
});
8888

@@ -423,10 +423,10 @@ test.describe('Without Write Permissions', () => {
423423
await expect(page.getByTestId('menu-item').filter({ hasText: 'Dashboard' })).toBeVisible();
424424
await expect(page.getByTestId('menu-item').filter({ hasText: 'Meetings' })).toBeVisible();
425425
await expect(page.getByTestId('menu-item').filter({ hasText: 'Committees' })).toBeVisible();
426-
await expect(page.getByTestId('menu-item').filter({ hasText: 'Mailing Lists' })).toBeVisible();
426+
// await expect(page.getByTestId('menu-item').filter({ hasText: 'Mailing Lists' })).toBeVisible();
427427

428428
// Settings tab should also be accessible (though it may have limited functionality)
429-
await expect(page.getByTestId('menu-item').filter({ hasText: 'Settings' })).toBeVisible();
429+
// await expect(page.getByTestId('menu-item').filter({ hasText: 'Settings' })).toBeVisible();
430430
});
431431
});
432432

apps/lfx-one/src/app/layouts/project-layout/project-layout.component.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ <h1 class="text-2xl font-display font-semibold text-gray-900 mb-3">
6868
</a>
6969
</div>
7070
</div>
71+
<!-- TODO: Add settings menu item back in
7172
<div class="items-center gap-3 hidden md:flex">
7273
<a
7374
routerLink="/project/{{ projectSlug() }}/settings"
@@ -79,6 +80,7 @@ <h1 class="text-2xl font-display font-semibold text-gray-900 mb-3">
7980
<span>Settings</span>
8081
</a>
8182
</div>
83+
-->
8284
</div>
8385
</div>
8486
</div>

apps/lfx-one/src/app/layouts/project-layout/project-layout.component.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,6 @@ export class ProjectLayoutComponent {
7171
routerLink: `/project/${this.projectSlug()}/committees`,
7272
routerLinkActiveOptions: { exact: false },
7373
},
74-
{
75-
label: 'Mailing Lists',
76-
icon: 'fa-light fa-envelope text-amber-500',
77-
routerLink: `/project/${this.projectSlug()}/mailing-lists`,
78-
routerLinkActiveOptions: { exact: false },
79-
},
8074
]);
8175

8276
public readonly metrics = computed(() => [

apps/lfx-one/src/app/modules/pages/home/home.component.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ <h1 class="text-3xl font-display font-light text-gray-900 mb-3 xl:px-96 lg:px-64
4040

4141
<div class="container mx-auto py-24 px-8" data-testid="projects-section">
4242
@if (projects().length > 0) {
43-
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" data-testid="projects-grid">
43+
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6" data-testid="projects-grid">
4444
@for (project of filteredProjects(); track project.uid; let i = $index) {
4545
<div
4646
pAnimateOnScroll
@@ -55,7 +55,8 @@ <h1 class="text-3xl font-display font-light text-gray-900 mb-3 xl:px-96 lg:px-64
5555
[description]="project.description ?? ''"
5656
[logo]="project.logo_url ?? ''"
5757
[url]="project.slug"
58-
[metrics]="project.metrics"></lfx-project-card>
58+
[metrics]="project.metrics"
59+
class="h-full"></lfx-project-card>
5960
</div>
6061
}
6162
</div>

apps/lfx-one/src/app/modules/pages/home/home.component.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,6 @@ export class HomeComponent {
5353
value: project.committees_count,
5454
icon: 'fa-light fa-people-group text-green-500',
5555
},
56-
{
57-
label: 'Mailing Lists',
58-
value: project.mailing_list_count,
59-
icon: 'fa-light fa-envelope text-amber-500',
60-
},
6156
];
6257

6358
return {

apps/lfx-one/src/app/modules/project/dashboard/project-dashboard/project.component.html

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@
156156
</lfx-card>
157157

158158
<!-- Original 3 cards row -->
159-
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-stretch">
159+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-6 items-stretch">
160160
<div class="items-stretch">
161161
@if (meetingsLoading()) {
162162
<div>
@@ -267,39 +267,6 @@ <h3 class="text-lg font-medium text-gray-900 mb-2">No Committees</h3>
267267
</lfx-card>
268268
}
269269
</div>
270-
<div class="items-stretch">
271-
<lfx-card header="Mailing Lists" styleClass="h-full" class="h-full">
272-
<div class="flex flex-col gap-2">
273-
<h3 class="text-sm font-display text-gray-500">Recently Joined Mailing Lists</h3>
274-
<lfx-table [value]="mailingListTableData()" [scrollable]="false">
275-
<ng-template #header let-columns>
276-
<tr>
277-
<th>
278-
<span class="text-sm font-sans text-gray-500">Mailing List</span>
279-
</th>
280-
<th>
281-
<div class="flex justify-end">
282-
<span class="text-sm font-sans text-gray-500">Last Updated</span>
283-
</div>
284-
</th>
285-
</tr>
286-
</ng-template>
287-
<ng-template #body let-row>
288-
<tr>
289-
<td class="w-1/2 max-w-0">
290-
<a class="block truncate text-sm" [title]="row.title" [routerLink]="[row.url]" [relativeTo]="activatedRoute">{{ row.title }}</a>
291-
</td>
292-
<td class="w-1/2">
293-
<div class="flex justify-end whitespace-nowrap">
294-
<span class="text-sm font-sans"> {{ row.date | date: 'MMM d, yyyy @ h:mm a' }}</span>
295-
</div>
296-
</td>
297-
</tr>
298-
</ng-template>
299-
</lfx-table>
300-
</div>
301-
</lfx-card>
302-
</div>
303270
</div>
304271
</div>
305272

apps/lfx-one/src/app/shared/components/project-card/project-card.component.html

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,60 +5,58 @@
55
class="bg-white rounded-lg border border-gray-200 shadow-sm hover:shadow-md transition-shadow cursor-pointer h-full"
66
[routerLink]="['/project', url()]"
77
data-testid="project-card-container">
8-
<div class="p-6">
8+
<div class="p-6 h-full flex flex-col gap-6">
99
<!-- Logo and Title Section -->
10-
<div class="flex flex-col gap-6 mb-6" data-testid="project-header">
10+
<div class="flex flex-col gap-6" data-testid="project-header">
1111
<div class="flex flex-col items-start gap-0">
1212
<!-- Logo Container -->
1313
<div class="flex items-start" data-testid="project-logo-container">
14-
<img [src]="logo()" [alt]="name() + ' logo'" class="w-full h-16 object-contain" data-testid="project-logo" />
15-
</div>
16-
17-
<!-- Title and Description -->
18-
<div class="flex flex-col gap-2 mt-4" data-testid="project-info">
19-
<h3 class="text-base font-display font-semibold text-gray-900" data-testid="project-title">
20-
{{ name() }}
21-
</h3>
22-
<p class="text-sm text-gray-600 leading-relaxed line-clamp-2" data-testid="project-description">
23-
{{ description() }}
24-
</p>
14+
<img [src]="logo()" [alt]="name() + ' logo'" class="w-full h-12 object-contain" data-testid="project-logo" />
2515
</div>
2616
</div>
2717
</div>
18+
<!-- Title and Description -->
19+
<div class="flex flex-col justify-between gap-2 h-full" data-testid="project-info">
20+
<h3 class="text-base font-display font-semibold text-gray-900" data-testid="project-title">
21+
{{ name() }}
22+
</h3>
23+
<div class="flex flex-col gap-2">
24+
<p class="text-sm text-gray-600 leading-relaxed line-clamp-2" data-testid="project-description">
25+
{{ description() }}
26+
</p>
2827

29-
<!-- Metrics Section -->
30-
@if (metrics().length > 0) {
31-
<div class="flex flex-col gap-3" data-testid="project-metrics">
32-
@for (metric of metrics(); track metric.label) {
33-
<div class="flex items-center justify-between py-0.5" data-testid="project-metric" [attr.data-metric-label]="metric.label">
34-
<div class="flex items-center gap-2" data-testid="metric-label-container">
35-
<i [class]="metric.icon" class="text-base w-5" data-testid="metric-icon"></i>
36-
<span class="text-sm text-gray-600" data-testid="metric-label">{{ metric.label }}</span>
37-
</div>
38-
<div class="flex items-center gap-2" data-testid="metric-value-container">
39-
@if (metric.badge) {
40-
<span
41-
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-sm font-semibold"
42-
[class]="
43-
metric.badge.severity === 'success'
44-
? 'bg-emerald-500 text-white'
45-
: metric.badge.severity === 'info'
46-
? 'bg-blue-500 text-white'
47-
: metric.badge.severity === 'warning'
48-
? 'bg-amber-500 text-white'
49-
: 'bg-red-500 text-white'
50-
"
51-
data-testid="metric-badge"
52-
[attr.data-badge-severity]="metric.badge.severity">
53-
{{ metric.badge.label }}
54-
</span>
55-
} @else {
56-
<span class="text-sm text-gray-900 font-normal font-display" data-testid="metric-value">{{ metric.value || 0 }}</span>
57-
}
58-
</div>
28+
<!-- Metrics Section -->
29+
@if (metrics().length > 0) {
30+
<div class="flex flex-col gap-3" data-testid="project-metrics">
31+
@for (metric of metrics(); track metric.label) {
32+
<div class="flex items-center justify-between py-0.5" data-testid="project-metric" [attr.data-metric-label]="metric.label">
33+
<div class="flex items-center gap-2" data-testid="metric-label-container">
34+
<i [class]="metric.icon" class="text-base w-5" data-testid="metric-icon"></i>
35+
<span class="text-sm text-gray-600" data-testid="metric-label">{{ metric.label }}</span>
36+
</div>
37+
<div class="flex items-center gap-2" data-testid="metric-value-container">
38+
@if (metric.badge) {
39+
<span
40+
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-sm font-semibold"
41+
[ngClass]="{
42+
'bg-emerald-500 text-white': metric.badge.severity === 'success',
43+
'bg-blue-500 text-white': metric.badge.severity === 'info',
44+
'bg-amber-500 text-white': metric.badge.severity === 'warning',
45+
'bg-red-500 text-white': metric.badge.severity === 'danger',
46+
}"
47+
data-testid="metric-badge"
48+
[attr.data-badge-severity]="metric.badge.severity">
49+
{{ metric.badge.label }}
50+
</span>
51+
} @else {
52+
<span class="text-sm text-gray-900 font-normal font-display" data-testid="metric-value">{{ metric.value || 0 }}</span>
53+
}
54+
</div>
55+
</div>
56+
}
5957
</div>
6058
}
6159
</div>
62-
}
60+
</div>
6361
</div>
6462
</div>

apps/lfx-one/src/server/controllers/project.controller.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ import { NextFunction, Request, Response } from 'express';
55

66
import { ServiceValidationError } from '../errors';
77
import { Logger } from '../helpers/logger';
8+
import { CommitteeService } from '../services/committee.service';
9+
import { MeetingService } from '../services/meeting.service';
810
import { ProjectService } from '../services/project.service';
911

1012
/**
1113
* Controller for handling project HTTP requests
1214
*/
1315
export class ProjectController {
1416
private projectService: ProjectService = new ProjectService();
17+
private meetingService: MeetingService = new MeetingService();
18+
private committeeService: CommitteeService = new CommitteeService();
1519

1620
/**
1721
* GET /projects
@@ -25,6 +29,15 @@ export class ProjectController {
2529
// Get the projects
2630
const projects = await this.projectService.getProjects(req, req.query as Record<string, any>);
2731

32+
// Add metrics to all projects
33+
// TODO: Remove this once we have a way to get the metrics from the microservice
34+
await Promise.all(
35+
projects.map(async (project) => {
36+
project.meetings_count = (await this.meetingService.getMeetings(req, { tags: `project_uid:${project.uid}` }).catch(() => [])).length;
37+
project.committees_count = (await this.committeeService.getCommittees(req, { tags: `project_uid:${project.uid}` }).catch(() => [])).length;
38+
})
39+
);
40+
2841
Logger.success(req, 'get_projects', startTime, {
2942
project_count: projects.length,
3043
});
@@ -68,6 +81,15 @@ export class ProjectController {
6881
// Search for the projects
6982
const results = await this.projectService.searchProjects(req, q);
7083

84+
// Add metrics to all projects
85+
// TODO: Remove this once we have a way to get the metrics from the microservice
86+
await Promise.all(
87+
results.map(async (project) => {
88+
project.meetings_count = (await this.meetingService.getMeetings(req, { tags: `project_uid:${project.uid}` }).catch(() => [])).length;
89+
project.committees_count = (await this.committeeService.getCommittees(req, { tags: `project_uid:${project.uid}` }).catch(() => [])).length;
90+
})
91+
);
92+
7193
// Log the success
7294
Logger.success(req, 'search_projects', startTime, {
7395
result_count: results.length,

0 commit comments

Comments
 (0)