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
3 changes: 0 additions & 3 deletions apps/lfx-one/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,3 @@
<div class="top-20">
<router-outlet />
</div>

<!-- Footer Component -->
<lfx-footer class="py-8"></lfx-footer>
3 changes: 1 addition & 2 deletions apps/lfx-one/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: MIT

import { CommonModule } from '@angular/common';
import { Component, CUSTOM_ELEMENTS_SCHEMA, inject, makeStateKey, REQUEST_CONTEXT, TransferState } from '@angular/core';
import { Component, inject, makeStateKey, REQUEST_CONTEXT, TransferState } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { AuthContext } from '@lfx-one/shared/interfaces';
import { ToastModule } from 'primeng/toast';
Expand All @@ -16,7 +16,6 @@ import { UserService } from './shared/services/user.service';
imports: [RouterOutlet, HeaderComponent, CommonModule, ToastModule],
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppComponent {
private readonly userService = inject(UserService);
Expand Down
6 changes: 0 additions & 6 deletions apps/lfx-one/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ export const routes: Routes = [
},
],
},
// Old UI route - shows when "Old UI" persona is selected
{
path: 'old-ui',
canActivate: [authGuard],
loadComponent: () => import('./modules/pages/home/home.component').then((m) => m.HomeComponent),
},
{
path: 'meetings',
loadChildren: () => import('./modules/meeting/meeting.routes').then((m) => m.MEETING_ROUTES),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ <h2 class="text-lg font-semibold text-gray-900">Menu</h2>
<!-- Main Content Area -->
<main class="flex-1 min-w-0 transition-all duration-300 lg:ml-72" data-testid="main-content">
<router-outlet />
<!-- Footer Component -->
<lfx-footer class="py-8"></lfx-footer>
</main>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: MIT

import { CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core';
import { Component, CUSTOM_ELEMENTS_SCHEMA, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NavigationEnd, Router, RouterModule } from '@angular/router';
import { AppService } from '@app/shared/services/app.service';
Expand All @@ -16,6 +16,7 @@ import { filter } from 'rxjs';
imports: [CommonModule, RouterModule, SidebarComponent],
templateUrl: './main-layout.component.html',
styleUrl: './main-layout.component.scss',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class MainLayoutComponent {
private readonly router = inject(Router);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ <h2 class="font-display font-semibold text-gray-900">My Meetings</h2>
</div>

<!-- Scrollable Content -->
<div class="flex flex-col flex-1">
<div class="flex flex-col flex-1 max-h-[28.125rem] overflow-y-auto">
<div class="flex flex-col gap-6" data-testid="dashboard-my-meetings-list">
@if (todayMeetings().length > 0 || upcomingMeetings().length > 0) {
<!-- TODAY Section - only show if there are meetings today -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,66 +57,14 @@ <h2 class="font-display font-semibold text-[16px]">My Projects</h2>
<!-- Code Activities Chart -->
<td class="py-2 px-3 border-t border-gray-200">
<div class="h-12 w-[12.5rem]">
<lfx-chart
type="line"
[data]="{
labels: generateLabels(project.codeActivities.length),
datasets: [
{
data: project.codeActivities,
borderColor: '#009AFF',
backgroundColor: 'rgba(0, 154, 255, 0.1)',
fill: true,
tension: 0.4,
borderWidth: 2,
pointRadius: 0,
},
],
}"
[options]="{
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false }, tooltip: { enabled: false } },
scales: {
x: { display: false },
y: { display: false },
},
}"
height="100%">
</lfx-chart>
<lfx-chart type="line" [data]="project.codeActivitiesChartData" [options]="chartOptions" height="100%"></lfx-chart>
</div>
</td>

<!-- Non-Code Activities Chart -->
<td class="py-2 px-3 border-t border-gray-200">
<div class="h-12 w-[12.5rem]">
<lfx-chart
type="line"
[data]="{
labels: generateLabels(project.nonCodeActivities.length),
datasets: [
{
data: project.nonCodeActivities,
borderColor: '#10b981',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
fill: true,
tension: 0.4,
borderWidth: 2,
pointRadius: 0,
},
],
}"
[options]="{
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false }, tooltip: { enabled: false } },
scales: {
x: { display: false },
y: { display: false },
},
}"
height="100%">
</lfx-chart>
<lfx-chart type="line" [data]="project.nonCodeActivitiesChartData" [options]="chartOptions" height="100%"></lfx-chart>
</div>
</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@ import { Component } from '@angular/core';
import { ChartComponent } from '@components/chart/chart.component';
import { TableComponent } from '@components/table/table.component';

import type { ChartData, ChartOptions } from 'chart.js';
import type { ProjectItem } from '@lfx-one/shared/interfaces';

/**
* Extended project item with pre-generated chart data
*/
interface ProjectItemWithCharts extends ProjectItem {
codeActivitiesChartData: ChartData<'line'>;
nonCodeActivitiesChartData: ChartData<'line'>;
}

