Skip to content

Commit 693baf6

Browse files
feat(committees): add committee detail shell with core overview tab (#294)
* feat(committees): add committee detail shell with core overview tab Rewrite committee-view component with PrimeNG tab shell and overview tab using only getCommitteeById data. No sub-resource API calls. - Add 6-tab layout: Overview, Members, Votes, Meetings, Surveys, Documents - Overview tab: stats row, channels card, configurations card, leadership card - Tab visibility: Members hidden when member_visibility=hidden, Votes hidden when voting disabled - Loading skeleton, error state with back navigation - Placeholder content for tabs coming in future PRs - Computed signals for categorySeverity, breadcrumbs, joinModeLabel, leadership dates LFXV2-1255 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): align JoinMode enum values with upstream Go service Update JoinMode type values from 'invite-only'/'apply' to 'invite_only'/'application' to match lfx-v2-committee-service. Remove deprecated joinable boolean from CommitteeCreateData. LFXV2-1255 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): remove unused joinModeOptions property from settings component Addresses asithade's review — joinModeOptions was declared but unreferenced in template. LFXV2-1283 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): align interface with upstream API data shapes - Change mailing_list from GroupMailingList object to plain string (upstream returns *string) - Change chat_channel from GroupChatChannel object to plain string (upstream returns *string) - Rename total_voting_reps to total_voting_repos to match upstream field name - Update template to display mailing_list and chat_channel as strings Addresses P0 data-shape mismatches flagged by asithade in PR #294. LFXV2-1283 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): guard Edit button with canManageConfigurations instead of !isBoardMember The Edit button was visible to all non-board-member personas, including Executive Directors who are not committee writers. Replace the broad !isBoardMember() guard with canManageConfigurations() which correctly restricts Edit access to maintainers and users with the writer flag set on the committee. Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * Revert "fix(committees): guard Edit button with canManageConfigurations instead of !isBoardMember" This reverts commit dccaa85. Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address asithade review findings on committee detail view - P0: Remove Leadership card (chair/co_chair not returned by upstream API) - P0: Remove chair/co_chair from Committee interface (not in upstream response) - P1: Fix Edit button auth — use canManageConfigurations() instead of !isBoardMember() - P1: Replace raw animate-pulse skeleton with lfx-route-loading component - P1: Replace raw "Voting Enabled" badge span with lfx-tag wrapper - P2: Replace PrimeNG Tabs (5 imports) with signal-driven tabs using Tailwind - Remove unused hasChair/hasCoChair signals and elected date initializers LFXV2-1283 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * feat(committees): add CSV export button to groups list page Files modified: - apps/lfx-one/src/app/modules/committees/utils/export-groups.util.ts (new) - apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts - apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.html LFXV2-1283 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * Revert "feat(committees): add CSV export button to groups list page" This reverts commit f426310aecd3c92bfb0548eb5881f06bd4af062f. Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * feat(committees): add GET /committees/:id/meetings endpoint Add BFF endpoint that fetches meetings for a specific committee by querying with tags=committee_uid:{id}. Includes server-side controller, service method with lazy MeetingService import, and frontend service method. This replaces the need to fetch all project meetings and filter client-side by committee UID. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): use committee_uid param for meetings query The upstream meeting service uses committee_uid as a direct query parameter, not as a tag. Also reorders getCommitteeMeetings before private methods to satisfy member-ordering lint rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address PR review findings for detail shell - Replace manual date formatting signals with DatePipe - Add @else placeholder for website section when no URL configured - Remove unused formattedCreatedDate/formattedUpdatedDate initializers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address review feedback on committee detail shell LFXV2-committees-detail-shell Remove redundant /committees/:id/meetings endpoint (existing /api/meetings supports committee_uid query param). Replace joinModeLabel computed signal with JoinModeLabelPipe + JOIN_MODE_LABELS constant. Use [ngClass] instead of multiple [class.*] bindings on tab buttons. Use @switch for mutually exclusive tab panels. Remove unused COMMITTEE_LABEL import. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address PR #294 review comments - Replace isBoardMember/canManageConfigurations with canEdit based on committee.writer (API-driven authorization) - Remove PersonaService injection from committee-view and committee-members components (writer field handles auth) - Replace raw avatar div with lfx-avatar in committee-members-manager - Replace manual first_name/last_name concatenation with fullName pipe in committee-members-manager and committee-members templates - Add getMemberDisplayName helper for TS-side name formatting - Remove unused GroupMailingList, GroupChatChannel, ChatPlatform dead code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): use CommitteeMemberVisibility enum instead of string literal 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 detail shell PR - Extract overview tab (~180 lines) into CommitteeOverviewComponent — shell is now a thin data-fetching + tab-routing wrapper - Type-narrow activeTab from string to CommitteeTab union type - Differentiate error states: 404/403 shows "Group Not Found", 500/network shows "Something Went Wrong" with retry button - Remove chair/co_chair from CommitteeUpdateData — upstream PUT does not accept these fields - Remove stale LeadershipRole type and CommitteeLeadership interface (unused after leadership card removal) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): add console.error to silent catchError in loadCommittee The catchError in loadCommittee() silently discarded HTTP failures with no logging, making production debugging impossible when the committee fetch fails. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): address round 4 review findings on detail shell PR - Replace BehaviorSubject with WritableSignal for refresh trigger - Add standalone: true to JoinModeLabelPipe - Type-narrow JOIN_MODE_LABELS from Record<string> to Record<JoinMode> - Replace mb-4 spacing with flex + gap-4 in overview template cards Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org> * fix(committees): replace mb-2 label spacing with flex gap-2 in overview channels Replace margin-based label-to-content spacing (mb-2) with flex + gap-2 parent containers in channel item wrappers 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>
1 parent f1eb355 commit 693baf6

