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
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,5 @@ lfx-pcc-v3/
- All commits and pull requests need to be associated to a JIRA ticket. If there isn't one, we need to create it and reference it moving forward.
- Branch names should be following the commit types (feat,fix,docs, etc) followed by the JIRA ticket number. i.e; feat/LFXV2-123 or ci/LFXV2-456
- PR titles must also follow a similar format as conventional commits - `type(scope): description`. The scope has to follow the angular config for conventional commit and not include the JIRA ticket in the title, and everything should be in lowercase.

- All interfaces, resuable constants, and enums should live in the shared package.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class CommitteeDashboardComponent {

// Statistics calculations
public totalCommittees: Signal<number> = computed(() => this.committees().length);
public publicCommittees: Signal<number> = computed(() => this.committees().filter((c) => c.public_enabled).length);
public publicCommittees: Signal<number> = computed(() => this.committees().filter((c) => c.public).length);
public activeVoting: Signal<number> = computed(() => this.committees().filter((c) => c.enable_voting).length);

public constructor() {
Expand Down Expand Up @@ -162,7 +162,7 @@ export class CommitteeDashboardComponent {
const committee = this.selectedCommittee();
const projectId = this.project()?.uid;
if (committee && projectId) {
this.router.navigate(['/project', this.project()?.slug, 'committees', committee.id]);
this.router.navigate(['/project', this.project()?.slug, 'committees', committee.uid]);
}
}

Expand All @@ -188,7 +188,7 @@ export class CommitteeDashboardComponent {
private performDelete(committee: Committee): void {
this.isDeleting.set(true);

this.committeeService.deleteCommittee(committee.id).subscribe({
this.committeeService.deleteCommittee(committee.uid).subscribe({
next: () => {
this.isDeleting.set(false);
// Refresh the committees list by reloading
Expand Down Expand Up @@ -219,7 +219,7 @@ export class CommitteeDashboardComponent {
data: {
isEditing: true,
committee: committee,
committeeId: committee.id,
committeeId: committee.uid,
onCancel: () => this.dialogRef?.close(),
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ <h2 class="text-xl font-semibold text-gray-900 mb-2">Committee Not Found</h2>
}

<!-- Committee Content -->
@if (committee()?.id && !loading() && !error()) {
@if (committee()?.uid && !loading() && !error()) {
<div class="flex flex-col gap-6">
<!-- Header Section -->
<div class="flex justify-between items-start">
Expand Down Expand Up @@ -61,7 +61,7 @@ <h1 class="text-3xl font-bold text-gray-900 mb-2">{{ committee()?.name }}</h1>
<!-- Left Column - Committee Details -->
<div class="lg:col-span-2 flex flex-col gap-6">
<!-- Members Section -->
@if (committee()?.id) {
@if (committee()?.uid) {
<lfx-committee-members
[committee]="committee()"
[members]="members()"
Expand All @@ -74,8 +74,8 @@ <h1 class="text-3xl font-bold text-gray-900 mb-2">{{ committee()?.name }}</h1>
<div class="flex flex-col gap-6">
<!-- Upcoming Meeting -->
<lfx-card title="Next Meeting">
@if (committee()?.id) {
<lfx-upcoming-committee-meeting [committeeId]="committee()!.id"></lfx-upcoming-committee-meeting>
@if (committee()?.uid) {
<lfx-upcoming-committee-meeting [committeeId]="committee()!.uid"></lfx-upcoming-committee-meeting>
}
</lfx-card>

Expand All @@ -99,19 +99,19 @@ <h4 class="font-medium text-gray-700 mb-2">Description</h4>
<span class="font-semibold text-gray-900">{{ committee()?.total_voting_reps || 0 }}</span>
</div>

@if (committee()?.committee_website) {
@if (committee()?.website) {
<div class="flex justify-between items-center py-2 border-t border-gray-100">
<span class="text-gray-600">Website</span>
<a [href]="committee()?.committee_website" target="_blank" class="text-blue-600 hover:text-blue-800 underline text-sm">
{{ committee()?.committee_website }}
<a [href]="committee()?.website" target="_blank" class="text-blue-600 hover:text-blue-800 underline text-sm" rel="noopener noreferrer">
{{ committee()?.website }}
</a>
</div>
}

@if (committee()?.public_name) {
@if (committee()?.display_name) {
<div class="flex justify-between items-center py-2 border-t border-gray-100">
<span class="text-gray-600">Public Name</span>
<span class="text-gray-900">{{ committee()?.public_name }}</span>
<span class="text-gray-900">{{ committee()?.display_name }}</span>
</div>
}

Expand All @@ -132,12 +132,12 @@ <h4 class="font-medium text-gray-700 mb-2">Description</h4>
<span class="inline-flex items-center">
<i
class="fa-light text-sm mr-1"
[class.fa-check-circle]="committee()?.public_enabled"
[class.text-green-500]="committee()?.public_enabled"
[class.fa-times-circle]="!committee()?.public_enabled"
[class.text-red-500]="!committee()?.public_enabled"></i>
<span class="text-sm" [class.text-green-600]="committee()?.public_enabled" [class.text-red-600]="!committee()?.public_enabled">
{{ committee()?.public_enabled ? 'Enabled' : 'Disabled' }}
[class.fa-check-circle]="committee()?.public"
[class.text-green-500]="committee()?.public"
[class.fa-times-circle]="!committee()?.public"
[class.text-red-500]="!committee()?.public"></i>
<span class="text-sm" [class.text-green-600]="committee()?.public" [class.text-red-600]="!committee()?.public">
{{ committee()?.public ? 'Enabled' : 'Disabled' }}
</span>
</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export class CommitteeViewComponent {
private performDelete(committee: Committee): void {
this.isDeleting.set(true);

this.committeeService.deleteCommittee(committee.id).subscribe({
this.committeeService.deleteCommittee(committee.uid).subscribe({
next: () => {
this.isDeleting.set(false);
this.goBack();
Expand All @@ -138,7 +138,7 @@ export class CommitteeViewComponent {
data: {
isEditing: true,
committee: committee,
committeeId: committee.id,
committeeId: committee.uid,
onCancel: () => this.dialogRef?.close(),
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,19 @@ <h3 class="text-lg font-medium text-gray-900">Public Settings</h3>
<lfx-toggle
size="small"
[form]="form()"
control="public_enabled"
control="public"
label="Make Committee Public"
id="public-toggle"
tooltip="When enabled, the committee will be visible to the public on LFX tools like the public meeting calendar"
tooltipPosition="right"></lfx-toggle>

<!-- Conditional Public Name Field -->
<div *ngIf="form().get('public_enabled')?.value === true">
<div *ngIf="form().get('public')?.value === true">
<label for="public-name" class="block text-sm font-medium text-gray-700 mb-1"> Public Name </label>
<lfx-input-text
size="small"
[form]="form()"
control="public_name"
control="display_name"
id="public-name"
placeholder="Enter public display name"
styleClass="w-full"></lfx-input-text>
Expand Down Expand Up @@ -156,14 +156,12 @@ <h3 class="text-lg font-medium text-gray-900">Additional Information</h3>
<lfx-input-text
size="small"
[form]="form()"
control="committee_website"
control="website"
id="committee-website"
type="url"
placeholder="https://example.com"
styleClass="w-full"></lfx-input-text>
<p class="mt-1 text-sm text-red-600" *ngIf="form().get('committee_website')?.errors?.['pattern'] && form().get('committee_website')?.touched">
Please enter a valid URL
</p>
<p class="mt-1 text-sm text-red-600" *ngIf="form().get('website')?.errors?.['pattern'] && form().get('website')?.touched">Please enter a valid URL</p>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,16 @@ export class CommitteeFormComponent {

if (this.form().valid) {
const isEditingMode = this.isEditing();
const formValue = this.form().value;
const rawFormValue = {
...this.form().value,
calendar: {
public: this.form().value.public || false,
},
display_name: this.form().value.display_name || this.form().value.name,
website: this.form().value.website || null,
public: true,
};
const formValue = this.cleanFormData(rawFormValue);
const committeeId = this.committeeId();

this.submitting.set(true);
Expand Down Expand Up @@ -132,6 +141,23 @@ export class CommitteeFormComponent {
});
}

// Helper method to clean form data - convert empty strings to null
private cleanFormData(formData: any): any {
const cleaned: any = {};

Object.keys(formData).forEach((key) => {
const value = formData[key];
// Convert empty strings to null for optional string fields
if (typeof value === 'string' && value.trim() === '') {
cleaned[key] = null;
} else {
cleaned[key] = value;
}
});

return cleaned;
}

// Success handler
private onSuccess(): void {
const isEditing = this.isEditing();
Expand Down Expand Up @@ -190,12 +216,12 @@ export class CommitteeFormComponent {

// If editing, exclude the current committee
const currentCommitteeId = this.committee()?.id;
const availableCommittees = currentCommitteeId ? topLevelCommittees.filter((committee) => committee.id !== currentCommitteeId) : topLevelCommittees;
const availableCommittees = currentCommitteeId ? topLevelCommittees.filter((committee) => committee.uid !== currentCommitteeId) : topLevelCommittees;

// Transform to dropdown options
const options = availableCommittees.map((committee) => ({
label: committee.name,
value: committee.id,
value: committee.uid,
}));

// Add "No Parent Committee" option at the beginning
Expand All @@ -215,11 +241,11 @@ export class CommitteeFormComponent {
business_email_required: new FormControl(committee?.business_email_required || false),
enable_voting: new FormControl(committee?.enable_voting || false),
is_audit_enabled: new FormControl(committee?.is_audit_enabled || false),
public_enabled: new FormControl(committee?.public_enabled || false),
public_name: new FormControl(committee?.public_name || ''),
public: new FormControl(committee?.public || false),
display_name: new FormControl(committee?.display_name || ''),
sso_group_enabled: new FormControl(committee?.sso_group_enabled || false),
sso_group_name: new FormControl(committee?.sso_group_name || ''),
committee_website: new FormControl(committee?.committee_website || '', [Validators.pattern(/^https?:\/\/.+\..+/)]),
website: new FormControl(committee?.website || '', [Validators.pattern(/^https?:\/\/.+\..+/)]),
project_uid: new FormControl(committee?.project_uid || ''),
joinable: new FormControl(committee?.joinable || false),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export class CommitteeMembersComponent implements OnInit {

private performDelete(member: CommitteeMember): void {
const committee = this.committee();
if (!committee || !committee.id) {
if (!committee || !committee.uid) {
this.messageService.add({
severity: 'error',
summary: 'Error',
Expand All @@ -207,7 +207,7 @@ export class CommitteeMembersComponent implements OnInit {

this.isDeleting.set(true);

this.committeeService.deleteCommitteeMember(committee.id, member.id).subscribe({
this.committeeService.deleteCommitteeMember(committee.uid, member.id).subscribe({
next: () => {
this.isDeleting.set(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

<!-- Body template -->
<ng-template #body let-committee>
<tr class="border-b border-gray-50 hover:bg-gray-50/50 transition-colors" [attr.data-testid]="'committee-table-row-' + committee.id">
<tr class="border-b border-gray-50 hover:bg-gray-50/50 transition-colors" [attr.data-testid]="'committee-table-row-' + committee.uid">
<!-- Name column with hierarchy indicator -->
<td class="px-4 py-3">
<div class="flex items-center gap-2">
Expand All @@ -47,7 +47,7 @@
}
<div>
<a
[routerLink]="['/project', project()?.slug, 'committees', committee.id]"
[routerLink]="['/project', project()?.slug, 'committees', committee.uid]"
class="text-sm font-medium text-gray-900 hover:text-blue-600 transition-colors">
{{ committee.name }}
</a>
Expand Down Expand Up @@ -121,7 +121,7 @@
severity="secondary"
styleClass="h-8 w-8 p-0 text-gray-500 hover:text-gray-700"
(onClick)="onEdit(committee)"
[attr.data-testid]="'committee-edit-' + committee.id" />
[attr.data-testid]="'committee-edit-' + committee.uid" />
<lfx-button
icon="fa-light fa-users"
[text]="true"
Expand All @@ -130,7 +130,7 @@
severity="secondary"
styleClass="h-8 w-8 p-0 text-gray-500 hover:text-gray-700"
(onClick)="onView(committee)"
[attr.data-testid]="'committee-members-' + committee.id" />
[attr.data-testid]="'committee-members-' + committee.uid" />
<lfx-button
icon="fa-light fa-ellipsis"
[text]="true"
Expand All @@ -139,7 +139,7 @@
severity="secondary"
styleClass="h-8 w-8 p-0 text-gray-500 hover:text-gray-700"
(onClick)="toggleCommitteeActionMenu($event, committee, actionMenu)"
[attr.data-testid]="'committee-more-' + committee.id" />
[attr.data-testid]="'committee-more-' + committee.uid" />
</div>
</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ export class CommitteeTableComponent {
result.push(parent);

// Then add any subcommittees for this parent
const subcommittees = allCommittees.filter((c) => c.parent_uid === parent.id);
const subcommittees = allCommittees.filter((c) => c.parent_uid === parent.uid);
subcommittees.forEach((sub) => {
result.push({ ...sub, level: 1 });
});
});

// Add any orphaned committees (shouldn't happen, but just in case)
const orphaned = allCommittees.filter((c) => c.parent_uid && !allCommittees.find((p) => p.id === c.parent_uid));
const orphaned = allCommittees.filter((c) => c.parent_uid && !allCommittees.find((p) => p.uid === c.parent_uid));
result.push(...orphaned);

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,9 @@ export class ProjectComponent {
}

return committees.map((committee) => ({
id: committee.id,
id: committee.uid,
title: committee.name,
url: `/project/${project.slug}/committees/${committee.id}`,
url: `/project/${project.slug}/committees/${committee.uid}`,
status: 'Active',
date: committee.updated_at || committee.created_at,
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,9 @@ <h3 class="text-xl font-display font-semibold text-gray-900 mb-1 leading-tight"
<i class="fa-light fa-people-group text-gray-400 mt-0.5"></i>
<div class="flex flex-wrap gap-1">
<div class="text-sm text-gray-600">
@for (committee of meeting().meeting_committees!; track committee.id; let isLast = $last) {
@for (committee of meeting().meeting_committees!; track committee.uid; let isLast = $last) {
<a
[routerLink]="['/project', project()?.slug, 'committees', committee.id]"
[routerLink]="['/project', project()?.slug, 'committees', committee.uid]"
class="text-primary hover:text-primary-600 hover:underline"
[title]="committee.committee_total_members + ' members'">
{{ committee.name }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class MeetingCommitteeModalComponent {
public hasVotingEnabledCommittee = computed(() => {
const selectedIds = this.selectedCommitteeIds();
const committees = this.committees();
return committees.some((c) => selectedIds.includes(c.id) && c.enable_voting);
return committees.some((c) => selectedIds.includes(c.uid) && c.enable_voting);
});

public tableColspan = computed(() => {
Expand All @@ -100,7 +100,7 @@ export class MeetingCommitteeModalComponent {

// Set initial selected committees
if (this.meeting.meeting_committees && this.meeting.meeting_committees.length > 0) {
const committeeIds = this.meeting.meeting_committees.map((c) => c.id);
const committeeIds = this.meeting.meeting_committees.map((c) => c.uid);
this.selectedCommitteeIds.set(committeeIds);
this.form.patchValue({ committees: committeeIds });
// Load members for initially selected committees
Expand All @@ -125,7 +125,7 @@ export class MeetingCommitteeModalComponent {
const selectedIds = this.form.value.committees as string[];

// If no changes, just close
const currentIds = this.meeting.meeting_committees?.map((c) => c.id) || [];
const currentIds = this.meeting.meeting_committees?.map((c) => c.uid) || [];
if (JSON.stringify(selectedIds.sort()) === JSON.stringify(currentIds.sort())) {
this.ref.close();
return;
Expand Down Expand Up @@ -201,7 +201,7 @@ export class MeetingCommitteeModalComponent {

// Load members for committees that haven't been loaded yet
const memberRequests = committeesToLoad.map((id) => {
const committee = this.committees().find((c) => c.id === id);
const committee = this.committees().find((c) => c.uid === id);
return this.committeeService.getCommitteeMembers(id).pipe(
map((members) => {
const membersWithCommittee = members.map((member) => ({
Expand Down Expand Up @@ -237,7 +237,7 @@ export class MeetingCommitteeModalComponent {

for (const committeeId of committeeIds) {
const cachedMembers = this.committeesMembersCache.get(committeeId) || [];
const committee = this.committees().find((c) => c.id === committeeId);
const committee = this.committees().find((c) => c.uid === committeeId);
const committeeName = committee?.name || '';

for (const member of cachedMembers) {
Expand Down
Loading
Loading