@Component({
selector: 'lfx-my-projects',
standalone: true,
Expand All @@ -16,42 +25,96 @@ import type { ProjectItem } from '@lfx-one/shared/interfaces';
styleUrl: './my-projects.component.scss',
})
export class MyProjectsComponent {
protected readonly projects: ProjectItem[] = [
{
name: 'Kubernetes',
logo: 'https://avatars.githubusercontent.com/u/13455738?s=280&v=4',
role: 'Maintainer',
affiliations: ['CNCF', 'Google'],
codeActivities: [28, 32, 30, 35, 38, 40, 42],
nonCodeActivities: [8, 10, 12, 11, 13, 14, 15],
status: 'active',
},
{
name: 'Linux Kernel',
logo: 'https://upload.wikimedia.org/wikipedia/commons/3/35/Tux.svg',
role: 'Contributor',
affiliations: ['Linux Foundation'],
codeActivities: [15, 18, 20, 22, 24, 26, 28],
nonCodeActivities: [3, 4, 5, 6, 7, 7, 8],
status: 'active',
},
{
name: 'Node.js',
logo: 'https://nodejs.org/static/logos/nodejsHex.svg',
role: 'Reviewer',
affiliations: ['OpenJS Foundation'],
codeActivities: [18, 16, 15, 14, 13, 12, 12],
nonCodeActivities: [8, 7, 6, 6, 5, 5, 5],
status: 'archived',
/**
* Chart options for activity charts
*/
protected readonly chartOptions: ChartOptions<'line'> = {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false }, tooltip: { enabled: false } },
scales: {
x: { display: false },
y: { display: false },
},
];
};

/**
* Projects with pre-generated chart data
*/
protected readonly projects: ProjectItemWithCharts[];

public constructor() {
// Initialize projects with randomized chart data
const baseProjects: ProjectItem[] = [
{
name: 'Kubernetes',
logo: 'https://avatars.githubusercontent.com/u/13455738?s=280&v=4',
role: 'Maintainer',
affiliations: ['CNCF', 'Google'],
codeActivities: this.generateRandomData(7, 25, 45),
nonCodeActivities: this.generateRandomData(7, 8, 16),
status: 'active',
},
{
name: 'Linux Kernel',
logo: 'https://upload.wikimedia.org/wikipedia/commons/3/35/Tux.svg',
role: 'Contributor',
affiliations: ['Linux Foundation'],
codeActivities: this.generateRandomData(7, 12, 30),
nonCodeActivities: this.generateRandomData(7, 3, 9),
status: 'active',
},
{
name: 'Node.js',
logo: 'https://nodejs.org/static/logos/nodejsHex.svg',
role: 'Reviewer',
affiliations: ['OpenJS Foundation'],
codeActivities: this.generateRandomData(7, 10, 20),
nonCodeActivities: this.generateRandomData(7, 4, 10),
status: 'archived',
},
];

// Generate chart data for each project
this.projects = baseProjects.map((project) => ({
...project,
codeActivitiesChartData: this.createChartData(project.codeActivities, '#009AFF', 'rgba(0, 154, 255, 0.1)'),
nonCodeActivitiesChartData: this.createChartData(project.nonCodeActivities, '#10b981', 'rgba(16, 185, 129, 0.1)'),
}));
}

/**
* Generates labels for chart based on data length
* Generates random data array
* @param length - Number of data points
* @returns Array of empty strings for chart labels
* @param min - Minimum value
* @param max - Maximum value
* @returns Array of random numbers
*/
private generateRandomData(length: number, min: number, max: number): number[] {
return Array.from({ length }, () => Math.floor(Math.random() * (max - min + 1)) + min);
}

