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
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"PostgreSQL",
"PostgREST",
"primeng",
"sparkline",
"styleclass",
"supabase",
"timegrid",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!-- Copyright The Linux Foundation and each contributor to LFX. -->
<!-- SPDX-License-Identifier: MIT -->

<div class="container mx-auto px-4 sm:px-6 lg:px-8" data-testid="dashboard-container">
<!-- Organization Selector -->
<div class="mb-6 flex items-center gap-4" data-testid="organization-selector">
<label for="organization-select" class="text-sm font-semibold text-gray-700">Organization:</label>
<lfx-select
[form]="accountForm"
control="selectedAccountId"
[options]="availableAccounts()"
optionLabel="accountName"
optionValue="accountId"
[filter]="true"
filterPlaceholder="Search organizations..."
placeholder="Select an organization"
[showClear]="false"
styleClass="min-w-[300px]"
inputId="organization-select"
data-testid="organization-select"
(onChange)="handleAccountChange($event)" />
</div>

<!-- Dashboard Sections -->
<div class="flex flex-col gap-6" data-testid="dashboard-sections-grid">
<!-- Organization Involvement - Full Width -->
<lfx-organization-involvement />

<!-- Middle Row - Two Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- My Meetings -->
<lfx-my-meetings class="h-full" />

<!-- Pending Actions -->
<lfx-pending-actions class="h-full" />
</div>

<!-- Foundation Health - Full Width -->
<lfx-foundation-health />
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT

