Skip to content
Closed
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
86 changes: 83 additions & 3 deletions apps/lfx-one/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,91 @@ export const routes: Routes = [
data: { lens: 'project' },
loadComponent: () => import('./modules/dashboards/dashboard.component').then((m) => m.DashboardComponent),
},
// Org Lens dashboard (placeholder β€” reuses DashboardComponent for now)
{
path: 'org',
path: 'org/overview',
data: { lens: 'org' },
loadComponent: () => import('./modules/dashboards/dashboard.component').then((m) => m.DashboardComponent),
loadComponent: () => import('./modules/dashboards/org/org-overview/org-overview.component').then((m) => m.OrgOverviewComponent),
},
{
path: 'org/memberships',
data: { lens: 'org', title: 'Memberships', description: 'Active memberships and tier history.', icon: 'fa-light fa-display' },
loadComponent: () =>
import('./modules/dashboards/org/components/org-placeholder-page/org-placeholder-page.component').then((m) => m.OrgPlaceholderPageComponent),
},
{
path: 'org/projects',
data: { lens: 'org', title: 'Projects', description: 'Projects your organization participates in.', icon: 'fa-light fa-folder' },
loadComponent: () =>
import('./modules/dashboards/org/components/org-placeholder-page/org-placeholder-page.component').then((m) => m.OrgPlaceholderPageComponent),
},
{
path: 'org/roi',
data: { lens: 'org', title: 'ROI', description: 'Return on investment across your memberships and engagement.', icon: 'fa-light fa-chart-line-up' },
loadComponent: () =>
import('./modules/dashboards/org/components/org-placeholder-page/org-placeholder-page.component').then((m) => m.OrgPlaceholderPageComponent),
},
{
path: 'org/governance',
data: { lens: 'org', title: 'Governance', description: 'Board seats and governance participation.', icon: 'fa-light fa-layer-group' },
loadComponent: () =>
import('./modules/dashboards/org/components/org-placeholder-page/org-placeholder-page.component').then((m) => m.OrgPlaceholderPageComponent),
},
{
path: 'org/people',
data: { lens: 'org', title: 'People', description: 'Employees and contributors associated with your organization.', icon: 'fa-light fa-users' },
loadComponent: () =>
import('./modules/dashboards/org/components/org-placeholder-page/org-placeholder-page.component').then((m) => m.OrgPlaceholderPageComponent),
},
{
path: 'org/contributions',
data: {
lens: 'org',
title: 'Code Contributions',
description: "Open-source contributions from your organization's contributors.",
icon: 'fa-light fa-code',
},
loadComponent: () =>
import('./modules/dashboards/org/components/org-placeholder-page/org-placeholder-page.component').then((m) => m.OrgPlaceholderPageComponent),
},
{
path: 'org/events',
data: { lens: 'org', title: 'Events', description: 'Events your organization is sponsoring or attending.', icon: 'fa-light fa-calendar' },
loadComponent: () =>
import('./modules/dashboards/org/components/org-placeholder-page/org-placeholder-page.component').then((m) => m.OrgPlaceholderPageComponent),
},
{
path: 'org/training',
data: {
lens: 'org',
title: 'Training & Certification',
description: 'Training enrollments and certifications across your organization.',
icon: 'fa-light fa-graduation-cap',
},
loadComponent: () =>
import('./modules/dashboards/org/components/org-placeholder-page/org-placeholder-page.component').then((m) => m.OrgPlaceholderPageComponent),
},
{
path: 'org/meetings',
data: { lens: 'org', title: 'Meetings', description: 'Meetings your organization is participating in.', icon: 'fa-light fa-video' },
loadComponent: () =>
import('./modules/dashboards/org/components/org-placeholder-page/org-placeholder-page.component').then((m) => m.OrgPlaceholderPageComponent),
},
{
path: 'org/groups',
data: { lens: 'org', title: 'Groups', description: 'Committees your organization participates in.', icon: 'fa-light fa-users-rectangle' },
loadComponent: () =>
import('./modules/dashboards/org/components/org-placeholder-page/org-placeholder-page.component').then((m) => m.OrgPlaceholderPageComponent),
},
{
path: 'org/profile',
data: { lens: 'org', title: 'Profile', description: 'Public-facing details about your organization.', icon: 'fa-light fa-file' },
loadComponent: () =>
import('./modules/dashboards/org/components/org-placeholder-page/org-placeholder-page.component').then((m) => m.OrgPlaceholderPageComponent),
},
{
path: 'org',
redirectTo: 'org/overview',
pathMatch: 'full',
},
// Foundation Lens β€” feature routes (lens-tagged so deep links restore the foundation lens)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
[items]="sidebarItems()"
[showMeSelector]="activeLens() === 'me'"
[showProjectSelector]="activeLens() === 'project' || activeLens() === 'foundation'"
[showOrgSelector]="activeLens() === 'org'"
[(selectorPanelOpen)]="selectorPanelOpen"></lfx-sidebar>
</div>
</div>
Expand Down Expand Up @@ -64,6 +65,7 @@
[items]="sidebarItems()"
[showMeSelector]="activeLens() === 'me'"
[showProjectSelector]="activeLens() === 'project' || activeLens() === 'foundation'"
[showOrgSelector]="activeLens() === 'org'"
[(selectorPanelOpen)]="selectorPanelOpen"
[mobile]="true"></lfx-sidebar>
</div>
Expand Down
82 changes: 48 additions & 34 deletions apps/lfx-one/src/app/layouts/main-layout/main-layout.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,70 +335,84 @@ export class MainLayoutComponent {
},
];

