Skip to content

Commit 9a961d6

Browse files
manishdixitlfxclaudeasithade
authored andcommitted
feat(committees): add committee dashboard list view and table component (#293)
* feat(committees): add committee dashboard list view and table component Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address PR #293 reviewer comments - Remove unused DialogService injection (dashboard + table components) - Type BFF service methods with shared interfaces instead of any/any[] - Register missing getPublicCommittees route on router - Fix leaveGroup to use correct leave endpoint - Remove duplicate CommitteeJoinApplication model - Fix camelCase field definitions in dashboard interfaces to snake_case - Update all field references to snake_case after interface rename - Fix getPublicCommittees JSDoc to match actual return shape - Restore join_mode entry in COMMITTEE_SETTINGS_FEATURES - Remove unused LeadershipRole type Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address CodeRabbit review round 2 - Replace nested <a> tags in My Groups cards with <div> + click handler - Show "All Groups" heading when committees exist (not just myCommittees) - Scope myCommittees to current project context - Map public endpoint response to PublicCommittee DTO - Save/restore bearer token in public committees handler - Add logger.warning to all catch-block fallbacks in BFF service - Remove unused RouterLink import from dashboard component Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): fix getMyCommittees to fetch actual user memberships The previous implementation passed { my: true } to the query service which doesn't support that filter, returning all committees instead of only the user's. Now fetches committee_member records by username tag (matching the persona-helper pattern) and enriches with committee details, populating my_role and my_member_uid. Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address CR and Copilot issues in dashboard PR - Restore req.bearerToken in finally block to prevent M2M token leak - Disable unimplemented Invite and Add Member CTAs with tooltips - Fix proxyRequest generic type for getCommitteeBudget Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address Asitha code review on dashboard PR - Fix leaveGroup to use DELETE method matching upstream API contract - Fix mailing_list/chat_channel to use plain strings matching upstream schema - Remove GroupMailingList, GroupChatChannel, ChatPlatform dead types - Remove public committee endpoint, route, and server.ts mount - Remove ~1100 lines of unconsumed server endpoints (invites, applications, votes, resolutions, activity, contributors, deliverables, discussions, events, campaigns, engagement, budget, documents, surveys) - Remove N+1 getCommitteeSettings calls from getCommittees/getMyCommittees - Remove sanitizeUrl from template, fix channels to use plain string bindings - Remove stopPropagation calls from channel links and join button - Remove unused inviteToGroup, onAddMember, sanitizeUrl, inviteClick - Disable Add Member and Invite CTAs with coming-soon tooltips - Remove all console.error calls from frontend service Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address remaining Asitha review findings on dashboard PR - Replace nested ternary with getRoleBadgeClass method (Issue 5) - Remove unused personaLabel, onCategoryChange, onVotingStatusChange, onSearch properties/methods (Issue 10) - Move join/leave logic into table component, remove joinClick output, remove duplicate ConfirmationService from dashboard (Issues 15, 16) - Rename /my route to /my-committees (Issue 18) - Consolidate committeeLabel/committeeLabelPlural to single COMMITTEE_LABEL constant with .singular/.plural in templates (Issue 19) - Pass project_uid to getMyCommittees for server-side filtering (Issue 14) - Add keyboard accessibility to My Groups cards (tabindex, role, keydown) - Add aria-label to icon-only channel links - Add logger.warning in getMyCommittees per-committee catch block - Preserve last successful memberships on /my API failure (use EMPTY) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): remove deriveLeadership and chair/co_chair from Committee interface Upstream GET /committees/{uid} has no chair/co_chair fields. Leadership is only available via committee members with role "Chair"/"Vice Chair". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): remove all chair/co_chair references from update flow Upstream has no chair/co_chair fields on GET or PUT. Remove leadership PATCH block from updateCommittee, remove chair/co_chair from CommitteeUpdateData, and remove unused CommitteeLeadership interface. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address final review findings on dashboard PR - Fix operator precedence: wrap (committee.total_members ?? 0) in parentheses so the number pipe applies to the full expression - Replace getRoleBadgeClass() method call in template with a pure RoleBadgeClassPipe to comply with no-function-calls-in-templates rule - Wire category and voting status dropdown filters to filteredCommittees signal via toSignal() on form control valueChanges — previously the dropdowns rendered but their values never reached the filter logic Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): remove delete button from group list table Committee deletion from the list view is a risky action — it belongs in the detail view settings, not as a quick action on every row. Removed delete button, onDeleteCommittee/performDelete methods, isDeleting signal, ConfirmationService, and ConfirmDialogModule. Delete option will be added in the Settings tab in a follow-up PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address round 3 review findings on dashboard PR - Replace BehaviorSubject with WritableSignal for refresh trigger - Add console.error to silent catchError blocks in CommitteeService - Make error handling consistent (EMPTY → of([])) in both initializers - Add standalone: true to RoleBadgeClassPipe - Restore console.error in dashboard component catchError blocks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): replace mb- spacing with flex gap in dashboard template Replace margin-based vertical rhythm (mb-8, mb-6) between page sections with flex + flex-col + gap-8/gap-6 parent containers per project styling guidelines. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> --------- Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Asitha de Silva <asithade@gmail.com> Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
1 parent 58c0786 commit 9a961d6

10 files changed

Lines changed: 530 additions & 237 deletions

File tree

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

Lines changed: 122 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@
66
<div class="flex justify-center items-center min-h-96">
77
<div class="text-center">
88
<i class="fa-light fa-spinner-third fa-spin text-3xl text-blue-600 mb-4"></i>
9-
<p class="text-gray-600">Loading {{ committeeLabel.toLowerCase() }} details...</p>
9+
<p class="text-gray-600">Loading {{ committeeLabel.singular.toLowerCase() }} details...</p>
1010
</div>
1111
</div>
1212
} @else {
13-
<div class="mb-8">
14-
<div class="mb-8">
13+
<div class="flex flex-col gap-8">
14+
<div class="flex flex-col gap-6">
1515
<!-- Page Title with Create Group Button -->
1616
<div class="flex justify-between items-center w-full gap-4">
17-
<h1>{{ committeeLabelPlural }}</h1>
17+
<h1>{{ committeeLabel.plural }}</h1>
1818
@if (canCreateGroup()) {
1919
<lfx-button
20-
[label]="'Create ' + committeeLabel"
20+
[label]="'Create ' + committeeLabel.singular"
2121
icon="fa-light fa-users mr-1"
2222
severity="info"
2323
size="small"
@@ -27,41 +27,148 @@ <h1>{{ committeeLabelPlural }}</h1>
2727
}
2828
</div>
2929
<p class="mt-2 text-gray-500" data-testid="dashboard-hero-description">
30-
Manage {{ committeeLabelPlural.toLowerCase() }} and governance for your project.
30+
Manage {{ committeeLabel.plural.toLowerCase() }} and governance for your project.
3131
</p>
3232
</div>
3333

3434
<!-- Statistics Bar -->
3535
@if (committees().length > 0) {
36-
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6" data-testid="dashboard-stats-bar">
36+
<div class="grid grid-cols-2 md:grid-cols-4 gap-4" data-testid="dashboard-stats-bar">
3737
<lfx-card>
3838
<div class="text-center py-2">
39-
<p class="text-2xl font-semibold text-gray-900">{{ totalCommittees() }}</p>
40-
<p class="text-xs text-gray-500 mt-1">Total {{ committeeLabelPlural }}</p>
39+
<p class="text-2xl font-semibold text-gray-900">{{ totalCommittees() | number }}</p>
40+
<p class="text-xs text-gray-500 mt-1">Total {{ committeeLabel.plural }}</p>
4141
</div>
4242
</lfx-card>
4343
<lfx-card>
4444
<div class="text-center py-2">
45-
<p class="text-2xl font-semibold text-gray-900">{{ publicCommittees() }}</p>
45+
<p class="text-2xl font-semibold text-gray-900">{{ publicCommittees() | number }}</p>
4646
<p class="text-xs text-gray-500 mt-1">Public</p>
4747
</div>
4848
</lfx-card>
4949
<lfx-card>
5050
<div class="text-center py-2">
51-
<p class="text-2xl font-semibold text-emerald-600">{{ activeVoting() }}</p>
51+
<p class="text-2xl font-semibold text-emerald-600">{{ activeVoting() | number }}</p>
5252
<p class="text-xs text-gray-500 mt-1">Active Voting</p>
5353
</div>
5454
</lfx-card>
5555
<lfx-card>
5656
<div class="text-center py-2">
57-
<p class="text-2xl font-semibold text-gray-900">{{ totalMembers() }}</p>
57+
<p class="text-2xl font-semibold text-gray-900">{{ totalMembers() | number }}</p>
5858
<p class="text-xs text-gray-500 mt-1">Total Members</p>
5959
</div>
6060
</lfx-card>
6161
</div>
6262
}
6363
</div>
6464

65+
<!-- My Groups Section -->
66+
@if (myCommittees().length > 0) {
67+
<div>
68+
<div class="flex items-center gap-2 mb-4">
69+
<h2 class="text-lg font-semibold text-gray-900">My {{ committeeLabel.plural }}</h2>
70+
<span class="text-xs font-medium text-gray-500 bg-gray-100 rounded-full px-2 py-0.5">{{ myCommittees().length }}</span>
71+
</div>
72+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
73+
@for (committee of myCommittees(); track committee.uid) {
74+
<div
75+
class="block cursor-pointer"
76+
tabindex="0"
77+
role="link"
78+
[attr.aria-label]="'Open ' + (committee.display_name || committee.name)"
79+
(click)="onCommitteeClick(committee)"
80+
(keydown.enter)="onCommitteeClick(committee)">
81+
<lfx-card styleClass="hover:shadow-md transition-shadow cursor-pointer h-full">
82+
<div class="flex flex-col h-full">
83+
<!-- Header: Name + Role Badge -->
84+
<div class="flex items-start justify-between gap-2 mb-3">
85+
<div class="min-w-0 flex-1">
86+
<h3 class="text-sm font-semibold text-gray-900 truncate">{{ committee.display_name || committee.name }}</h3>
87+
<div class="flex items-center gap-2 mt-1">
88+
<span class="text-xs text-gray-500">{{ committee.category }}</span>
89+
@if (!committee.public) {
90+
<i class="fa-light fa-lock w-3 h-3 text-gray-400"></i>
91+
}
92+
</div>
93+
</div>
94+
<!-- Role Badge -->
95+
<span
96+
class="inline-flex items-center text-xs font-medium rounded-full px-2.5 py-1 flex-shrink-0"
97+
[ngClass]="committee.my_role | roleBadgeClass">
98+
{{ committee.my_role }}
99+
</span>
100+
</div>
101+
102+
<!-- Meta Row: Members + Voting + Channels -->
103+
<div class="flex items-center gap-4 mt-auto pt-3 border-t border-gray-100">
104+
<div class="flex items-center gap-1.5 text-xs text-gray-500">
105+
<i class="fa-light fa-users w-3.5 h-3.5"></i>
106+
<span>{{ committee.total_members || 0 }} members</span>
107+
</div>
108+
@if (committee.enable_voting) {
109+
<div class="flex items-center gap-1.5 text-xs text-emerald-600">
110+
<i class="fa-light fa-check-to-slot w-3.5 h-3.5"></i>
111+
<span>Voting</span>
112+
</div>
113+
}
114+
<!-- Channels: mailing_list and chat_channel are plain strings from upstream -->
115+
@if (committee.mailing_list) {
116+
<a
117+
[href]="'mailto:' + committee.mailing_list"
118+
class="flex items-center gap-1 text-xs text-gray-500 hover:text-blue-600 no-underline"
119+
[pTooltip]="committee.mailing_list"
120+
tooltipPosition="top"
121+
aria-label="Mailing list">
122+
<i class="fa-light fa-envelope w-3.5 h-3.5" aria-hidden="true"></i>
123+
</a>
124+
} @else {
125+
<span class="flex items-center gap-1 text-gray-300 cursor-default" pTooltip="No mailing list" tooltipPosition="top">
126+
<i class="fa-light fa-envelope w-3.5 h-3.5"></i>
127+
</span>
128+
}
129+
@if (committee.chat_channel) {
130+
<a
131+
[href]="committee.chat_channel"
132+
target="_blank"
133+
rel="noopener"
134+
class="flex items-center gap-1 text-xs text-gray-500 hover:text-blue-600 no-underline"
135+
pTooltip="Chat channel"
136+
tooltipPosition="top"
137+
aria-label="Chat channel">
138+
<i class="fa-light fa-comment w-3.5 h-3.5" aria-hidden="true"></i>
139+
</a>
140+
} @else {
141+
<span class="flex items-center gap-1 text-gray-300 cursor-default" pTooltip="No chat channel" tooltipPosition="top">
142+
<i class="fa-light fa-comment w-3.5 h-3.5"></i>
143+
</span>
144+
}
145+
</div>
146+
</div>
147+
</lfx-card>
148+
</div>
149+
}
150+
</div>
151+
</div>
152+
} @else if (myCommitteesLoading()) {
153+
<div>
154+
<div class="flex items-center gap-2 mb-4">
155+
<h2 class="text-lg font-semibold text-gray-900">My {{ committeeLabel.plural }}</h2>
156+
</div>
157+
<div class="flex items-center gap-2 text-gray-400 text-sm py-4">
158+
<i class="fa-light fa-spinner-third fa-spin"></i>
159+
<span>Loading your groups...</span>
160+
</div>
161+
</div>
162+
}
163+
164+
<!-- All Groups Section -->
165+
@if (committees().length > 0) {
166+
<div class="flex items-center gap-2">
167+
<h2 class="text-lg font-semibold text-gray-900">All {{ committeeLabel.plural }}</h2>
168+
<span class="text-xs font-medium text-gray-500 bg-gray-100 rounded-full px-2 py-0.5">{{ totalCommittees() }}</span>
169+
</div>
170+
}
171+
65172
<!-- Content Area - Table or Cards -->
66173
<div class="min-h-[400px]">
67174
@if (committees().length === 0 && project()?.uid) {
@@ -71,7 +178,7 @@ <h1>{{ committeeLabelPlural }}</h1>
71178
<div class="text-center max-w-md">
72179
<div class="text-gray-400 mb-4">
73180
<i class="fa-light fa-eyes text-[2rem] mb-4"></i>
74-
<h3 class="text-gray-600 mt-2">Your project has no {{ committeeLabelPlural.toLowerCase() }}, yet.</h3>
181+
<h3 class="text-gray-600 mt-2">Your project has no {{ committeeLabel.plural.toLowerCase() }}, yet.</h3>
75182
</div>
76183
</div>
77184
</div>
@@ -84,7 +191,7 @@ <h3 class="text-gray-600 mt-2">Your project has no {{ committeeLabelPlural.toLow
84191
<div class="text-gray-400 mb-4">
85192
<i class="fa-light fa-eyes text-[2rem] mb-4"></i>
86193
</div>
87-
<h3 class="text-xl font-semibold text-gray-900 mt-4">No {{ committeeLabelPlural.toLowerCase() }} Found</h3>
194+
<h3 class="text-xl font-semibold text-gray-900 mt-4">No {{ committeeLabel.plural.toLowerCase() }} Found</h3>
88195
<p class="text-gray-600 mt-2">Try adjusting your search or filter criteria</p>
89196
</div>
90197
</div>
@@ -98,9 +205,7 @@ <h3 class="text-xl font-semibold text-gray-900 mt-4">No {{ committeeLabelPlural.
98205
[categoryOptions]="categories()"
99206
[votingStatusOptions]="votingStatusOptions()"
100207
(refresh)="refreshCommittees()"
101-
(rowClick)="onCommitteeClick($event)"
102-
(joinClick)="joinGroup($event)"
103-
(inviteClick)="inviteToGroup($event)">
208+
(rowClick)="onCommitteeClick($event)">
104209
</lfx-committee-table>
105210
}
106211
</div>

0 commit comments

Comments
 (0)