13 files changed

Lines changed: 519 additions & 301 deletions

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

Lines changed: 155 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,96 +4,185 @@
44
<div class="container mx-auto py-6 px-8">
55
<!-- Loading State -->
66
@if (loading()) {
7+
<lfx-route-loading />
8+
} @else if (error()) {
9+
<!-- Error State -->
710
<div class="flex justify-center items-center min-h-96">
8-
<div class="text-center">
9-
<i class="fa-light fa-spinner-third fa-spin text-3xl text-blue-600 mb-4"></i>
10-
<p class="text-gray-600">Loading group details...</p>
11-
</div>
12-
</div>
13-
}
14-
15-
<!-- Error State -->
16-
@if (error()) {
17-
<div class="flex justify-center items-center min-h-96">
18-
<div class="text-center">
19-
<i class="fa-light fa-exclamation-triangle text-3xl text-red-500 mb-4"></i>
20-
<h2 class="mb-2">Group Not Found</h2>
21-
<p class="text-gray-600 mb-4">The group you're looking for doesn't exist or has been removed.</p>
22-
<lfx-button label="Back to Groups" styleClass="p-button-outlined" (onClick)="goBack()"> </lfx-button>
11+
<div class="text-center max-w-md">
12+
@if (errorType() === 'not-found') {
13+
<div class="w-16 h-16 rounded-full bg-red-50 flex items-center justify-center mx-auto mb-4">
14+
<i class="fa-light fa-exclamation-triangle text-2xl text-red-500"></i>
15+
</div>
16+
<h2 class="text-lg font-semibold text-gray-900 mb-2">Group Not Found</h2>
17+
<p class="text-sm text-gray-500 mb-6">The group you're looking for doesn't exist, has been removed, or you may not have permission to view it.</p>
18+
<lfx-button label="Back to Groups" severity="secondary" [outlined]="true" icon="fa-light fa-arrow-left" (onClick)="goBack()"></lfx-button>
19+
} @else {
20+
<div class="w-16 h-16 rounded-full bg-amber-50 flex items-center justify-center mx-auto mb-4">
21+
<i class="fa-light fa-cloud-exclamation text-2xl text-amber-500"></i>
22+
</div>
23+
<h2 class="text-lg font-semibold text-gray-900 mb-2">Something Went Wrong</h2>
24+
<p class="text-sm text-gray-500 mb-6">We couldn't load this group right now. Please try again.</p>
25+
<div class="flex items-center justify-center gap-3">
26+
<lfx-button label="Try Again" severity="info" [outlined]="true" icon="fa-light fa-rotate-right" (onClick)="refreshCommittee()"></lfx-button>
27+
<lfx-button label="Back to Groups" severity="secondary" [outlined]="true" icon="fa-light fa-arrow-left" (onClick)="goBack()"></lfx-button>
28+
</div>
29+
}
2330
</div>
2431
</div>
25-
}
26-
27-
<!-- Committee Content -->
28-
@if (committee()?.uid && !loading() && !error()) {
32+
} @else if (committee()?.uid) {
33+
<!-- Committee Dashboard Content -->
2934
<div class="flex flex-col gap-6">
3035
<!-- Header Section -->
3136
<div class="flex justify-between items-start">
32-
<div class="flex flex-col gap-4" data-testid="committee-view-header">
37+
<div class="flex flex-col gap-3" data-testid="committee-view-header">
3338
<lfx-breadcrumb [model]="breadcrumbItems()" data-testid="committee-view-breadcrumb"></lfx-breadcrumb>
34-
<div class="flex flex-col gap-1">
39+
<div class="flex items-center gap-3">
3540
<h1 data-testid="committee-view-name">{{ committee()?.name }}</h1>
36-
<p class="text-gray-500" data-testid="committee-view-description">{{ committee()?.description }}</p>
41+
@if (!committee()?.public) {
42+
<i class="fa-light fa-lock w-4 h-4 text-gray-400"></i>
43+
}
3744
</div>
38-
<div class="flex items-center gap-4 text-sm text-gray-500">
45+
@if (committee()?.description) {
46+
<p class="text-gray-500 max-w-2xl" data-testid="committee-view-description">{{ committee()?.description }}</p>
47+
}
48+
<div class="flex items-center gap-3 flex-wrap">
3949
@if (committee()?.category) {
4050
<lfx-tag [value]="committee()!.category" [severity]="categorySeverity()"></lfx-tag>
4151
}
42-
<span class="text-gray-600">Created {{ formattedCreatedDate() }}</span>
52+
@if (committee()?.enable_voting) {
53+
<lfx-tag value="Voting Enabled" severity="success"></lfx-tag>
54+
}
55+
<span class="text-xs text-gray-400">Created {{ committee()?.created_at | date: 'MMM d, y' }}</span>
4356
@if (committee()?.updated_at) {
44-
<span class="text-gray-600">Last updated {{ formattedUpdatedDate() }}</span>
57+
<span class="text-xs text-gray-400">· Updated {{ committee()?.updated_at | date: 'MMM d, y' }}</span>
4558
}
4659
</div>
4760
</div>
4861

49-
<!-- Action Menu -->
50-
@if (!isBoardMember()) {
51-
<div class="flex items-center gap-3">
52-
<lfx-button icon="fa-light fa-file-lines" label="Edit Group" size="small" [routerLink]="['/groups', committee()?.uid || '', 'edit']"> </lfx-button>
62+
<!-- Action Buttons -->
63+
@if (canEdit()) {
64+
<div class="flex items-center gap-2 flex-shrink-0">
65+
<lfx-button icon="fa-light fa-pen-to-square" label="Edit" size="small" [routerLink]="['/groups', committee()?.uid || '', 'edit']"></lfx-button>
5366
</div>
5467
}
5568
</div>
5669

57-
<!-- Configurations Section -->
58-
<lfx-card data-testid="committee-view-configurations">
59-
<h3 class="text-sm font-medium mb-4" data-testid="committee-view-configurations-title">Configurations</h3>
60-
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-3">
61-
<div class="flex items-center justify-between" data-testid="committee-view-config-public">
62-
<span class="text-sm">Public</span>
63-
<lfx-tag [value]="committee()?.public ? 'Enabled' : 'Disabled'" [severity]="committee()?.public ? 'success' : 'danger'"></lfx-tag>
64-
</div>
65-
<div class="flex items-center justify-between" data-testid="committee-view-config-voting">
66-
<span class="text-sm">Voting</span>
67-
<lfx-tag [value]="committee()?.enable_voting ? 'Enabled' : 'Disabled'" [severity]="committee()?.enable_voting ? 'success' : 'danger'"></lfx-tag>
68-
</div>
69-
<div class="flex items-center justify-between" data-testid="committee-view-config-business-email">
70-
<span class="text-sm">Business Email Required</span>
71-
<lfx-tag
72-
[value]="committee()?.business_email_required ? 'Enabled' : 'Disabled'"
73-
[severity]="committee()?.business_email_required ? 'success' : 'danger'"></lfx-tag>
70+
<!-- Tabs -->
71+
<div class="flex gap-1 border-b" data-testid="committee-view-tabs">
72+
<button
73+
(click)="activeTab.set('overview')"
74+
class="px-4 py-2 text-sm font-medium transition-colors"
75+
[ngClass]="{
76+
'border-b-2 border-blue-600 text-blue-600': activeTab() === 'overview',
77+
'text-gray-500': activeTab() !== 'overview',
78+
}">
79+
<i class="fa-light fa-gauge mr-2"></i>Overview
80+
</button>
81+
@if (isMembersTabVisible()) {
82+
<button
83+
(click)="activeTab.set('members')"
84+
class="px-4 py-2 text-sm font-medium transition-colors"
85+
[ngClass]="{
86+
'border-b-2 border-blue-600 text-blue-600': activeTab() === 'members',
87+
'text-gray-500': activeTab() !== 'members',
88+
}">
89+
<i class="fa-light fa-users mr-2"></i>Members
90+
</button>
91+
}
92+
@if (isVotesTabVisible()) {
93+
<button
94+
(click)="activeTab.set('votes')"
95+
class="px-4 py-2 text-sm font-medium transition-colors"
96+
[ngClass]="{
97+
'border-b-2 border-blue-600 text-blue-600': activeTab() === 'votes',
98+
'text-gray-500': activeTab() !== 'votes',
99+
}">
100+
<i class="fa-light fa-check-to-slot mr-2"></i>Votes
101+
</button>
102+
}
103+
<button
104+
(click)="activeTab.set('meetings')"
105+
class="px-4 py-2 text-sm font-medium transition-colors"
106+
[ngClass]="{
107+
'border-b-2 border-blue-600 text-blue-600': activeTab() === 'meetings',
108+
'text-gray-500': activeTab() !== 'meetings',
109+
}">
110+
<i class="fa-light fa-calendar mr-2"></i>Meetings
111+
</button>
112+
<button
113+
(click)="activeTab.set('surveys')"
114+
class="px-4 py-2 text-sm font-medium transition-colors"
115+
[ngClass]="{
116+
'border-b-2 border-blue-600 text-blue-600': activeTab() === 'surveys',
117+
'text-gray-500': activeTab() !== 'surveys',
118+
}">
119+
<i class="fa-light fa-chart-simple mr-2"></i>Surveys
120+
</button>
121+
<button
122+
(click)="activeTab.set('documents')"
123+
class="px-4 py-2 text-sm font-medium transition-colors"
124+
[ngClass]="{
125+
'border-b-2 border-blue-600 text-blue-600': activeTab() === 'documents',
126+
'text-gray-500': activeTab() !== 'documents',
127+
}">
128+
<i class="fa-light fa-folder-open mr-2"></i>Documents
129+
</button>
130+
</div>
131+
132+
<!-- Tab Panels -->
133+
@switch (activeTab()) {
134+
@case ('overview') {
135+
<lfx-committee-overview [committee]="committee()!" [canEdit]="canEdit()" />
136+
}
137+
@case ('members') {
138+
@if (isMembersTabVisible()) {
139+
<div class="flex items-center justify-center py-16">
140+
<div class="text-center">
141+
<i class="fa-light fa-users text-3xl text-gray-300 mb-3"></i>
142+
<p class="text-sm font-medium text-gray-600">Members</p>
143+
<p class="text-xs text-gray-400 mt-1">Coming in a future PR</p>
144+
</div>
145+
</div>
146+
}
147+
}
148+
@case ('votes') {
149+
@if (isVotesTabVisible()) {
150+
<div class="flex items-center justify-center py-16">
151+
<div class="text-center">
152+
<i class="fa-light fa-check-to-slot text-3xl text-gray-300 mb-3"></i>
153+
<p class="text-sm font-medium text-gray-600">Votes</p>
154+
<p class="text-xs text-gray-400 mt-1">Coming in a future PR</p>
155+
</div>
156+
</div>
157+
}
158+
}
159+
@case ('meetings') {
160+
<div class="flex items-center justify-center py-16">
161+
<div class="text-center">
162+
<i class="fa-light fa-calendar text-3xl text-gray-300 mb-3"></i>
163+
<p class="text-sm font-medium text-gray-600">Meetings</p>
164+
<p class="text-xs text-gray-400 mt-1">Coming in a future PR</p>
165+
</div>
74166
</div>
75-
<div class="flex items-center justify-between" data-testid="committee-view-config-sso-group">
76-
<span class="text-sm">SSO Group</span>
77-
<lfx-tag
78-
[value]="committee()?.sso_group_enabled ? 'Enabled' : 'Disabled'"
79-
[severity]="committee()?.sso_group_enabled ? 'success' : 'danger'"></lfx-tag>
167+
}
168+
@case ('surveys') {
169+
<div class="flex items-center justify-center py-16">
170+
<div class="text-center">
171+
<i class="fa-light fa-chart-simple text-3xl text-gray-300 mb-3"></i>
172+
<p class="text-sm font-medium text-gray-600">Surveys</p>
173+
<p class="text-xs text-gray-400 mt-1">Coming in a future PR</p>
174+
</div>
80175
</div>
81-
<div class="flex items-center justify-between" data-testid="committee-view-config-audit">
82-
<span class="text-sm">Audit</span>
83-
<lfx-tag
84-
[value]="committee()?.is_audit_enabled ? 'Enabled' : 'Disabled'"
85-
[severity]="committee()?.is_audit_enabled ? 'success' : 'danger'"></lfx-tag>
176+
}
177+
@case ('documents') {
178+
<div class="flex items-center justify-center py-16">
179+
<div class="text-center">
180+
<i class="fa-light fa-folder-open text-3xl text-gray-300 mb-3"></i>
181+
<p class="text-sm font-medium text-gray-600">Documents</p>
182+
<p class="text-xs text-gray-400 mt-1">Coming in a future PR</p>
183+
</div>
86184
</div>
87-
</div>
88-
</lfx-card>
89-
90-
<!-- Members Section -->
91-
@if (committee()?.uid) {
92-
<lfx-committee-members
93-
[committee]="committee()"
94-
[members]="members()"
95-
[membersLoading]="membersLoading()"
96-
(refresh)="refreshMembers()"></lfx-committee-members>
185+
}
97186
}
98187
</div>
99188
}
Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,6 @@
11
// Copyright The Linux Foundation and each contributor to LFX.
22
// SPDX-License-Identifier: MIT
33

4-
// Committee View Component Styles
5-
// Minimal styles as we're using Tailwind CSS for most styling
6-
74
:host {
85
display: block;
96
}
10-
11-
// Ensure proper spacing for the loading spinner
12-
.fa-spinner-third {
13-
animation: spin 1s linear infinite;
14-
}
15-
16-
@keyframes spin {
17-
from {
18-
transform: rotate(0deg);
19-
}
20-
to {
21-
transform: rotate(360deg);
22-
}
23-
}
24-
25-
// Custom table styling for subcommittees if needed
26-
.subcommittees-table {
27-
.p-datatable-tbody > tr > td {
28-
@apply py-3;
29-
}
30-
}
31-
32-
// Settings status indicators
33-
.settings-grid {
34-
.status-indicator {
35-
@apply inline-flex items-center;
36-
37-
i {
38-
@apply text-sm mr-1;
39-
}
40-
41-
&.enabled {
42-
@apply text-emerald-600;
43-
44-
i {
45-
@apply text-emerald-500;
46-
}
47-
}
48-
49-
&.disabled {
50-
@apply text-red-600;
51-
52-
i {
53-
@apply text-red-500;
54-
}
55-
}
56-
}
57-
}

0 commit comments

Comments
 (0)