Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import { AnimateOnScrollModule } from 'primeng/animateonscroll';
import { ConfirmationService, MenuItem } from 'primeng/api';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
import { of } from 'rxjs';
import { debounceTime, distinctUntilChanged, startWith, tap } from 'rxjs/operators';
import { BehaviorSubject, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, startWith, switchMap, tap } from 'rxjs/operators';

import { CommitteeFormComponent } from '../components/committee-form/committee-form.component';
import { UpcomingCommitteeMeetingComponent } from '../components/upcoming-committee-meeting/upcoming-committee-meeting.component';
Expand Down Expand Up @@ -55,22 +55,23 @@ export class CommitteeDashboardComponent {
private readonly dialogService = inject(DialogService);

// Class variables with types
private dialogRef: DynamicDialogRef | undefined;
public project: typeof this.projectService.project;
public selectedCommittee: WritableSignal<Committee | null>;
public isDeleting: WritableSignal<boolean>;
public first: WritableSignal<number>;
public rows: number;
public searchForm: FormGroup;
public categoryFilter: WritableSignal<string | null>;
private searchTerm: Signal<string>;
public committeesLoading: WritableSignal<boolean>;
public committees: Signal<Committee[]>;
public categories: Signal<{ label: string; value: string | null }[]>;
public filteredCommittees: Signal<Committee[]>;
public totalRecords: Signal<number>;
public menuItems: MenuItem[];
public actionMenuItems: MenuItem[];
public refresh: BehaviorSubject<void>;
private searchTerm: Signal<string>;
private dialogRef: DynamicDialogRef | undefined;

public constructor() {
// Initialize all class variables
Expand All @@ -80,6 +81,7 @@ export class CommitteeDashboardComponent {
this.first = signal<number>(0);
this.rows = 10;
this.committeesLoading = signal<boolean>(true);
this.refresh = new BehaviorSubject<void>(undefined);
this.committees = this.initializeCommittees();
this.searchForm = this.initializeSearchForm();
this.categoryFilter = signal<string | null>(null);
Expand Down Expand Up @@ -189,9 +191,7 @@ export class CommitteeDashboardComponent {
}

private refreshCommittees(): void {
this.router.navigate(['/project', this.project()?.slug], { skipLocationChange: true }).then(() => {
this.router.navigate(['/project', this.project()?.slug, 'committees']);
});
this.refresh.next();
}

private openEditDialog(): void {
Expand Down Expand Up @@ -227,7 +227,12 @@ export class CommitteeDashboardComponent {

private initializeCommittees(): Signal<Committee[]> {
return toSignal(
this.project() ? this.committeeService.getCommitteesByProject(this.project()!.id).pipe(tap(() => this.committeesLoading.set(false))) : of([]),
this.project()
? this.refresh.pipe(
tap(() => this.committeesLoading.set(true)),
switchMap(() => this.committeeService.getCommitteesByProject(this.project()!.id).pipe(tap(() => this.committeesLoading.set(false))))
)
: of([]),
{ initialValue: [] }
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ <h1 class="text-3xl font-bold text-gray-900 mb-2">{{ committee()?.name }}</h1>
<div class="lg:col-span-2 flex flex-col gap-6">
<!-- Members Section -->
@if (committee()?.id) {
<lfx-committee-members [committee]="committee()" (refresh)="refreshMembers()"></lfx-committee-members>
<lfx-committee-members
[committee]="committee()"
[members]="members()"
[membersLoading]="membersLoading()"
(refresh)="refreshMembers()"></lfx-committee-members>
}

<!-- Subcommittees Section -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
// SPDX-License-Identifier: MIT

import { CommonModule } from '@angular/common';
import { Component, computed, inject, signal, Signal, WritableSignal } from '@angular/core';
import { Component, computed, inject, Injector, signal, Signal, WritableSignal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute, Router } from '@angular/router';
import { ButtonComponent } from '@components/button/button.component';
import { CardComponent } from '@components/card/card.component';
import { MenuComponent } from '@components/menu/menu.component';
import { TableComponent } from '@components/table/table.component';
import { Committee } from '@lfx-pcc/shared/interfaces';
import { Committee, CommitteeMember } from '@lfx-pcc/shared/interfaces';
import { CommitteeService } from '@services/committee.service';
import { ProjectService } from '@services/project.service';
import { ConfirmationService, MenuItem } from 'primeng/api';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
import { of, switchMap } from 'rxjs';
import { BehaviorSubject, combineLatest, of, switchMap } from 'rxjs';

import { CommitteeFormComponent } from '../components/committee-form/committee-form.component';
import { CommitteeMembersComponent } from '../components/committee-members/committee-members.component';
Expand Down Expand Up @@ -46,25 +46,32 @@ export class CommitteeViewComponent {
private readonly committeeService = inject(CommitteeService);
private readonly confirmationService = inject(ConfirmationService);
private readonly dialogService = inject(DialogService);
private readonly injector = inject(Injector);

// Class variables with types
private dialogRef: DynamicDialogRef | undefined;
public project: typeof this.projectService.project;
public committee: Signal<Committee | null>;
public loading: Signal<boolean>;
public members: WritableSignal<CommitteeMember[]>;
public membersLoading: WritableSignal<boolean>;
public loading: WritableSignal<boolean>;
public error: WritableSignal<boolean>;
public isDeleting: WritableSignal<boolean>;
public actionMenuItems: MenuItem[];
public formattedCreatedDate: Signal<string>;
public formattedUpdatedDate: Signal<string>;
public refresh: BehaviorSubject<void>;

public constructor() {
// Initialize all class variables
this.project = this.projectService.project;
this.error = signal<boolean>(false);
this.isDeleting = signal<boolean>(false);
this.refresh = new BehaviorSubject<void>(undefined);
this.members = signal<CommitteeMember[]>([]);
this.membersLoading = signal<boolean>(true);
this.loading = signal<boolean>(true);
this.committee = this.initializeCommittee();
this.loading = this.initializeLoading();
this.actionMenuItems = this.initializeActionMenuItems();
this.formattedCreatedDate = this.initializeFormattedCreatedDate();
this.formattedUpdatedDate = this.initializeFormattedUpdatedDate();
Expand All @@ -83,13 +90,7 @@ export class CommitteeViewComponent {
}

public refreshMembers(): void {
this.router
.navigate(['/project', this.project()?.slug, 'committees'], {
skipLocationChange: true,
})
.then(() => {
this.router.navigate(['/project', this.project()?.slug, 'committees', this.committee()?.id]);
});
this.refresh.next();
}

// Action handlers
Expand Down Expand Up @@ -148,26 +149,28 @@ export class CommitteeViewComponent {

private initializeCommittee(): Signal<Committee | null> {
return toSignal(
this.route.paramMap.pipe(
switchMap((params) => {
const committeeId = params.get('id');
combineLatest([this.route.paramMap, this.refresh]).pipe(
switchMap(([params]) => {
const committeeId = params?.get('id');
if (!committeeId) {
this.error.set(true);
return of(null);
}
return this.committeeService.getCommittee(committeeId);

return combineLatest([this.committeeService.getCommittee(committeeId), this.committeeService.getCommitteeMembers(committeeId)]).pipe(
switchMap(([committee, members]) => {
this.members.set(members);
this.loading.set(false);
this.membersLoading.set(false);
return of(committee);
})
);
})
),
{ initialValue: null }
);
}

private initializeLoading(): Signal<boolean> {
return computed(() => {
return !this.error() && this.committee() === null;
});
}

private initializeActionMenuItems(): MenuItem[] {
return [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- Copyright The Linux Foundation and each contributor to LFX. -->
<!-- SPDX-License-Identifier: MIT -->

<form [formGroup]="form()" (ngSubmit)="handleSubmit()" class="space-y-6">
<form [formGroup]="form()" (ngSubmit)="onSubmit()" class="space-y-6">
<!-- Basic Information Section -->
<div class="flex flex-col gap-3">
<h3 class="text-lg font-medium text-gray-900">Basic Information</h3>
Expand Down Expand Up @@ -115,13 +115,12 @@ <h3 class="text-lg font-medium text-gray-900">Additional Information</h3>

<!-- Form Actions -->
<div class="flex justify-end gap-3 pt-6 border-t">
<lfx-button label="Cancel" severity="secondary" [outlined]="true" (click)="handleCancel()" size="small" type="button"></lfx-button>
<lfx-button label="Cancel" severity="secondary" [outlined]="true" (click)="onCancel()" size="small" type="button"></lfx-button>
<lfx-button
[label]="isEditing() ? 'Update Committee' : 'Create Committee'"
[loading]="loading()"
[disabled]="loading()"
type="submit"
size="small"
(onClick)="handleSubmit()"></lfx-button>
size="small"></lfx-button>
</div>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class CommitteeFormComponent {
}

// Form submission handler
protected handleSubmit(): void {
protected onSubmit(): void {
this.markAllFieldsAsTouched();

if (this.form().valid) {
Expand All @@ -66,23 +66,23 @@ export class CommitteeFormComponent {
this.committeeService.updateCommittee(committeeId, formValue).subscribe({
next: () => {
this.submitting.set(false);
this.handleSuccess();
this.onSuccess();
},
error: (error) => {
this.submitting.set(false);
this.handleError('Failed to update committee:', error);
this.onError('Failed to update committee:', error);
},
});
} else {
// Create new committee
this.committeeService.createCommittee(formValue).subscribe({
next: () => {
this.submitting.set(false);
this.handleSuccess();
this.onSuccess();
},
error: (error) => {
this.submitting.set(false);
this.handleError('Failed to create committee:', error);
this.onError('Failed to create committee:', error);
},
});
}
Expand All @@ -92,7 +92,7 @@ export class CommitteeFormComponent {
}

// Cancel handler
protected handleCancel(): void {
protected onCancel(): void {
if (this.config.data?.onCancel) {
this.config.data.onCancel();
} else {
Expand Down Expand Up @@ -127,7 +127,7 @@ export class CommitteeFormComponent {
}

// Success handler
private handleSuccess(): void {
private onSuccess(): void {
const isEditing = this.isEditing();
const action = isEditing ? 'updated' : 'created';

Expand All @@ -149,7 +149,7 @@ export class CommitteeFormComponent {
}

// Error handler
private handleError(message: string, error: any): void {
private onError(message: string, error: any): void {
console.error(message, error);

this.messageService.add({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,23 +191,5 @@ <h3 class="text-lg font-medium text-gray-900 mb-2">No Members Yet</h3>
}
</div>
}
} @else {
@if (membersLoading()) {
<div class="flex justify-center items-center h-64">
<i class="fa-light fa-circle-notch fa-spin text-4xl text-gray-400"></i>
</div>
} @else {
<div class="text-center py-8">
<i class="fa-light fa-users text-4xl text-gray-400 mb-4"></i>
@if (members().length > 0) {
<h3 class="text-lg font-medium text-gray-900 mb-2">No Members Found</h3>
<p class="text-gray-600 mb-4">Try adjusting your filters to find members.</p>
} @else {
<h3 class="text-lg font-medium text-gray-900 mb-2">No Members Yet</h3>
<p class="text-gray-600 mb-4">This committee doesn't have any members yet.</p>
<lfx-button label="Add First Member" icon="fa-light fa-user-plus" severity="secondary" (onClick)="openAddMemberDialog()"> </lfx-button>
}
</div>
}
}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: MIT

import { CommonModule } from '@angular/common';
import { Component, computed, inject, Injector, input, OnInit, output, runInInjectionContext, signal, Signal, WritableSignal } from '@angular/core';
import { Component, computed, inject, Injector, input, OnInit, output, signal, Signal, WritableSignal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { ButtonComponent } from '@components/button/button.component';
Expand All @@ -18,7 +18,7 @@ import { AnimateOnScrollModule } from 'primeng/animateonscroll';
import { ConfirmationService, MenuItem, MessageService } from 'primeng/api';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
import { debounceTime, distinctUntilChanged, finalize, startWith, take } from 'rxjs/operators';
import { debounceTime, distinctUntilChanged, startWith, take } from 'rxjs/operators';

import { MemberCardComponent } from '../member-card/member-card.component';
import { MemberFormComponent } from '../member-form/member-form.component';
Expand Down Expand Up @@ -54,13 +54,13 @@ export class CommitteeMembersComponent implements OnInit {

// Input signals
public committee = input.required<Committee | null>();
public members = input.required<CommitteeMember[]>();
public membersLoading = input<boolean>(true);

public readonly refresh = output<void>();

// Class variables with types
private dialogRef: DynamicDialogRef | undefined;
public membersLoading: WritableSignal<boolean>;
public members: Signal<CommitteeMember[]> = signal<CommitteeMember[]>([]);
public selectedMember: WritableSignal<CommitteeMember | null>;
public isDeleting: WritableSignal<boolean>;
public memberActionMenuItems: MenuItem[] = [];
Expand All @@ -85,7 +85,6 @@ export class CommitteeMembersComponent implements OnInit {
// Initialize all class variables
this.selectedMember = signal<CommitteeMember | null>(null);
this.isDeleting = signal<boolean>(false);
this.membersLoading = signal<boolean>(true);
// Initialize filter form
this.filterForm = this.initializeFilterForm();
this.searchTerm = this.initializeSearchTerm();
Expand All @@ -104,10 +103,7 @@ export class CommitteeMembersComponent implements OnInit {
}

public ngOnInit(): void {
runInInjectionContext(this.injector, () => {
this.members = this.initializeMembers();
this.memberActionMenuItems = this.initializeMemberActionMenuItems();
});
this.memberActionMenuItems = this.initializeMemberActionMenuItems();
}

public onMemberMenuToggle(data: { event: Event; member: CommitteeMember; menu: MenuComponent }): void {
Expand Down Expand Up @@ -268,13 +264,6 @@ export class CommitteeMembersComponent implements OnInit {
private initializeOrganizationFilter(): Signal<string | null> {
return toSignal(this.filterForm.get('organization')!.valueChanges.pipe(startWith(null), distinctUntilChanged()), { initialValue: null });
}
private initializeMembers(): Signal<CommitteeMember[]> {
const committee = this.committee();
if (!committee || !committee.id) {
return signal<CommitteeMember[]>([]);
}
return toSignal(this.committeeService.getCommitteeMembers(committee.id).pipe(finalize(() => this.membersLoading.set(false))), { initialValue: [] });
}

private initializeMemberActionMenuItems(): MenuItem[] {
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@
[loading]="submitting()"
[disabled]="submitting()"
type="submit"
size="small"
(onClick)="onSubmit()"></lfx-button>
size="small"></lfx-button>
</div>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,6 @@ <h3 class="text-lg font-medium text-gray-900">Meeting Settings</h3>
[loading]="submitting()"
[disabled]="submitting()"
type="submit"
size="small"
(onClick)="onSubmit()"></lfx-button>
size="small"></lfx-button>
</div>
</form>
Loading