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 @@ -208,13 +208,13 @@ export class CommitteeManageComponent {
// Update existing committee
this.committeeService.updateCommittee(this.committeeId()!, committeeData).subscribe({
next: () => this.handleCommitteeSuccess('updated'),
error: (error) => this.handleCommitteeError(error, 'update'),
error: () => this.handleCommitteeError('update'),
});
} else {
// Create new committee
this.committeeService.createCommittee(committeeData).subscribe({
next: (committee) => this.handleCreateSuccess(committee),
error: (error) => this.handleCommitteeError(error, 'create'),
error: () => this.handleCommitteeError('create'),
});
}
}
Expand Down Expand Up @@ -246,8 +246,7 @@ export class CommitteeManageComponent {
this.showMemberOperationToast(totalSuccess, totalFailed, totalSuccess + totalFailed);
this.router.navigate(['/groups']);
},
error: (error) => {
console.error('Error processing member changes:', error);
error: () => {
this.messageService.add({
severity: 'error',
summary: 'Error',
Expand Down Expand Up @@ -333,8 +332,7 @@ export class CommitteeManageComponent {
// Navigate back to committees list
this.router.navigate(['/groups']);
},
error: (error: unknown) => {
console.error('Error saving committee and members:', error);
error: () => {
this.messageService.add({
severity: 'error',
summary: 'Error',
Expand Down Expand Up @@ -455,8 +453,7 @@ export class CommitteeManageComponent {
}
}

private handleCommitteeError(error: unknown, operation: 'create' | 'update'): void {
console.error(`Error ${operation} committee:`, error);
private handleCommitteeError(operation: 'create' | 'update'): void {
this.submitting.set(false);

this.messageService.add({
Expand Down Expand Up @@ -570,10 +567,7 @@ export class CommitteeManageComponent {
private createMemberOperation(type: string, operation: () => Observable<unknown>) {
return operation().pipe(
switchMap(() => of({ type, success: 1, failed: 0 })),
catchError((error) => {
console.error(`Error ${type} member:`, error);
return of({ type, success: 0, failed: 1 });
})
catchError(() => of({ type, success: 0, failed: 1 }))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ <h1 data-testid="committee-view-name">{{ committee()?.name }}</h1>
}
<span class="text-xs text-gray-400">Created {{ committee()?.created_at | date: 'MMM d, y' }}</span>
@if (committee()?.updated_at) {
<span class="text-xs text-gray-400">· Updated {{ committee()?.updated_at | date: 'MMM d, y' }}</span>
<span class="text-xs text-gray-400">&middot; Updated {{ committee()?.updated_at | date: 'MMM d, y' }}</span>
}
</div>
</div>
Expand Down Expand Up @@ -187,6 +187,4 @@ <h1 data-testid="committee-view-name">{{ committee()?.name }}</h1>
</div>
}

<!-- Confirmation Dialog -->
<p-confirmDialog></p-confirmDialog>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { BreadcrumbComponent } from '@components/breadcrumb/breadcrumb.component';
import { ButtonComponent } from '@components/button/button.component';
import { TagComponent } from '@components/tag/tag.component';
import { RouteLoadingComponent } from '@components/loading/route-loading.component';
import { Committee, CommitteeMemberVisibility, getCommitteeCategorySeverity, TagSeverity } from '@lfx-one/shared';
import { CommitteeService } from '@services/committee.service';
import { RouteLoadingComponent } from '@components/loading/route-loading.component';
import { MenuItem, MessageService } from 'primeng/api';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { catchError, combineLatest, finalize, of, switchMap } from 'rxjs';

import { CommitteeOverviewComponent } from '../components/committee-overview/committee-overview.component';
Expand All @@ -21,17 +20,7 @@ type CommitteeTab = 'overview' | 'members' | 'votes' | 'meetings' | 'surveys' |

@Component({
selector: 'lfx-committee-view',
imports: [
BreadcrumbComponent,
ButtonComponent,
TagComponent,
ConfirmDialogModule,
RouterLink,
RouteLoadingComponent,
DatePipe,
NgClass,
CommitteeOverviewComponent,
],
imports: [BreadcrumbComponent, ButtonComponent, TagComponent, RouterLink, RouteLoadingComponent, DatePipe, NgClass, CommitteeOverviewComponent],
templateUrl: './committee-view.component.html',
styleUrl: './committee-view.component.scss',
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ <h3 class="text-lg font-medium text-gray-900">{{ committeeLabel.singular }} Memb
size="small"
severity="secondary"
[href]="'mailto:' + member.email"
target="_blank"
pTooltip="Send Message">
</lfx-button>
}
Expand Down Expand Up @@ -186,15 +185,8 @@ <h3 class="text-lg font-medium text-gray-900">{{ committeeLabel.singular }} Memb
<tr>
<td [attr.colspan]="committee()?.enable_voting ? 6 : 4" class="text-center py-8">
<div class="text-center">
@if (groupBehavioralClass() === 'governing-board' || groupBehavioralClass() === 'oversight-committee') {
<i class="fa-light fa-user-crown text-3xl text-gray-300 mb-2"></i>
<p class="text-sm font-medium text-gray-600">No Board Members Found</p>
<p class="text-xs text-gray-400 mt-1">Board members with voting rights will appear here once added.</p>
} @else {
<i class="fa-light fa-users-gear text-3xl text-gray-300 mb-2"></i>
<p class="text-sm font-medium text-gray-600">No Contributors Yet</p>
<p class="text-xs text-gray-400 mt-1">Contributors will appear here as they join this working group.</p>
}
<i class="fa-light fa-users text-3xl text-gray-300 mb-2"></i>
<p class="text-sm font-medium text-gray-600">No members found</p>
@if (canManageMembers()) {
<div class="mt-3">
<button class="text-xs text-blue-600 hover:text-blue-700 font-medium" (click)="openAddMemberDialog()">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: MIT

import { TitleCasePipe } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
import { Component, computed, inject, input, OnInit, output, signal, Signal } from '@angular/core';
import { FullNamePipe } from '@pipes/full-name.pipe';
import { toSignal } from '@angular/core/rxjs-interop';
Expand All @@ -14,9 +13,8 @@ import { MenuComponent } from '@components/menu/menu.component';
import { SelectComponent } from '@components/select/select.component';
import { TableComponent } from '@components/table/table.component';
import { COMMITTEE_LABEL } from '@lfx-one/shared/constants';
import { Committee, CommitteeMember, GroupBehavioralClass } from '@lfx-one/shared/interfaces';
import { Committee, CommitteeMember } from '@lfx-one/shared/interfaces';
import { CommitteeService } from '@services/committee.service';
import { PersonaService } from '@services/persona.service';
import { ConfirmationService, MenuItem, MessageService } from 'primeng/api';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { DialogService, DynamicDialogModule } from 'primeng/dynamicdialog';
Expand Down Expand Up @@ -51,13 +49,11 @@ export class CommitteeMembersComponent implements OnInit {
private readonly confirmationService = inject(ConfirmationService);
private readonly dialogService = inject(DialogService);
private readonly messageService = inject(MessageService);
private readonly personaService = inject(PersonaService);

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

public readonly refresh = output<void>();

Expand All @@ -68,9 +64,8 @@ export class CommitteeMembersComponent implements OnInit {
public committeeLabel = COMMITTEE_LABEL;

// Computed signals — inline per component-organization.md
public readonly isBoardMember = computed(() => this.personaService.currentPersona() === 'board-member');
public readonly isMaintainer = computed(() => this.personaService.currentPersona() === 'maintainer');
public readonly canManageMembers = computed(() => !this.isBoardMember() && (!!this.committee()?.writer || this.isMaintainer()));
// Permission is solely driven by the API's writer flag
public readonly canManageMembers = computed(() => !!this.committee()?.writer);
// Default to hidden while committee is loading (fail closed for privacy)
public readonly isMembersVisible = computed(() => {
const committee = this.committee();
Expand Down Expand Up @@ -206,9 +201,8 @@ export class CommitteeMembersComponent implements OnInit {
// Refresh members list by re-fetching
this.refreshMembers();
},
error: (err: HttpErrorResponse) => {
error: () => {
this.isDeleting.set(false);
console.error('Failed to delete member:', err);

this.messageService.add({
severity: 'error',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,34 @@ <h3 class="text-base font-medium text-gray-900">Member Visibility</h3>
</div>
</div>

<!-- Join Mode Setting -->
<div class="flex flex-col gap-2">
<h3 class="text-base font-medium text-gray-900">How Can People Join the Group?</h3>
<div class="bg-white border border-gray-200 p-4 rounded-lg">
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
<div class="p-2 rounded-lg bg-green-50 text-green-500">
<i class="fa-light fa-door-open text-sm"></i>
</div>
<div class="flex-1">
<label for="join-mode" class="text-sm font-semibold text-gray-900">Join Mode</label>
<p class="text-xs text-gray-600">Control how new members can join this {{ committeeLabel.toLowerCase() }}</p>
</div>
</div>
<lfx-select
[form]="form()"
control="join_mode"
[options]="joinModeOptions"
placeholder="Select join mode"
styleClass="w-full"
size="small"
id="join-mode"
data-testid="join-mode-select">
</lfx-select>
</div>
</div>
</div>

<!-- Committee Settings Features -->
<div class="flex flex-col gap-4">
<h3 class="text-base font-medium text-gray-900">{{ committeeLabel }} Features</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MessageComponent } from '@components/message/message.component';
import { SelectComponent } from '@components/select/select.component';
import { ToggleComponent } from '@components/toggle/toggle.component';
import { COMMITTEE_LABEL, COMMITTEE_SETTINGS_FEATURES, MEMBER_VISIBILITY_OPTIONS } from '@lfx-one/shared/constants';
import { COMMITTEE_LABEL, COMMITTEE_SETTINGS_FEATURES, JOIN_MODE_OPTIONS, MEMBER_VISIBILITY_OPTIONS } from '@lfx-one/shared/constants';

@Component({
selector: 'lfx-committee-settings',
Expand All @@ -21,4 +21,5 @@ export class CommitteeSettingsComponent {
public readonly features = COMMITTEE_SETTINGS_FEATURES;
public readonly committeeLabel = COMMITTEE_LABEL.singular;
public readonly memberVisibilityOptions = MEMBER_VISIBILITY_OPTIONS;
public readonly joinModeOptions = JOIN_MODE_OPTIONS;
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,13 @@
placeholder="Start date"
[showButtonBar]="true"
appendTo="body"

data-testid="member-form-role-start"></lfx-calendar>
<lfx-calendar
[form]="form()"
control="role_end"
placeholder="End date"
[showButtonBar]="true"
appendTo="body"

data-testid="member-form-role-end"></lfx-calendar>
</div>
@if (form().errors?.['role_start_after_role_end']) {
Expand Down Expand Up @@ -192,15 +190,13 @@
placeholder="Start date"
[showButtonBar]="true"
appendTo="body"

data-testid="member-form-voting-start"></lfx-calendar>
<lfx-calendar
[form]="form()"
control="voting_status_end"
placeholder="End date"
[showButtonBar]="true"
appendTo="body"

data-testid="member-form-voting-end"></lfx-calendar>
</div>
@if (form().errors?.['voting_status_start_after_voting_status_end']) {
Expand Down
Loading