Skip to content

Commit ba409b9

Browse files
authored
feat(meetings): comprehensive meeting management improvements (#175)
* feat(meetings): implement comprehensive meeting management improvements - Unified edit and create flows with step-based stepper approach - Added mobile sidebar with PrimeNG drawer and hamburger menu - Reorganized dashboard filters with meeting type filter in top nav - Redesigned RSVP buttons with improved visual states - Added direct step navigation via query parameters - Implemented automatic scroll to top on route navigation - Client-side sorting for past meetings by scheduled start time LFXV2-788, LFXV2-792, LFXV2-793, LFXV2-794, LFXV2-795, LFXV2-796, LFXV2-797 Signed-off-by: Asitha de Silva <[email protected]> * fix(ui): restore encapsulation and fix memory leaks - Restore private signal with readonly accessor in AppService - Use one-way binding with visibleChange event for p-drawer - Add takeUntilDestroyed to prevent subscription memory leaks - Add form validation to onSubmitAll method - Allow users to change RSVP response by removing disabled states LFXV2-792, LFXV2-793, LFXV2-794, LFXV2-795 Signed-off-by: Asitha de Silva <[email protected]> --------- Signed-off-by: Asitha de Silva <[email protected]>
1 parent 72623b7 commit ba409b9

27 files changed

+525
-417
lines changed

apps/lfx-one/src/app/app.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/
55
import { ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core';
66
import { provideClientHydration, withEventReplay, withHttpTransferCacheOptions, withIncrementalHydration } from '@angular/platform-browser';
77
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
8-
import { provideRouter, withPreloading } from '@angular/router';
8+
import { provideRouter, withInMemoryScrolling, withPreloading } from '@angular/router';
99
import { lfxPreset } from '@linuxfoundation/lfx-ui-core';
1010
import { definePreset } from '@primeng/themes';
1111
import Aura from '@primeng/themes/aura';
@@ -29,7 +29,7 @@ const customPreset = definePreset(Aura, {
2929
export const appConfig: ApplicationConfig = {
3030
providers: [
3131
provideExperimentalZonelessChangeDetection(),
32-
provideRouter(routes, withPreloading(CustomPreloadingStrategy)),
32+
provideRouter(routes, withPreloading(CustomPreloadingStrategy), withInMemoryScrolling({ scrollPositionRestoration: 'top' })),
3333
provideClientHydration(withEventReplay(), withIncrementalHydration(), withHttpTransferCacheOptions({ includeHeaders: ['Authorization'] })),
3434
provideHttpClient(withFetch(), withInterceptors([authenticationInterceptor])),
3535
provideAnimationsAsync(),

apps/lfx-one/src/app/layouts/main-layout/main-layout.component.html

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,37 @@
77
<lfx-sidebar [items]="sidebarItems()" [footerItems]="sidebarFooterItems()" [showProjectSelector]="true"></lfx-sidebar>
88
</div>
99

10-
<!-- Sidebar - Mobile Overlay -->
11-
@if (showMobileSidebar()) {
12-
<div class="lg:hidden fixed inset-0 z-40 bg-black bg-opacity-50" data-testid="mobile-sidebar-overlay" (click)="closeMobileSidebar()">
13-
<div class="absolute top-0 left-0 bottom-0 w-64 bg-white shadow-xl" (click)="$event.stopPropagation()" data-testid="mobile-sidebar">
14-
<div class="flex items-center justify-between p-4 border-b border-gray-200">
15-
<h2>Menu</h2>
16-
<button
17-
type="button"
18-
class="hover:opacity-80 transition-opacity p-2"
19-
(click)="closeMobileSidebar()"
20-
aria-label="Close menu"
21-
data-testid="mobile-sidebar-close">
22-
<i class="fa-light fa-times text-gray-600 text-xl"></i>
23-
</button>
24-
</div>
25-
<div class="overflow-y-auto h-[calc(100vh-4rem)]">
26-
<lfx-sidebar [items]="sidebarItems()" [footerItems]="sidebarFooterItems()" [showProjectSelector]="true"></lfx-sidebar>
27-
</div>
10+
<!-- Sidebar - Mobile Drawer -->
11+
<p-drawer
12+
[visible]="showMobileSidebar()"
13+
(visibleChange)="onDrawerVisibilityChange($event)"
14+
position="left"
15+
[modal]="true"
16+
[dismissable]="true"
17+
[showCloseIcon]="true"
18+
styleClass="lg:hidden w-86 mobile-sidebar-drawer"
19+
data-testid="mobile-sidebar-drawer">
20+
<ng-template #header>
21+
<div class="flex items-center gap-3">
22+
<img src="/images/lfx-one-logo.svg" alt="LFX Logo" class="w-auto h-5" />
2823
</div>
29-
</div>
30-
}
24+
</ng-template>
25+
<lfx-sidebar [items]="sidebarItems()" [footerItems]="sidebarFooterItems()" [showProjectSelector]="true" [mobile]="true"></lfx-sidebar>
26+
</p-drawer>
3127

3228
<!-- Main Content Area -->
3329
<main class="flex-1 min-w-0 transition-all duration-300 lg:ml-64 flex flex-col" data-testid="main-content">
30+
<!-- Mobile Menu Button -->
31+
<div class="lg:hidden sticky top-0 z-30 bg-white border-b border-gray-200 px-5 py-3">
32+
<button
33+
type="button"
34+
class="flex items-center justify-center w-10 h-10 rounded-lg hover:bg-gray-100 transition-colors"
35+
(click)="toggleMobileSidebar()"
36+
aria-label="Toggle menu"
37+
data-testid="mobile-menu-button">
38+
<i class="fa-light fa-bars text-gray-600 text-xl"></i>
39+
</button>
40+
</div>
3441
<div class="flex-grow px-5 md:px-8 py-6">
3542
<router-outlet />
3643
</div>

apps/lfx-one/src/app/layouts/main-layout/main-layout.component.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,11 @@
44
:host {
55
display: block;
66
}
7+
8+
::ng-deep {
9+
.mobile-sidebar-drawer {
10+
.p-drawer-content {
11+
@apply px-2;
12+
}
13+
}
14+
}

apps/lfx-one/src/app/layouts/main-layout/main-layout.component.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import { AppService } from '@services/app.service';
1313
import { FeatureFlagService } from '@services/feature-flag.service';
1414
import { PersonaService } from '@services/persona.service';
1515
import { ProjectContextService } from '@services/project-context.service';
16+
import { DrawerModule } from 'primeng/drawer';
1617
import { filter } from 'rxjs';
1718

1819
@Component({
1920
selector: 'lfx-main-layout',
2021
standalone: true,
21-
imports: [CommonModule, RouterModule, SidebarComponent],
22+
imports: [CommonModule, RouterModule, SidebarComponent, DrawerModule],
2223
templateUrl: './main-layout.component.html',
2324
styleUrl: './main-layout.component.scss',
2425
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -30,7 +31,7 @@ export class MainLayoutComponent {
3031
private readonly personaService = inject(PersonaService);
3132
private readonly projectContextService = inject(ProjectContextService);
3233

33-
// Expose mobile sidebar state from service
34+
// Expose mobile sidebar state from service (writable for two-way binding with p-drawer)
3435
protected readonly selectedProject = this.projectContextService.selectedProject;
3536
protected readonly showMobileSidebar = this.appService.showMobileSidebar;
3637

@@ -119,7 +120,13 @@ export class MainLayoutComponent {
119120
});
120121
}
121122

122-
public closeMobileSidebar(): void {
123-
this.appService.closeMobileSidebar();
123+
public toggleMobileSidebar(): void {
124+
this.appService.toggleMobileSidebar();
125+
}
126+
127+
public onDrawerVisibilityChange(visible: boolean): void {
128+
if (!visible) {
129+
this.appService.closeMobileSidebar();
130+
}
124131
}
125132
}

apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@
66
<div class="flex flex-col md:flex-row md:items-center gap-3">
77
<h2>{{ title() }}</h2>
88

9-
<!-- Filter Pills -->
10-
<lfx-filter-pills
11-
[options]="filterOptions"
12-
[selectedFilter]="selectedFilter()"
13-
(filterChange)="handleFilterChange($event)"
14-
data-testid="foundation-health-filters"></lfx-filter-pills>
9+
<div class="flex items-center gap-2">
10+
<!-- Filter Pills -->
11+
<lfx-filter-pills
12+
[options]="filterOptions"
13+
[selectedFilter]="selectedFilter()"
14+
(filterChange)="handleFilterChange($event)"
15+
data-testid="foundation-health-filters"></lfx-filter-pills>
16+
17+
<!-- Data Copilot Button -->
18+
<lfx-data-copilot></lfx-data-copilot>
19+
</div>
1520
</div>
1621

1722
<!-- Carousel Controls -->

apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import { CommonModule } from '@angular/common';
55
import { Component, computed, ElementRef, input, signal, ViewChild } from '@angular/core';
6+
import { DataCopilotComponent } from '@app/shared/components/data-copilot/data-copilot.component';
67
import { ChartComponent } from '@components/chart/chart.component';
78
import { FilterOption, FilterPillsComponent } from '@components/filter-pills/filter-pills.component';
89
import { AGGREGATE_FOUNDATION_METRICS, FOUNDATION_BAR_CHART_OPTIONS, FOUNDATION_SPARKLINE_CHART_OPTIONS } from '@lfx-one/shared/constants';
@@ -12,7 +13,7 @@ import { hexToRgba } from '@lfx-one/shared/utils';
1213
@Component({
1314
selector: 'lfx-foundation-health',
1415
standalone: true,
15-
imports: [CommonModule, FilterPillsComponent, ChartComponent],
16+
imports: [CommonModule, FilterPillsComponent, ChartComponent, DataCopilotComponent],
1617
templateUrl: './foundation-health.component.html',
1718
styleUrl: './foundation-health.component.scss',
1819
})

apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
<div class="flex flex-col md:flex-row md:items-center gap-3">
77
<h2>{{ accountName() }}'s Involvement</h2>
88

9-
<!-- Filter Pills -->
10-
<lfx-filter-pills [options]="filterOptions" [selectedFilter]="selectedFilter()" (filterChange)="handleFilterChange($event)"></lfx-filter-pills>
9+
<div class="flex items-center gap-2">
10+
<!-- Filter Pills -->
11+
<lfx-filter-pills [options]="filterOptions" [selectedFilter]="selectedFilter()" (filterChange)="handleFilterChange($event)"></lfx-filter-pills>
12+
<!-- Data Copilot Button -->
13+
<lfx-data-copilot></lfx-data-copilot>
14+
</div>
1115
</div>
1216

1317
<!-- Carousel Controls -->

apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { CommonModule } from '@angular/common';
55
import { Component, computed, ElementRef, inject, signal, ViewChild } from '@angular/core';
66
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
7+
import { DataCopilotComponent } from '@app/shared/components/data-copilot/data-copilot.component';
78
import { ChartComponent } from '@components/chart/chart.component';
89
import { FilterOption, FilterPillsComponent } from '@components/filter-pills/filter-pills.component';
910
import { TagComponent } from '@components/tag/tag.component';
@@ -19,7 +20,7 @@ import { combineLatest, finalize, map, of, switchMap } from 'rxjs';
1920
@Component({
2021
selector: 'lfx-organization-involvement',
2122
standalone: true,
22-
imports: [CommonModule, ChartComponent, TooltipModule, FilterPillsComponent, TagComponent],
23+
imports: [CommonModule, ChartComponent, TooltipModule, FilterPillsComponent, TagComponent, DataCopilotComponent],
2324
templateUrl: './organization-involvement.component.html',
2425
styleUrl: './organization-involvement.component.scss',
2526
})

apps/lfx-one/src/app/modules/meetings/components/meeting-card/meeting-card.component.html

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,16 @@ <h3 class="text-base font-medium text-gray-900 leading-tight tracking-tight" dat
148148
<i class="fa-light fa-paperclip text-xs"></i>
149149
<span>Resources</span>
150150
</div>
151-
@if (!pastMeeting()) {
152-
<lfx-button icon="fa-light fa-upload text-xs" label="Add" size="small" severity="secondary" data-testid="add-resource-button"> </lfx-button>
151+
@if (!pastMeeting() && meeting().organizer) {
152+
<lfx-button
153+
icon="fa-light fa-upload text-xs"
154+
label="Add"
155+
size="small"
156+
severity="secondary"
157+
data-testid="add-resource-button"
158+
[routerLink]="['/meetings', meeting().uid, 'edit']"
159+
[queryParams]="{ step: '4' }">
160+
</lfx-button>
153161
}
154162
</div>
155163

@@ -248,8 +256,7 @@ <h3 class="text-base font-medium text-gray-900 leading-tight tracking-tight" dat
248256
[currentOccurrence]="currentOccurrence()"
249257
[pastMeeting]="pastMeeting()"
250258
[showAddModal]="!pastMeeting()"
251-
[additionalRegistrantsCount]="additionalRegistrantsCount()"
252-
(addParticipant)="openAddRegistrantModal()">
259+
[additionalRegistrantsCount]="additionalRegistrantsCount()">
253260
</lfx-meeting-rsvp-details>
254261
} @else if (!pastMeeting()) {
255262
<!-- Show RSVP Selection for authenticated non-organizers (upcoming meetings only) -->

apps/lfx-one/src/app/modules/meetings/components/meeting-card/meeting-card.component.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard';
55
import { CommonModule } from '@angular/common';
66
import { Component, computed, effect, inject, Injector, input, OnInit, output, runInInjectionContext, signal, Signal, WritableSignal } from '@angular/core';
7+
import { Router } from '@angular/router';
78
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
89
import {
910
MeetingDeleteConfirmationComponent,
@@ -36,7 +37,6 @@ import {
3637
} from '@lfx-one/shared';
3738
import { MeetingCommitteeModalComponent } from '@modules/meetings/components/meeting-committee-modal/meeting-committee-modal.component';
3839
import { RecordingModalComponent } from '@modules/meetings/components/recording-modal/recording-modal.component';
39-
import { RegistrantModalComponent } from '@modules/meetings/components/registrant-modal/registrant-modal.component';
4040
import { SummaryModalComponent } from '@modules/meetings/components/summary-modal/summary-modal.component';
4141
import { FileSizePipe } from '@pipes/file-size.pipe';
4242
import { FileTypeIconPipe } from '@pipes/file-type-icon.pipe';
@@ -89,6 +89,7 @@ export class MeetingCardComponent implements OnInit {
8989
private readonly injector = inject(Injector);
9090
private readonly clipboard = inject(Clipboard);
9191
private readonly userService = inject(UserService);
92+
private readonly router = inject(Router);
9293

9394
public readonly meetingInput = input.required<Meeting | PastMeeting>();
9495
public readonly occurrenceInput = input<MeetingOccurrence | null>(null);
@@ -186,27 +187,15 @@ export class MeetingCardComponent implements OnInit {
186187

187188
public onRegistrantsToggle(): void {
188189
if (this.meetingRegistrantCount() === 0 && !this.pastMeeting()) {
189-
this.openAddRegistrantModal();
190+
this.router.navigate(['/meetings', this.meeting().uid, 'edit'], {
191+
queryParams: { step: '5' },
192+
});
190193
return;
191194
}
192195

193196
this.showRegistrants.set(!this.showRegistrants());
194197
}
195198

196-
public openAddRegistrantModal(): void {
197-
this.dialogService.open(RegistrantModalComponent, {
198-
header: 'Add Guests',
199-
width: '650px',
200-
modal: true,
201-
closable: true,
202-
dismissableMask: true,
203-
data: {
204-
meetingId: this.meeting().uid,
205-
registrant: null,
206-
},
207-
});
208-
}
209-
210199
public openCommitteeModal(): void {
211200
const header = this.meeting().committees && this.meeting().committees!.length > 0 ? 'Manage Committees' : 'Connect Committees';
212201
this.dialogService
@@ -431,7 +420,8 @@ export class MeetingCardComponent implements OnInit {
431420
baseItems.push({
432421
label: 'Add Guests',
433422
icon: 'fa-light fa-plus',
434-
command: () => this.openAddRegistrantModal(),
423+
routerLink: ['/meetings', this.meeting().uid, 'edit'],
424+
queryParams: { step: '5' },
435425
});
436426

437427
baseItems.push({

0 commit comments

Comments
 (0)