Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -6,171 +6,209 @@
<lfx-card>
<div class="flex justify-between gap-4 mb-8">
<h3 class="text-lg font-medium text-gray-900">{{ committeeLabel.singular }} Members</h3>
<!-- Add Member Button -->
@if (canManageMembers()) {
<lfx-button label="Add Member" icon="fa-light fa-user-plus" size="small" severity="secondary" (onClick)="openAddMemberDialog()"> </lfx-button>
}
<!-- Member Actions -->
<div class="flex gap-2">
@if (canManageMembers()) {
<lfx-button
icon="fa-light fa-user-plus"
label="Add Member"
size="small"
severity="secondary"
styleClass="p-button-outlined"
(onClick)="openAddMemberDialog()"
data-testid="add-member-btn">
</lfx-button>
}
</div>
</div>

<!-- Search and Filter Controls -->
<div class="flex flex-col md:flex-row md:flex-wrap gap-4 mb-6">
<!-- Search Input -->
<div class="flex-1">
<lfx-input-text [form]="filterForm" control="search" placeholder="Search members..." icon="fa-light fa-search" styleClass="w-full" size="small">
</lfx-input-text>
</div>
@if (!isMembersVisible()) {
<div class="text-sm text-gray-500 text-center py-6" data-testid="members-hidden-placeholder">Member list is not publicly visible for this group.</div>
} @else {
<!-- Search and Filter Controls -->
<div class="flex flex-col md:flex-row md:flex-wrap gap-4 mb-6">
<!-- Search Input -->
<div class="flex-1">
<lfx-input-text [form]="filterForm" control="search" placeholder="Search members..." icon="fa-light fa-search" styleClass="w-full" size="small">
</lfx-input-text>
</div>

<!-- Role Filter -->
<div class="w-full flex-1 sm:flex-none md:w-48">
<lfx-select
[form]="filterForm"
control="role"
size="small"
[options]="roleOptions()"
placeholder="Filter by Role"
[showClear]="true"
styleClass="w-full">
</lfx-select>
</div>
<!-- Role Filter -->
<div class="w-full flex-1 sm:flex-none md:w-48">
<lfx-select
[form]="filterForm"
control="role"
size="small"
[options]="roleOptions()"
placeholder="Filter by Role"
[showClear]="true"
styleClass="w-full">
</lfx-select>
</div>

<!-- Voting Status Filter -->
<div class="w-full flex-1 sm:flex-none md:w-48">
<lfx-select
[form]="filterForm"
control="votingStatus"
size="small"
[options]="votingStatusOptions()"
placeholder="Filter by Voting Status"
[showClear]="true"
styleClass="w-full">
</lfx-select>
</div>
<!-- Voting Status Filter -->
<div class="w-full flex-1 sm:flex-none md:w-48">
<lfx-select
[form]="filterForm"
control="votingStatus"
size="small"
[options]="votingStatusOptions()"
placeholder="Filter by Voting Status"
[showClear]="true"
styleClass="w-full">
</lfx-select>
</div>

<!-- Organization Filter -->
<div class="w-full flex-1 sm:flex-none md:w-48">
<lfx-select
[form]="filterForm"
control="organization"
size="small"
[options]="organizationOptions()"
placeholder="Filter by Organization"
[showClear]="true"
styleClass="w-full">
</lfx-select>
<!-- Organization Filter -->
<div class="w-full flex-1 sm:flex-none md:w-48">
<lfx-select
[form]="filterForm"
control="organization"
size="small"
[options]="organizationOptions()"
placeholder="Filter by Organization"
[showClear]="true"
styleClass="w-full">
</lfx-select>
</div>
</div>
</div>

<!-- Table View -->
<lfx-table
[value]="filteredMembers()"
[paginator]="true"
[rows]="10"
[rowsPerPageOptions]="[10, 25, 50]"
[showCurrentPageReport]="true"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} members"
styleClass="p-datatable-sm"
sortField="first_name"
[sortOrder]="1">
<ng-template #header>
<tr>
<th>
<span class="text-sm font-sans text-gray-500">Name</span>
</th>
<th>
<span class="text-sm font-sans text-gray-500">Email</span>
</th>
<th>
<span class="text-sm font-sans text-gray-500">Organization</span>
</th>
@if (committee()?.enable_voting) {
<!-- Table View -->
<lfx-table
[value]="filteredMembers()"
[paginator]="true"
[rows]="10"
[rowsPerPageOptions]="[10, 25, 50]"
[showCurrentPageReport]="true"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} members"
styleClass="p-datatable-sm"
sortField="first_name"
[sortOrder]="1">
<ng-template #header>
<tr>
<th>
<span class="text-sm font-sans text-gray-500">Role</span>
<span class="text-sm font-sans text-gray-500">Name</span>
</th>
<th>
<span class="text-sm font-sans text-gray-500">Voting Status</span>
<span class="text-sm font-sans text-gray-500">Email</span>
</th>
}
<th style="width: 80px">
<div class="flex justify-center">
<span class="text-sm font-sans text-gray-500">Actions</span>
</div>
</th>
</tr>
</ng-template>

