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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { ButtonComponent } from '@components/button/button.component';
import { CardComponent } from '@components/card/card.component';
import { ExpandableTextComponent } from '@components/expandable-text/expandable-text.component';
import { InputTextComponent } from '@components/input-text/input-text.component';
import { MeetingRegistrantsComponent } from '@components/meeting-registrants/meeting-registrants.component';
import { MeetingRsvpDetailsComponent } from '@components/meeting-rsvp-details/meeting-rsvp-details.component';
import { RsvpButtonGroupComponent } from '@components/rsvp-button-group/rsvp-button-group.component';
import { environment } from '@environments/environment';
import {
Expand Down Expand Up @@ -46,6 +48,8 @@ import { catchError, combineLatest, debounceTime, filter, map, Observable, of, s
CardComponent,
InputTextComponent,
RsvpButtonGroupComponent,
MeetingRsvpDetailsComponent,
MeetingRegistrantsComponent,
ToastModule,
TooltipModule,
MeetingTimePipe,
Expand Down Expand Up @@ -86,9 +90,10 @@ export class MeetingJoinComponent {
public messageIcon: Signal<string>;
public alertMessage: Signal<string>;
private hasAutoJoined: WritableSignal<boolean> = signal<boolean>(false);
public showRegistrants: WritableSignal<boolean> = signal<boolean>(false);

// Form value signals for reactivity
private formValues: Signal<{ name: string; email: string; organization: string }>;
public formValues: Signal<{ name: string; email: string; organization: string }>;

public constructor() {
// Initialize all class variables
Expand Down Expand Up @@ -120,6 +125,10 @@ export class MeetingJoinComponent {
});
}

public onRegistrantsToggle(): void {
this.showRegistrants.set(!this.showRegistrants());
}

private initializeAutoJoin(): void {
// Use toObservable to create an Observable from the signals, then subscribe once
// This executes only when all conditions are met
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
data-testid="share-meeting-button"
tooltip="Share Meeting"></lfx-button>
}
@if (project()?.slug && meeting().organizer) {
@if (meeting().project_slug && meeting().organizer) {
<lfx-button
icon="fa-light fa-edit"
[text]="true"
Expand All @@ -90,7 +90,7 @@
severity="secondary"
data-testid="edit-meeting-button"
tooltip="Edit Meeting"
[routerLink]="['/project', project()!.slug, 'meetings', meeting().uid, 'edit']"></lfx-button>
[routerLink]="['/project', meeting().project_slug, 'meetings', meeting().uid, 'edit']"></lfx-button>

<lfx-button
[icon]="loading() ? 'fa-light fa-circle-notch fa-spin' : 'fa-light fa-ellipsis'"
Expand Down Expand Up @@ -243,13 +243,6 @@ <h3 class="text-base font-medium text-gray-900 leading-tight tracking-tight" dat
</div>
}

<!-- RSVP Section - Show for authenticated users on upcoming meetings -->
@if (!pastMeeting() && authenticated() && !meeting().organizer) {
<div class="mt-3.5">
<lfx-rsvp-button-group [meeting]="meeting()" [occurrenceId]="occurrence()?.occurrence_id" [showHeader]="false"> </lfx-rsvp-button-group>
</div>
}

<!-- Past Meeting Buttons -->
@if (pastMeeting()) {
<div class="flex gap-2 mt-3.5">
Expand Down Expand Up @@ -281,102 +274,35 @@ <h3 class="text-base font-medium text-gray-900 leading-tight tracking-tight" dat

<!-- People/Attendees Card Column -->
<div class="flex flex-col">
<div
class="rounded-md p-3 space-y-2 flex-1"
[ngClass]="pastMeeting() && attendancePercentage() < 50 ? 'bg-amber-50 border border-amber-200' : 'bg-gray-100/60'"
data-testid="attendees-card">
<div class="flex items-center justify-between gap-2">
<div
class="flex items-center gap-2 text-xs tracking-wide"
[ngClass]="pastMeeting() && attendancePercentage() < 50 ? 'text-amber-600' : 'text-gray-600'">
<i class="fa-light fa-users text-xs"></i>
<span>{{ pastMeeting() ? 'Attendees' : 'People Invited' }}</span>
</div>
@if (!pastMeeting()) {
<lfx-button
icon="fa-light fa-user-plus text-xs"
label="Add"
size="small"
severity="secondary"
data-testid="add-participant-button"
(click)="openAddRegistrantModal()">
</lfx-button>
}
</div>

<div class="space-y-2">
<div class="flex items-center justify-between">
<span
class="text-[12.25px] leading-tight tracking-tight"
[ngClass]="pastMeeting() && attendancePercentage() < 50 ? 'text-amber-900' : 'text-gray-900'">
@if (pastMeeting()) {
{{ attendedCount() }} of {{ meetingRegistrantCount() }} attended
} @else {
{{ meeting().registrants_accepted_count || 0 }} of {{ meetingRegistrantCount() }} attending
}
</span>
<span class="text-gray-600 text-xs tracking-wide">{{ attendancePercentage() }}%</span>
</div>

<!-- Progress Bar -->
<div class="w-full bg-gray-200/80 rounded-full h-[5.25px] flex overflow-hidden">
@if (meeting().registrants_accepted_count && meeting().registrants_accepted_count > 0) {
<div
class="h-[5.25px] transition-all duration-300 bg-blue-500"
[style.width.%]="(meeting().registrants_accepted_count! / meetingRegistrantCount()) * 100"></div>
}
@if (!pastMeeting() && meeting().registrants_pending_count && meeting().registrants_pending_count > 0) {
<div
class="h-[5.25px] transition-all duration-300 bg-amber-500"
[style.width.%]="(meeting().registrants_pending_count! / meetingRegistrantCount()) * 100"></div>
}
@if (!pastMeeting() && meeting().registrants_declined_count && meeting().registrants_declined_count > 0) {
<div
class="h-[5.25px] transition-all duration-300 bg-red-500"
[style.width.%]="(meeting().registrants_declined_count! / meetingRegistrantCount()) * 100"></div>
}
</div>

<!-- Response Breakdown -->
@if (!pastMeeting()) {
<div class="flex items-center justify-between text-gray-600">
<div class="flex items-center gap-1">
<i class="flex fa-light fa-check-circle text-xs text-blue-500"></i>
<span class="text-xs tracking-wide">{{ meeting().registrants_accepted_count || 0 }} Yes</span>
</div>
<div class="flex items-center gap-1">
<i class="flex fa-light fa-question-circle text-xs text-amber-500"></i>
<span class="text-xs tracking-wide">{{ meeting().registrants_pending_count || 0 }} Maybe</span>
</div>
<div class="flex items-center gap-1">
<i class="flex fa-light fa-times-circle text-xs text-red-500"></i>
<span class="text-xs tracking-wide">{{ meeting().registrants_declined_count || 0 }} No</span>
</div>
</div>
}

<!-- Poor Attendance Warning -->
@if (pastMeeting() && attendancePercentage() < 50) {
<div class="flex items-center justify-center gap-2 mt-2 pt-2">
<i class="fa-light fa-triangle-exclamation w-3.5 h-3.5 text-amber-600 flex-shrink-0"></i>
<p class="text-xs tracking-wide text-amber-900">This meeting was poorly attended</p>
</div>
}
</div>
</div>
@if (meeting().organizer) {
<!-- Show RSVP Details for organizers and past meetings -->
<lfx-meeting-rsvp-details
[meeting]="meeting()"
[currentOccurrence]="currentOccurrence()"
[pastMeeting]="pastMeeting()"
[showAddModal]="!pastMeeting()"
[additionalRegistrantsCount]="additionalRegistrantsCount()"
(addParticipant)="openAddRegistrantModal()">
</lfx-meeting-rsvp-details>
} @else {
<!-- Show RSVP Selection for authenticated non-organizers -->
<lfx-rsvp-button-group [meeting]="meeting()" [occurrenceId]="occurrence()?.occurrence_id"> </lfx-rsvp-button-group>
}

<!-- Action Buttons for People Column -->
<div class="flex gap-2 mt-3.5">
<lfx-button
class="w-full"
icon="fa-light fa-users"
[label]="meetingRegistrantCount() > 0 ? 'View Guests' : 'Add Guests'"
size="small"
severity="secondary"
styleClass="w-full"
data-testid="view-guests-button"
(click)="onRegistrantsToggle()">
</lfx-button>
@if (meeting().organizer) {
<lfx-button
class="w-full"
icon="fa-light fa-users"
[label]="meetingRegistrantCount() > 0 ? 'View Guests' : 'Add Guest'"
size="small"
severity="secondary"
styleClass="w-full"
data-testid="view-guests-button"
(click)="onRegistrantsToggle()">
</lfx-button>
}
<lfx-button
class="w-full"
icon="fa-light fa-arrow-up-right-from-square"
Expand All @@ -393,85 +319,13 @@ <h3 class="text-base font-medium text-gray-900 leading-tight tracking-tight" dat
</div>

<!-- Meeting Registrants -->
@if (showRegistrants()) {
<div class="border-t border-gray-200 pt-4 mt-4" data-testid="registrants-section">
@if (registrantsLoading()) {
<div class="flex items-center justify-center py-8">
<i class="fa-light fa-circle-notch fa-spin text-2xl text-blue-600"></i>
</div>
}

@if (!registrantsLoading()) {
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
@if (!pastMeeting()) {
<!-- Add Registrant Option -->
<div
class="flex items-center gap-2 cursor-pointer hover:bg-blue-50 p-2 rounded-md transition-colors border-2 border-dashed border-blue-200 hover:border-blue-400"
(click)="openAddRegistrantModal()"
data-testid="add-registrant-button">
<div class="w-8 h-8 bg-blue-100 border border-blue-200 rounded-full flex items-center justify-center">
<i class="fa-light fa-plus text-blue-500"></i>
</div>
<div class="flex flex-col min-w-0 flex-1">
<span class="text-sm text-blue-600 font-medium">Add Guests</span>
<span class="text-xs text-blue-500">Click to add new guests</span>
</div>
</div>
}

@if (pastMeeting()) {
@for (participant of pastMeetingParticipants(); track participant.uid) {
<div class="flex items-center gap-2 p-2 rounded-md" [attr.data-testid]="'participant-' + participant.uid">
@if (participant.is_attended) {
<i class="fa-light fa-check-circle text-green-500" pTooltip="Attended"></i>
} @else {
<i class="fa-light fa-times-circle text-red-500" pTooltip="Did Not Attend"></i>
}
<div class="flex flex-col min-w-0 flex-1">
<div class="flex items-center gap-1 text-xs">
<i class="fa-light fa-user text-blue-500" pTooltip="Participant"></i>
<span class="text-sm text-gray-900 truncate">{{ participant.first_name }} {{ participant.last_name }}</span>
</div>
<span class="text-xs text-gray-500 truncate">{{ participant.email }}</span>
</div>
</div>
}
} @else {
@for (registrant of registrants(); track registrant.uid) {
<div
class="flex items-center gap-2 cursor-pointer hover:bg-gray-50 p-2 rounded-md transition-colors"
(click)="onRegistrantEdit(registrant, $event)"
[attr.data-testid]="'registrant-' + registrant.uid">
<div class="relative">
<lfx-avatar [label]="registrant.first_name" styleClass="bg-blue-50 border border-gray-200 w-8 h-8" shape="circle"></lfx-avatar>
<div class="absolute -top-1 -right-1">
@if (registrant.invite_accepted === false) {
<i class="fa-solid text-xs fa-times-circle text-red-500" pTooltip="Declined Invite"></i>
} @else if (!registrant.invite_accepted) {
<i class="fa-solid text-xs fa-circle-question text-amber-400" pTooltip="Pending Response"></i>
} @else {
<i class="fa-solid text-xs fa-check-circle text-green-500" pTooltip="Invite Accepted"></i>
}
</div>
</div>
<div class="flex flex-col min-w-0 flex-1">
<div class="flex items-center gap-1 text-xs">
@if (registrant.type === 'committee') {
<i class="fa-light fa-people-group text-green-500" pTooltip="Committee"></i>
} @else {
<i class="fa-light fa-user text-blue-500" pTooltip="Individual"></i>
}
<span class="text-sm text-gray-900 truncate">{{ registrant.first_name }} {{ registrant.last_name }}</span>
</div>
<span class="text-xs text-gray-500 truncate">{{ registrant.email }}</span>
</div>
</div>
}
}
</div>
}
</div>
}
<lfx-meeting-registrants
[meeting]="meeting()"
[pastMeeting]="pastMeeting()"
[visible]="showRegistrants()"
[showAddRegistrant]="!pastMeeting()"
(registrantsCountChange)="additionalRegistrantsCount.set($event)">
</lfx-meeting-registrants>

<!-- Confirmation Dialog -->
<p-confirmDialog></p-confirmDialog>
Expand Down
Loading