// --- Org Lens Items ---
private readonly orgLensItems: SidebarMenuItem[] = [
{
label: 'Overview',
label: 'Org Overview',
icon: 'fa-light fa-grid-2',
routerLink: '/org',
routerLink: '/org/overview',
},
{
label: 'Portfolio',
label: 'Org Foundations',
isSection: true,
expanded: true,
items: [
{
label: 'Key Projects',
icon: 'fa-light fa-diagram-project',
label: 'Memberships',
icon: 'fa-light fa-display',
routerLink: '/org/memberships',
},
{
label: 'Projects',
icon: 'fa-light fa-folder',
routerLink: '/org/projects',
},
{
label: 'Code Contributions',
icon: 'fa-light fa-code',
routerLink: '/org/code',
label: 'ROI',
icon: 'fa-light fa-chart-line-up',
routerLink: '/org/roi',
},
{
label: 'Governance',
icon: 'fa-light fa-layer-group',
routerLink: '/org/governance',
},
],
},
{
label: 'Membership',
label: 'Org Engagement',
isSection: true,
expanded: true,
items: [
{
label: 'Membership',
icon: 'fa-light fa-id-card',
routerLink: '/org/membership',
label: 'People',
icon: 'fa-light fa-users',
routerLink: '/org/people',
},
{
label: 'Benefits',
icon: 'fa-light fa-gift',
routerLink: '/org/benefits',
label: 'Code Contributions',
icon: 'fa-light fa-code',
routerLink: '/org/contributions',
},
],
},
{
label: 'Administration',
isSection: true,
expanded: true,
items: [
{
label: COMMITTEE_LABEL.plural,
icon: 'fa-light fa-users-rectangle',
routerLink: '/org/groups',
label: 'Events',
icon: 'fa-light fa-calendar',
routerLink: '/org/events',
},
{
label: 'CLA Management',
icon: 'fa-light fa-file-signature',
routerLink: '/org/cla',
label: 'Training & Certification',
icon: 'fa-light fa-graduation-cap',
routerLink: '/org/training',
},
{
label: 'Access & Permissions',
icon: 'fa-light fa-key',
routerLink: '/org/permissions',
label: 'Meetings',
icon: 'fa-light fa-video',
routerLink: '/org/meetings',
},
{
label: 'Org Profile',
icon: 'fa-light fa-building',
label: COMMITTEE_LABEL.plural,
icon: 'fa-light fa-users-rectangle',
routerLink: '/org/groups',
},
],
},
{
label: 'Org Admin',
isSection: true,
expanded: true,
items: [
{
label: 'Profile',
icon: 'fa-light fa-file',
routerLink: '/org/profile',
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<!-- Copyright The Linux Foundation and each contributor to LFX. -->
<!-- SPDX-License-Identifier: MIT -->

<tr
[class]="trClasses()"
[attr.role]="trRole()"
[attr.tabindex]="trTabIndex()"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug()"
(click)="onRowClick($event)"
(keydown)="onRowKeydown($event)">
<td class="px-4 py-3 align-middle">
<div class="flex items-center gap-3">
<button
type="button"
class="h-7 w-7 p-0 flex items-center justify-center rounded text-gray-500 hover:bg-gray-200 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:outline-none"
[attr.aria-expanded]="expanded()"
[attr.aria-label]="chevronAriaLabel()"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug() + '-caret'"
(click)="onChevronClick($event)">
<i class="fa-light" [class.fa-chevron-down]="!expanded()" [class.fa-chevron-up]="expanded()" aria-hidden="true"></i>
</button>

@if (row().foundationLogoUrl) {
<img
[src]="row().foundationLogoUrl"
[alt]="row().foundationName + ' logo'"
loading="lazy"
class="h-14 w-14 rounded-lg object-contain shrink-0 bg-white"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug() + '-logo'" />
} @else {
<div
class="h-14 w-14 rounded-lg shrink-0 flex items-center justify-center text-base font-semibold"
[class]="logoSquareClasses()"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug() + '-logo-square'">
{{ initials() }}
</div>
}

<div class="flex flex-col gap-1 min-w-0">
<span class="text-sm font-medium text-gray-900 truncate">{{ row().foundationName }}</span>
<div class="flex items-center gap-2 text-xs">
@if (showProjectsInvolved()) {
<span class="text-gray-500">{{ projectsInvolvedText() }} <span class="text-gray-400" aria-hidden="true">Β·</span></span>
}
<span
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium"
[class]="ribbonClasses()"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug() + '-tier-ribbon'">
<svg class="h-3 w-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21l-3-6h6l-3 6zM7 3h10v9H7z" />
</svg>
{{ subtitleText() }}
</span>
</div>
</div>
</div>
</td>

<td class="px-4 py-3 align-middle">
<span
class="inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium"
[class]="orgRoleBadgeClasses(row().badges.orgRole)"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug() + '-badge-orgRole'">
{{ row().badges.orgRole }}
</span>
</td>

<td class="px-4 py-3 align-middle">
@if (row().badges.votingStatus === 'β€”') {
<span
class="text-sm"
[class]="votingStatusBadgeClasses(row().badges.votingStatus)"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug() + '-badge-votingStatus'"
>β€”</span
>
} @else {
<span
class="inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium"
[class]="votingStatusBadgeClasses(row().badges.votingStatus)"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug() + '-badge-votingStatus'">
{{ row().badges.votingStatus }}
</span>
}
</td>

<td class="px-4 py-3 align-middle" [pTooltip]="governanceHeaderTooltip" tooltipPosition="top">
@if (row().badges.governanceParticipation === 'β€”') {
<span
class="text-sm"
[class]="governanceBadgeClasses(row().badges.governanceParticipation)"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug() + '-badge-governanceParticipation'"
>β€”</span
>
} @else {
<span
class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium"
[class]="governanceBadgeClasses(row().badges.governanceParticipation)"
[pTooltip]="governanceTooltip()"
tooltipPosition="top"
[tooltipDisabled]="!governanceTooltip()"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug() + '-badge-governanceParticipation'">
{{ row().badges.governanceParticipation }}
</span>
}
</td>
</tr>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT

// Host element disappears from the box tree so the inner `<tr>` is a direct
// child of the parent `<tbody>` (required for HTML table layout). This is
// the canonical Angular alternative to attribute-selector row components,
// which our eslint rules disallow (component selectors must be kebab-case
// elements per `@angular-eslint/component-selector`).
:host {
display: contents;
}
Loading
Loading