/**
* Creates chart data configuration
* @param data - Array of values
* @param borderColor - Chart border color
* @param backgroundColor - Chart background color
* @returns Chart.js data configuration
*/
protected generateLabels(length: number): string[] {
return Array.from({ length }, () => '');
private createChartData(data: number[], borderColor: string, backgroundColor: string): ChartData<'line'> {
return {
labels: Array.from({ length: data.length }, () => ''),
datasets: [
{
data,
borderColor,
backgroundColor,
fill: true,
tension: 0.4,
borderWidth: 2,
pointRadius: 0,
},
],
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ <h2 class="font-display font-semibold text-gray-900">Pending Actions</h2>
</div>

<!-- Scrollable Content -->
<div class="flex flex-col flex-1">
<div class="flex flex-col flex-1 max-h-[28.125rem] overflow-y-auto">
<div class="flex flex-col gap-3" data-testid="dashboard-pending-actions-list">
@for (item of pendingActions(); track item.text) {
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@case ('maintainer') {
<lfx-maintainer-dashboard />
}
@default {
<lfx-core-developer-dashboard />
@case ('projects') {
<lfx-home />
}
}
12 changes: 7 additions & 5 deletions apps/lfx-one/src/app/modules/dashboards/dashboard.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT

import { Component, computed, inject } from '@angular/core';
import { Component, computed, inject, Signal } from '@angular/core';
import { PersonaService } from '@app/shared/services/persona.service';
import { PersonaType } from '@lfx-one/shared/interfaces';

import { HomeComponent } from '../pages/home/home.component';
import { CoreDeveloperDashboardComponent } from './core-developer/core-developer-dashboard.component';
import { MaintainerDashboardComponent } from './maintainer/maintainer-dashboard.component';

Expand All @@ -13,7 +16,7 @@ import { MaintainerDashboardComponent } from './maintainer/maintainer-dashboard.
@Component({
selector: 'lfx-dashboard',
standalone: true,
imports: [CoreDeveloperDashboardComponent, MaintainerDashboardComponent],
imports: [CoreDeveloperDashboardComponent, MaintainerDashboardComponent, HomeComponent],
templateUrl: './dashboard.component.html',
})
export class DashboardComponent {
Expand All @@ -23,9 +26,8 @@ export class DashboardComponent {
* Computed signal that determines which dashboard to display
* based on the current persona selection
*/
protected readonly dashboardType = computed(() => {
protected readonly dashboardType: Signal<PersonaType> = computed(() => {
const persona = this.personaService.currentPersona();
// Filter out 'old-ui' as it has its own route
return persona === 'old-ui' ? 'core-developer' : persona;
return persona;
});
Comment on lines +29 to 32
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Typed Signal looks good; add a safe default to harden against bad persona values.

Defensively default to 'projects' if the service ever returns null/unknown.

-  protected readonly dashboardType: Signal<PersonaType> = computed(() => {
-    const persona = this.personaService.currentPersona();
-    return persona;
-  });
+  protected readonly dashboardType: Signal<PersonaType> = computed(() => {
+    const persona = this.personaService.currentPersona();
+    return (persona ?? 'projects') as PersonaType;
+  });

To ensure alignment with objectives, please confirm the set of persona literals used across the app:


🏁 Script executed:

#!/bin/bash
rg -nP -C2 "PersonaType|currentPersona|setPersona|\bprojects\b|\bhome\b"

Length of output: 111653


Remove unnecessary fallback and add license header

  • The dashboardType computed signal safely returns a PersonaType and cannot be null/undefined, so the proposed ?? 'projects' fallback is redundant.
  • Add the required SPDX license header at the top of apps/lfx-one/src/app/modules/dashboards/dashboard.component.ts.
🤖 Prompt for AI Agents
In apps/lfx-one/src/app/modules/dashboards/dashboard.component.ts around lines
29 to 32, remove the unnecessary fallback used when computing dashboardType (the
computed signal should directly return the PersonaType from
personaService.currentPersona() with no '?? "projects"' or similar fallback) and
add the required SPDX license header at the very top of the file (e.g., the
appropriate SPDX short identifier and copyright line) so the file begins with
the licensed header followed by the existing imports and code.

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- Copyright The Linux Foundation and each contributor to LFX. -->
<!-- SPDX-License-Identifier: MIT -->

<div class="container mx-auto pt-24 px-8" data-testid="hero-section">
<div class="container mx-auto px-8" data-testid="hero-section">
<div class="flex flex-col items-center text-center">
<h1 class="text-3xl font-display font-light text-gray-900 mb-3 xl:px-96 lg:px-64 md:px-48" data-testid="hero-title">
Your personalized control panel for managing projects, committees, and meetings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@
[pTooltip]="tooltip()"
[tooltipPosition]="tooltipPosition()"
(onClick)="handleClick($event)"
[routerLink]="routerLink()">
[routerLink]="routerLink()"
[queryParams]="queryParams()"
[target]="target()">
<ng-content></ng-content>
</p-button>
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export class ButtonComponent {
public readonly href = input<string | undefined>(undefined);
public readonly target = input<string | undefined>(undefined);
public readonly rel = input<string | undefined>(undefined);
public readonly queryParams = input<Record<string, string>>({});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use Angular Params type for router query params.

The shared button should accept the full RouterLink Params contract (numbers, booleans, arrays). Record<string, string> rejects those valid shapes and forces consumers to cast or stringify. Switch to Params and keep the input optional to match Angular’s expectations.

-import { RouterModule } from '@angular/router';
+import { Params, RouterModule } from '@angular/router';-  public readonly queryParams = input<Record<string, string>>({});
+  public readonly queryParams = input<Params | undefined>(undefined);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public readonly queryParams = input<Record<string, string>>({});
// Before:
import { RouterModule } from '@angular/router';
// After:
import { Params, RouterModule } from '@angular/router';
// Before:
public readonly queryParams = input<Record<string, string>>({});
// After:
public readonly queryParams = input<Params | undefined>(undefined);
🤖 Prompt for AI Agents
In apps/lfx-one/src/app/shared/components/button/button.component.ts around line
58, replace the current input type Record<string, string> with Angular's Params
type and make the input optional; update imports to include Params from
@angular/router and change the input declaration to use Params (or Params |
undefined) with an optional/default value so that numbers, booleans, arrays and
other valid RouterLink shapes are accepted without consumer casting.


// Events
public readonly onClick = output<MouseEvent>();
Expand Down
Loading