import { Component, computed, inject, Signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { Account } from '@lfx-one/shared/interfaces';
import { SelectComponent } from '../../../shared/components/select/select.component';
import { AccountContextService } from '../../../shared/services/account-context.service';
import { FoundationHealthComponent } from '../components/foundation-health/foundation-health.component';
import { MyMeetingsComponent } from '../components/my-meetings/my-meetings.component';
import { OrganizationInvolvementComponent } from '../components/organization-involvement/organization-involvement.component';
import { PendingActionsComponent } from '../components/pending-actions/pending-actions.component';

@Component({
selector: 'lfx-board-member-dashboard',
imports: [OrganizationInvolvementComponent, PendingActionsComponent, MyMeetingsComponent, FoundationHealthComponent, SelectComponent, ReactiveFormsModule],
templateUrl: './board-member-dashboard.component.html',
styleUrl: './board-member-dashboard.component.scss',
})
export class BoardMemberDashboardComponent {
private readonly accountContextService = inject(AccountContextService);

protected readonly accountForm = new FormGroup({
selectedAccountId: new FormControl<string>(this.accountContextService.selectedAccount().accountId),
});

protected readonly availableAccounts: Signal<Account[]> = computed(() => this.accountContextService.availableAccounts);

/**
* Handle account selection change
*/
protected handleAccountChange(event: any): void {
const selectedAccountId = event.value as string;
const selectedAccount = this.accountContextService.availableAccounts.find((acc) => acc.accountId === selectedAccountId);
if (selectedAccount) {
this.accountContextService.setAccount(selectedAccount);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<!-- Copyright The Linux Foundation and each contributor to LFX. -->
<!-- SPDX-License-Identifier: MIT -->

<section data-testid="dashboard-foundation-health-section">
<!-- Header with title and optional View All button -->
<div class="flex items-center justify-between mb-4">
<h2 class="font-['Roboto_Slab'] font-semibold text-[16px]">Foundation Health</h2>
@if (onViewAll()) {
<button
class="flex items-center gap-1 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-100 rounded transition-colors"
(click)="onViewAll()!()"
data-testid="foundation-health-view-all">
View All
<i class="fa-light fa-chevron-right w-4 h-4"></i>
</button>
}
</div>

<!-- Foundation Health Table -->
<div class="bg-white rounded-lg border border-slate-200">
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-border">
<th class="sticky left-0 z-10 bg-white text-left py-2 px-6 text-xs font-medium text-muted-foreground min-w-[200px] border-r-2 border-slate-200">
Foundation
</th>
<th class="text-left py-2 px-3 text-xs font-medium text-gray-500 min-w-[140px]">
<div class="flex items-center gap-1">
Health Score
<i class="fa-light fa-circle-question w-3 h-3 cursor-help" title="Overall health score of the foundation"></i>
</div>
</th>
<th class="text-left py-2 px-3 text-xs font-medium text-gray-500 min-w-[140px]">
<div class="flex items-center gap-1">
Software Value
<i class="fa-light fa-circle-question w-3 h-3 cursor-help" title="Estimated total value of software managed by the foundation"></i>
</div>
</th>
<th class="text-left py-2 px-3 text-xs font-medium text-gray-500 min-w-[140px]">
<div class="flex items-center gap-1">
Total Members
<i class="fa-light fa-circle-question w-3 h-3 cursor-help" title="Total number of member organizations in the foundation"></i>
</div>
</th>
<th class="text-left py-2 px-3 text-xs font-medium text-gray-500 min-w-[140px]">
<div class="flex items-center gap-1">
Active Contributors
<i class="fa-light fa-circle-question w-3 h-3 cursor-help" title="Average number of active contributors over the past year"></i>
</div>
</th>
<th class="text-left py-2 px-3 text-xs font-medium text-gray-500 min-w-[140px]">
<div class="flex items-center gap-1">
Maintainers
<i class="fa-light fa-circle-question w-3 h-3 cursor-help" title="Average number of project maintainers over the past year"></i>
</div>
</th>
<th class="text-left py-2 px-3 text-xs font-medium text-gray-500 min-w-[140px]">
<div class="flex items-center gap-1">
Events
<i class="fa-light fa-circle-question w-3 h-3 cursor-help" title="Total number of events hosted by the foundation this year"></i>
</div>
</th>
<th class="text-left py-2 px-3 text-xs font-medium text-gray-500 min-w-[180px]">
<div class="flex items-center gap-1">
Org Dependency Risk
<i
class="fa-light fa-circle-question w-3 h-3 cursor-help"
title="Risk level based on concentration of contributions from top organizations"></i>
</div>
</th>
</tr>
</thead>
<tbody>
@for (foundation of foundations(); track foundation.id) {
<tr class="border-b border-border last:border-b-0" [attr.data-testid]="'foundation-row-' + foundation.id">
<!-- Foundation Name Column (Sticky) -->
<td class="sticky left-0 z-10 bg-white py-3 px-6 border-r-2 border-slate-200">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full overflow-hidden flex-shrink-0 bg-white p-1">
<img [src]="foundation.logo" [alt]="foundation.name + ' logo'" class="w-full h-full object-contain" />
</div>
<div class="min-w-0">
<div class="font-medium text-sm text-[#009aff] truncate">{{ foundation.name }}</div>
@if (foundation.projectBreakdown) {
<div class="text-xs text-gray-500">
<div>{{ foundation.projectBreakdown.sandbox }} sandbox</div>
<div>{{ foundation.projectBreakdown.incubating }} incubating</div>
<div>{{ foundation.projectBreakdown.graduated }} graduated</div>
</div>
} @else {
<div class="text-xs text-gray-500">{{ foundation.projectCount }} projects</div>
}
</div>
</div>
</td>

<!-- Health Score -->
<td class="py-3 px-3">
<lfx-health-score-tag [score]="foundation.healthScore"></lfx-health-score-tag>
</td>

<!-- Software Value -->
<td class="py-3 px-3">
<div class="text-sm font-medium whitespace-nowrap">
{{ foundation.softwareValueFormatted }}
</div>
</td>

<!-- Total Members -->
<td class="py-3 px-3">
<div class="text-sm font-medium whitespace-nowrap">
{{ foundation.totalMembersFormatted }}
</div>
</td>

<!-- Active Contributors with Sparkline -->
<td class="py-3 px-3">
<div class="flex items-center gap-2">
<div class="w-[60px] h-6 flex-shrink-0">
<lfx-chart type="line" [data]="foundation.activeContributorsChartData" [options]="sparklineOptions" height="100%"> </lfx-chart>
</div>
<div class="text-sm font-medium whitespace-nowrap">
{{ foundation.activeContributorsAvg }}
</div>
</div>
</td>

<!-- Maintainers with Sparkline -->
<td class="py-3 px-3">
<div class="flex items-center gap-2">
<div class="w-[60px] h-6 flex-shrink-0">
<lfx-chart type="line" [data]="foundation.maintainersChartData" [options]="sparklineOptions" height="100%"> </lfx-chart>
</div>
<div class="text-sm font-medium whitespace-nowrap">
{{ foundation.maintainersAvg }}
</div>
</div>
</td>

<!-- Events with Monthly Bar Chart -->
<td class="py-3 px-3">
<div class="flex items-center gap-2">
<div class="w-[80px] flex-shrink-0">
<div class="flex items-end gap-0.5 h-10">
@for (height of foundation.barHeights; track $index) {
<div class="flex-1 bg-[#0094FF] rounded-sm min-w-[3px]" [style.height.%]="height" [attr.data-testid]="'event-bar-' + $index"></div>
}
</div>
</div>
<div class="text-sm font-medium whitespace-nowrap">
{{ foundation.eventsTotal }}
</div>
</div>
</td>

<!-- Org Dependency Risk with Pie Chart -->
<td class="py-3 px-3">
<div class="flex items-center gap-3">
<div class="relative w-10 h-10 flex-shrink-0">
<svg class="w-10 h-10" viewBox="0 0 40 40">
<!-- Other orgs slice (light grey) -->
<path [attr.d]="foundation.pieChartPaths.otherPath" fill="#E5E7EB" />
<!-- Top orgs slice (risk color) -->
<path [attr.d]="foundation.pieChartPaths.topPath" [attr.fill]="foundation.orgDependencyColor" />
</svg>
</div>
<div class="flex flex-col gap-0.5 min-w-0">
<div class="text-sm font-medium" [ngClass]="foundation.orgDependencyTextColorClass">
{{ foundation.orgDependency.topOrgsCount }} orgs: {{ foundation.orgDependency.topOrgsPercentage }}%
</div>
<div class="text-xs text-gray-500">
{{ foundation.orgDependency.otherOrgsCount }} orgs: {{ foundation.orgDependency.otherOrgsPercentage }}%
</div>
</div>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT

:host {
display: block;
}
Loading