<ng-template #body let-member>
<tr>
<td>
<span class="text-sm font-sans text-gray-900 font-medium">
{{ member | fullName | titlecase }}
</span>
</td>
<td>
<span class="text-sm font-sans text-gray-500">{{ member.email || '-' }}</span>
</td>
<td>
@if (member.organization?.website) {
<a [href]="member.organization.website" target="_blank" class="text-primary hover:text-primary-600 text-sm font-sans">
{{ member.organization.name }}
</a>
} @else {
<span class="text-sm font-sans text-gray-500">{{ member.organization?.name || '-' }}</span>
<th>
<span class="text-sm font-sans text-gray-500">Organization</span>
</th>
@if (committee()?.enable_voting) {
<th>
<span class="text-sm font-sans text-gray-500">Role</span>
</th>
<th>
Comment on lines +98 to +102
<span class="text-sm font-sans text-gray-500">Voting Status</span>
</th>
}
Comment on lines +98 to 105
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Render the rest of the new governance fields in the members table.

The form now captures and submits appointed_by plus the role/voting date ranges, but this table still only renders Role and Voting Status. Those values stay invisible after save, so the governance-column part of this PR is still incomplete.

Also applies to: 133-140

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/lfx-one/src/app/modules/committees/components/committee-members/committee-members.component.html`
around lines 98 - 105, The members table only conditionally renders Role and
Voting Status when committee()?.enable_voting is true but does not render the
new governance fields captured by the form (appointed_by and the role/voting
date ranges), so update the committee-members component template to include
table columns and cells for appointed_by, role start/end and voting start/end
(use the existing committee()?.enable_voting condition and the table row
rendering for each member) and bind them to the member properties (e.g.,
member.appointed_by, member.role, member.role_start_date, member.role_end_date,
member.voting_start_date, member.voting_end_date) so saved values are visible;
keep the same styling structure as the existing <th>/<span> and cell rendering
used for Role and Voting Status.

</td>
@if (committee()?.enable_voting) {
<th style="width: 80px">
<div class="flex justify-center">
<span class="text-sm font-sans text-gray-500">Actions</span>
</div>
</th>
</tr>
</ng-template>

<ng-template #body let-member>
<tr>
<td>
<span class="text-sm font-sans text-gray-500">{{ member.role?.name || 'None' }}</span>
<span class="text-sm font-sans text-gray-900 font-medium">
{{ member | fullName | titlecase }}
</span>
</td>
<td>
<span class="text-sm font-sans text-gray-500">{{ member.voting?.status || 'None' }}</span>
<span class="text-sm font-sans text-gray-500">{{ member.email || '-' }}</span>
</td>
}
<td class="text-right relative">
<lfx-menu #memberActionMenu [model]="memberActionMenuItems" [popup]="true" appendTo="body"> </lfx-menu>
@if (canManageMembers()) {
<lfx-button
icon="fa-light fa-ellipsis-vertical"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
(onClick)="toggleMemberActionMenu($event, member, memberActionMenu)">
</lfx-button>
} @else {
<lfx-button
icon="fa-light fa-envelope"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
[href]="`mailto:${member.email}`"
target="_blank"
pTooltip="Send Message">
</lfx-button>
}
</td>
</tr>
</ng-template>

<!-- Empty Message Template -->
<ng-template #emptymessage>
@if (membersLoading()) {
<tr>
<td [attr.colspan]="committee()?.enable_voting ? 6 : 4" class="text-center py-8">
<i class="fa-light fa-circle-notch fa-spin text-4xl text-gray-400"></i>
<td>
@if (member.organization?.website) {
<a [href]="member.organization.website" target="_blank" class="text-primary hover:text-primary-600 text-sm font-sans">
{{ member.organization.name }}
</a>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} @else {
<span class="text-sm font-sans text-gray-500">{{ member.organization?.name || '-' }}</span>
}
</td>
</tr>
} @else {
<tr>
<td [attr.colspan]="committee()?.enable_voting ? 6 : 4" class="text-center py-8">
<div class="text-center">
<i class="fa-light fa-eyes text-3xl text-gray-400 mb-2"></i>
<p class="text-sm text-gray-500">No members found</p>
</div>
@if (committee()?.enable_voting) {
<td>
<span class="text-sm font-sans text-gray-500">{{ member.role?.name || 'None' }}</span>
</td>
<td>
<span class="text-sm font-sans text-gray-500">{{ member.voting?.status || 'None' }}</span>
</td>
}
<td class="text-right relative">
<lfx-menu #memberActionMenu [model]="memberActionMenuItems" [popup]="true" appendTo="body"> </lfx-menu>
@if (canManageMembers()) {
<lfx-button
icon="fa-light fa-ellipsis-vertical"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
(onClick)="toggleMemberActionMenu($event, member, memberActionMenu)">
</lfx-button>
} @else {
<lfx-button
icon="fa-light fa-envelope"
[text]="true"
[rounded]="true"
size="small"
severity="secondary"
[href]="`mailto:${member.email}`"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Blocking] Template literal in Angular binding — will fail at runtime

[href]="`mailto:${member.email}`"

Angular template expressions don't support JavaScript template literals (backtick strings). This will throw a template parse error at runtime.

Fix:

[href]="'mailto:' + member.email"

target="_blank"
pTooltip="Send Message">
</lfx-button>
}
</td>
</tr>
}
</ng-template>
</lfx-table>
</ng-template>

<!-- Empty Message Template -->
<ng-template #emptymessage>
@if (membersLoading()) {
<tr>
<td [attr.colspan]="committee()?.enable_voting ? 6 : 4" class="text-center py-8">
<!-- Loading skeleton rows -->
<div class="flex flex-col gap-3 animate-pulse px-4">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom skeleton instead of PrimeNG <p-skeleton>.

Problem: The loading state uses hand-rolled skeleton markup with animate-pulse and bg-gray-200 divs.

Why it's a problem: The rest of the codebase (route-loading.component, metric-card.component) uses <p-skeleton> from primeng/skeleton for loading states. Custom skeletons create visual inconsistency (different animation timing, color, border-radius) and bypass theming provided by PrimeNG.

Fix: Replace the custom animate-pulse / bg-gray-200 div block with <p-skeleton> elements matching the table row layout. Import SkeletonModule from primeng/skeleton in the component's imports array.

@for (_ of [1, 2, 3]; track _) {
<div class="flex items-center gap-4">
<div class="w-8 h-8 rounded-full bg-gray-200"></div>
<div class="h-4 bg-gray-200 rounded w-32"></div>
<div class="h-4 bg-gray-200 rounded w-40"></div>
<div class="h-4 bg-gray-200 rounded w-24"></div>
</div>
}
</div>
</td>
</tr>
} @else {
<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>
}
@if (canManageMembers()) {
<div class="mt-3">
<button class="text-xs text-blue-600 hover:text-blue-700 font-medium" (click)="openAddMemberDialog()">
<i class="fa-light fa-user-plus mr-1"></i>Add the first member
</button>
</div>
}
</div>
</td>
</tr>
}
</ng-template>
</lfx-table>
}
</lfx-card>
</div>
Loading
Loading