Skip to content

Commit 7829da9

Browse files
authored
refactor(committees): migrate to global architecture (#165)
* refactor(committees): migrate to global architecture Migrate committees functionality from project-scoped to global architecture: - Remove project-specific committee routes and components - Add global committee routes with new view page - Migrate committee components to global scope - Update services for global committee context - Add committee type colors to Tailwind safelist - Update related components and documentation LFXV2-765 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Asitha de Silva <[email protected]> * refactor(meetings): remove unnecessary afterNextRender wrapper Remove afterNextRender wrapper from meeting-join component as toSignal already handles SSR properly with initialValue options. LFXV2-765 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Asitha de Silva <[email protected]> * refactor(meetings): remove redundant loading cleanup in catchError Remove redundant loading.set(false) calls in catchError blocks and rely on finalize as the single cleanup point for both upcoming and past meetings initialization. LFXV2-765 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Asitha de Silva <[email protected]> --------- Signed-off-by: Asitha de Silva <[email protected]>
1 parent f15f8bb commit 7829da9

File tree

53 files changed

+369
-1129
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+369
-1129
lines changed

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

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ <h1 class="font-inter text-xl font-semibold">{{ committeeLabelPlural }}</h1>
4949
control="category"
5050
size="small"
5151
[options]="categories()"
52-
[placeholder]="'Select ' + committeeLabel + ' Type'"
52+
placeholder="Select Type"
5353
[showClear]="true"
5454
styleClass="w-full"
5555
(onChange)="onCategoryChange($event.value)"
@@ -74,29 +74,32 @@ <h1 class="font-inter text-xl font-semibold">{{ committeeLabelPlural }}</h1>
7474

7575
<!-- Content Area - Table or Cards -->
7676
<div class="w-full">
77-
@if (filteredCommittees().length === 0 && project()?.projectId) {
77+
@if (committees().length === 0 && project()?.uid) {
78+
<!-- Empty state: No committees exist -->
7879
<div class="text-center py-12">
7980
<div class="bg-muted/50 rounded-lg p-8">
8081
<i class="fa-light fa-people-group text-5xl text-muted-foreground mb-4"></i>
8182
<h3 class="text-muted-foreground">No {{ committeeLabelPlural.toLowerCase() }} found</h3>
8283
</div>
8384
</div>
84-
} @else if (filteredCommittees().length > 0) {
85+
} @else if (filteredCommittees().length === 0) {
86+
<!-- Empty state: Committees exist but filters returned no results -->
87+
<div class="text-center py-12">
88+
<div class="bg-muted/50 rounded-lg p-8">
89+
<i class="fa-light fa-filter text-5xl text-muted-foreground mb-4"></i>
90+
<h3 class="text-muted-foreground">No {{ committeeLabelPlural.toLowerCase() }} match your filters</h3>
91+
<p class="text-sm text-muted-foreground mt-2">Try adjusting your search or filter criteria</p>
92+
</div>
93+
</div>
94+
} @else {
8595
<lfx-committee-table
8696
[committees]="filteredCommittees()"
8797
[canManageCommittee]="canCreateGroup()"
8898
[committeeLabel]="committeeLabel"
89-
[isDeleting]="isDeleting()"
90-
(viewCommittee)="onViewCommittee($event)"
91-
(editCommittee)="onEditCommittee($event)"
92-
(deleteCommittee)="onDeleteCommittee($event)"
93-
(addMember)="onAddMember($event)">
99+
(refresh)="refreshCommittees()">
94100
</lfx-committee-table>
95101
}
96102
</div>
97103
</div>
98104
}
99-
100-
<!-- Confirmation dialog -->
101-
<p-confirmDialog></p-confirmDialog>
102105
</div>

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

Lines changed: 8 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@ import { COMMITTEE_LABEL } from '@lfx-one/shared/constants';
1414
import { Committee, ProjectContext } from '@lfx-one/shared/interfaces';
1515
import { CommitteeService } from '@services/committee.service';
1616
import { PersonaService } from '@services/persona.service';
17-
import { ConfirmationService, MessageService } from 'primeng/api';
18-
import { ConfirmDialogModule } from 'primeng/confirmdialog';
17+
import { MessageService } from 'primeng/api';
1918
import { BehaviorSubject, catchError, combineLatest, debounceTime, distinctUntilChanged, finalize, of, startWith, switchMap } from 'rxjs';
2019

2120
import { CommitteeTableComponent } from '../components/committee-table/committee-table.component';
2221

2322
@Component({
2423
selector: 'lfx-committee-dashboard',
2524
standalone: true,
26-
imports: [CommonModule, ReactiveFormsModule, InputTextComponent, SelectComponent, ButtonComponent, ConfirmDialogModule, CommitteeTableComponent],
25+
imports: [CommonModule, ReactiveFormsModule, InputTextComponent, SelectComponent, ButtonComponent, CommitteeTableComponent],
2726
templateUrl: './committee-dashboard.component.html',
2827
styleUrl: './committee-dashboard.component.scss',
2928
})
@@ -33,7 +32,6 @@ export class CommitteeDashboardComponent {
3332
private readonly committeeService = inject(CommitteeService);
3433
private readonly personaService = inject(PersonaService);
3534
private readonly router = inject(Router);
36-
private readonly confirmationService = inject(ConfirmationService);
3735
private readonly messageService = inject(MessageService);
3836

3937
// Use the configurable label constants
@@ -42,8 +40,6 @@ export class CommitteeDashboardComponent {
4240

4341
// State signals
4442
public project: Signal<ProjectContext | null>;
45-
public selectedCommittee: WritableSignal<Committee | null>;
46-
public isDeleting: WritableSignal<boolean>;
4743
public searchForm: FormGroup;
4844
public categoryFilter: WritableSignal<string | null>;
4945
public votingStatusFilter: WritableSignal<string | null>;
@@ -76,8 +72,6 @@ export class CommitteeDashboardComponent {
7672
this.canCreateGroup = computed(() => this.isMaintainer() && this.isNonFoundationProjectSelected());
7773

7874
// Initialize state
79-
this.selectedCommittee = signal<Committee | null>(null);
80-
this.isDeleting = signal<boolean>(false);
8175
this.committeesLoading = signal<boolean>(true);
8276
this.refresh = new BehaviorSubject<void>(undefined);
8377

@@ -115,8 +109,8 @@ export class CommitteeDashboardComponent {
115109
}
116110

117111
public openCreateDialog(): void {
118-
const projectId = this.project()?.projectId;
119-
if (!projectId) {
112+
const uid = this.project()?.uid;
113+
if (!uid) {
120114
this.messageService.add({
121115
severity: 'warn',
122116
summary: 'Warning',
@@ -128,87 +122,7 @@ export class CommitteeDashboardComponent {
128122
this.router.navigate(['/groups/create']);
129123
}
130124

131-
public onEditCommittee(committee: Committee): void {
132-
this.selectedCommittee.set(committee);
133-
this.editCommittee();
134-
}
135-
136-
public onViewCommittee(committee: Committee): void {
137-
this.selectedCommittee.set(committee);
138-
this.viewCommittee();
139-
}
140-
141-
public onDeleteCommittee(committee: Committee): void {
142-
this.selectedCommittee.set(committee);
143-
this.deleteCommittee();
144-
}
145-
146-
public onAddMember(committee: Committee): void {
147-
this.selectedCommittee.set(committee);
148-
// TODO: Implement add member functionality
149-
this.messageService.add({
150-
severity: 'info',
151-
summary: 'Info',
152-
detail: 'Add member functionality coming soon',
153-
});
154-
}
155-
156-
private viewCommittee(): void {
157-
const committee = this.selectedCommittee();
158-
const project = this.project();
159-
if (committee && project) {
160-
this.router.navigate(['/project', project.slug, 'committees', committee.uid]);
161-
}
162-
}
163-
164-
private editCommittee(): void {
165-
const committee = this.selectedCommittee();
166-
if (committee) {
167-
this.router.navigate(['/groups', committee.uid, 'edit']);
168-
}
169-
}
170-
171-
private deleteCommittee(): void {
172-
const committee = this.selectedCommittee();
173-
if (!committee) return;
174-
175-
this.confirmationService.confirm({
176-
message: `Are you sure you want to delete the ${this.committeeLabel.toLowerCase()} "${committee.name}"? This action cannot be undone.`,
177-
header: `Delete ${this.committeeLabel}`,
178-
acceptLabel: 'Delete',
179-
rejectLabel: 'Cancel',
180-
acceptButtonStyleClass: 'p-button-danger p-button-sm',
181-
rejectButtonStyleClass: 'p-button-outlined p-button-sm',
182-
accept: () => this.performDelete(committee),
183-
});
184-
}
185-
186-
private performDelete(committee: Committee): void {
187-
this.isDeleting.set(true);
188-
189-
this.committeeService.deleteCommittee(committee.uid).subscribe({
190-
next: () => {
191-
this.isDeleting.set(false);
192-
this.messageService.add({
193-
severity: 'success',
194-
summary: 'Success',
195-
detail: `${this.committeeLabel} deleted successfully`,
196-
});
197-
this.refreshCommittees();
198-
},
199-
error: (error) => {
200-
this.isDeleting.set(false);
201-
this.messageService.add({
202-
severity: 'error',
203-
summary: 'Error',
204-
detail: `Failed to delete ${this.committeeLabel.toLowerCase()}`,
205-
});
206-
console.error('Failed to delete committee:', error);
207-
},
208-
});
209-
}
210-
211-
private refreshCommittees(): void {
125+
public refreshCommittees(): void {
212126
this.committeesLoading.set(true);
213127
this.refresh.next();
214128
}
@@ -232,13 +146,13 @@ export class CommitteeDashboardComponent {
232146
return toSignal(
233147
combineLatest([project$, this.refresh]).pipe(
234148
switchMap(([project]) => {
235-
if (!project?.projectId) {
149+
if (!project?.uid) {
236150
this.committeesLoading.set(false);
237151
return of([]);
238152
}
239153

240154
this.committeesLoading.set(true);
241-
return this.committeeService.getCommitteesByProject(project.projectId).pipe(
155+
return this.committeeService.getCommitteesByProject(project.uid).pipe(
242156
catchError((error) => {
243157
console.error('Failed to load committees:', error);
244158
return of([]);
@@ -272,7 +186,7 @@ export class CommitteeDashboardComponent {
272186
value: cat,
273187
}));
274188

275-
return [{ label: `All ${this.committeeLabelPlural} Types`, value: null }, ...categoryOptions];
189+
return [{ label: `All Types`, value: null }, ...categoryOptions];
276190
});
277191
}
278192

apps/lfx-one/src/app/modules/committees/committee-manage/committee-manage.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export class CommitteeManageComponent {
8787
},
8888
display_name: this.form.value.display_name || this.form.value.name,
8989
website: this.form.value.website || null,
90-
project_uid: this.projectContextService.getProjectId() || this.projectContextService.getFoundationId() || null,
90+
project_uid: this.projectContextService.getProjectUid() || this.projectContextService.getFoundationId() || null,
9191
};
9292

9393
const committeeData = this.cleanFormData(formValue);

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

File renamed without changes.

apps/lfx-one/src/app/modules/project/committees/committee-view/committee-view.component.scss renamed to apps/lfx-one/src/app/modules/committees/committee-view/committee-view.component.scss

File renamed without changes.

apps/lfx-one/src/app/modules/project/committees/committee-view/committee-view.component.ts renamed to apps/lfx-one/src/app/modules/committees/committee-view/committee-view.component.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { CardComponent } from '@components/card/card.component';
1010
import { MenuComponent } from '@components/menu/menu.component';
1111
import { Committee, CommitteeMember } from '@lfx-one/shared/interfaces';
1212
import { CommitteeService } from '@services/committee.service';
13-
import { ProjectService } from '@services/project.service';
13+
import { ProjectContextService } from '@services/project-context.service';
1414
import { ConfirmationService, MenuItem, MessageService } from 'primeng/api';
1515
import { ConfirmDialogModule } from 'primeng/confirmdialog';
1616
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
@@ -39,15 +39,14 @@ export class CommitteeViewComponent {
3939
// Injected services
4040
private readonly route = inject(ActivatedRoute);
4141
private readonly router = inject(Router);
42-
private readonly projectService = inject(ProjectService);
42+
private readonly projectContextService = inject(ProjectContextService);
4343
private readonly committeeService = inject(CommitteeService);
4444
private readonly confirmationService = inject(ConfirmationService);
4545
private readonly dialogService = inject(DialogService);
4646
private readonly messageService = inject(MessageService);
4747

4848
// Class variables with types
4949
private dialogRef: DynamicDialogRef | undefined;
50-
public project: typeof this.projectService.project;
5150
public committee: Signal<Committee | null>;
5251
public members: WritableSignal<CommitteeMember[]>;
5352
public membersLoading: WritableSignal<boolean>;
@@ -61,7 +60,6 @@ export class CommitteeViewComponent {
6160

6261
public constructor() {
6362
// Initialize all class variables
64-
this.project = this.projectService.project;
6563
this.error = signal<boolean>(false);
6664
this.isDeleting = signal<boolean>(false);
6765
this.refresh = new BehaviorSubject<void>(undefined);

apps/lfx-one/src/app/modules/committees/committees.routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export const COMMITTEE_ROUTES: Routes = [
1717
loadComponent: () => import('./committee-manage/committee-manage.component').then((m) => m.CommitteeManageComponent),
1818
canActivate: [authGuard],
1919
},
20+
{
21+
path: ':id',
22+
loadComponent: () => import('./committee-view/committee-view.component').then((m) => m.CommitteeViewComponent),
23+
canActivate: [authGuard],
24+
},
2025
{
2126
path: ':id/edit',
2227
loadComponent: () => import('./committee-manage/committee-manage.component').then((m) => m.CommitteeManageComponent),

apps/lfx-one/src/app/modules/committees/components/committee-form/committee-form.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ export class CommitteeFormComponent {
5656

5757
private initializeParentCommitteeOptions(): Signal<{ label: string; value: string | null }[]> {
5858
// Get project ID from context (project or foundation)
59-
const projectId = this.projectContextService.getProjectId() || this.projectContextService.getFoundationId();
59+
const uid = this.projectContextService.getProjectUid() || this.projectContextService.getFoundationId();
6060

6161
const committees = toSignal(
62-
this.committeeService.getCommitteesByProject(projectId).pipe(
62+
this.committeeService.getCommitteesByProject(uid).pipe(
6363
map((committees: Committee[]) => {
6464
// Filter to only top-level committees (no parent_uid)
6565
const topLevelCommittees = committees.filter((committee) => !committee.parent_uid);

apps/lfx-one/src/app/modules/project/committees/components/committee-members/committee-members.component.html renamed to apps/lfx-one/src/app/modules/committees/components/committee-members/committee-members.component.html

File renamed without changes.

apps/lfx-one/src/app/modules/project/committees/components/committee-members/committee-members.component.scss renamed to apps/lfx-one/src/app/modules/committees/components/committee-members/committee-members.component.scss

File renamed without changes.

0 commit comments

Comments
 (0)