Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="entity-aow-2030_content_header">
<h3>2030 Outcomes</h3>
<div class="entity-aow-2030_content_header_dot"></div>
<h3 class="entity-aow-2030_content_header_phase">Reporting Phase 2025</h3>
<h3 class="entity-aow-2030_content_header_phase">Reporting Phase {{ entityAowService.reportingPhaseYear }}</h3>
</div>

<div class="entity-aow-2030_content_table">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ describe('AowHloTableComponent', () => {
const mockExistingResultsContributors = signal<any[]>([]);

mockEntityAowService = {
reportingPhaseYear: 2026,
aowId: signal<string>(''),
entityId: signal<string>(''),
entityDetails: signal<any>({}),
Expand Down Expand Up @@ -225,10 +226,10 @@ describe('AowHloTableComponent', () => {
describe('Component Initialization', () => {
it('should initialize with default values', () => {
expect(component.columnOrder()).toEqual([
{ title: 'Indicator name', attr: 'indicator_description', width: '30%' },
{ title: 'Type', attr: 'type_name', width: '10%' },
{ title: 'Expected target 2025', attr: 'target_value_sum', width: '10%' },
{ title: 'Actual achieved', attr: 'actual_achieved_value_sum', width: '10%' },
{ title: 'KPI statement', attr: 'indicator_description', width: '30%' },
{ title: 'Indicator typology', attr: 'type_name', width: '10%' },
{ title: '2026 target', attr: 'target_value_sum', width: '10%' },
{ title: 'Achieved target', attr: 'actual_achieved_value_sum', width: '10%' },
{ title: 'Status', attr: 'status', hideSortIcon: true, width: '11%' }
]);
});
Expand All @@ -245,22 +246,22 @@ describe('AowHloTableComponent', () => {

expect(columns).toHaveLength(5);
expect(columns[0]).toEqual({
title: 'Indicator name',
title: 'KPI statement',
attr: 'indicator_description',
width: '30%'
});
expect(columns[1]).toEqual({
title: 'Type',
title: 'Indicator typology',
attr: 'type_name',
width: '10%'
});
expect(columns[2]).toEqual({
title: 'Expected target 2025',
title: '2026 target',
attr: 'target_value_sum',
width: '10%'
});
expect(columns[3]).toEqual({
title: 'Actual achieved',
title: 'Achieved target',
attr: 'actual_achieved_value_sum',
width: '10%'
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ export class AowHloTableComponent {
return expanded;
});

columnOrder = signal<ColumnOrder[]>([
{ title: 'Indicator name', attr: 'indicator_description', width: '30%' },
{ title: 'Type', attr: 'type_name', width: '10%' },
{ title: 'Expected target 2025', attr: 'target_value_sum', width: '10%' },
{ title: 'Actual achieved', attr: 'actual_achieved_value_sum', width: '10%' },
// P2-3053: agreed nomenclature + dynamic phase year ("<year> target") instead of hardcoded "2025".
columnOrder = computed<ColumnOrder[]>(() => [
{ title: 'KPI statement', attr: 'indicator_description', width: '30%' },
{ title: 'Indicator typology', attr: 'type_name', width: '10%' },
{ title: `${this.entityAowService.reportingPhaseYear} target`.trim(), attr: 'target_value_sum', width: '10%' },
{ title: 'Achieved target', attr: 'actual_achieved_value_sum', width: '10%' },
{ title: 'Status', attr: 'status', hideSortIcon: true, width: '11%' }
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@

<app-alert-status
status="info"
description="Indicate in this box the numerical value that your result contributes toward the 2025 target of the indicator.<br>
<br><strong>Example:</strong> If the 2025 indicator target is 200 (people trained) and your result (e.g., a capacity-sharing activity) provides evidence of 90 people trained, enter <strong>90</strong> in this box.<br>
<br>The values entered here will be aggregated at the end of the reporting cycle to assess progress toward the planned 2025 target for the indicator."
description="Indicate in this box the numerical value that your result contributes toward the {{ entityAowService.reportingPhaseYear }} target of the indicator.<br>
<br><strong>Example:</strong> If the {{ entityAowService.reportingPhaseYear }} indicator target is 200 (people trained) and your result (e.g., a capacity-sharing activity) provides evidence of 90 people trained, enter <strong>90</strong> in this box.<br>
<br>The values entered here will be aggregated at the end of the reporting cycle to assess progress toward the planned {{ entityAowService.reportingPhaseYear }} target for the indicator."
inlineStyles="margin: 0; margin-bottom: 10px !important;"></app-alert-status>

<p-inputnumber
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<div class="tab-content_header">
<h3>High-Level Outputs Indicators</h3>
<div class="tab-content_header_dot"></div>
<h3 class="tab-content_header_phase">Reporting Phase 2025</h3>
<h3 class="tab-content_header_phase">Reporting Phase {{ entityAowService.reportingPhaseYear }}</h3>
</div>

<div class="tab-content">
Expand All @@ -40,7 +40,7 @@ <h3 class="tab-content_header_phase">Reporting Phase 2025</h3>
<div class="tab-content_header">
<h3>Intermediate Outcomes Indicators</h3>
<div class="tab-content_header_dot"></div>
<h3 class="tab-content_header_phase">Reporting Phase 2025</h3>
<h3 class="tab-content_header_phase">Reporting Phase {{ entityAowService.reportingPhaseYear }}</h3>
</div>

<div class="tab-content">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import { forkJoin } from 'rxjs';
export class EntityAowService {
private readonly api = inject(ApiService);

// P2-3053: active reporting-phase year for the ToC views (replaces hardcoded "2025").
// Returns '' until the current phase is loaded so the UI never shows "null".
get reportingPhaseYear(): number | string {
return this.api.dataControlSE.reportingCurrentPhase.phaseYear ?? '';
}

entityId = signal<string>('');
aowId = signal<string>('');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ <h3 class="details_card_content_graph_item_title">Outcomes</h3>

<div class="entity-details_cards">
<div class="entity-details_card">
<div class="entity-details_card_header">Reporting by Theory of Change</div>
<div class="entity-details_card_header">
Results planned in your {{ entityAowService.reportingPhaseYear }} ToC
<i
class="pi pi-question-circle entity-details_card_header_info"
pTooltip="Report results as planned theory of change"
tooltipPosition="top"></i>
</div>

<div class="entity-details_card_content">
@if (entityAowService.isLoadingDetails()) {
Expand All @@ -89,7 +95,11 @@ <h1 class="entity-details_card_content_empty">No areas of work found for this sc
</div>
<div class="entity-details_card">
<div class="entity-details_card_header">
Reporting by Result Category
Report Emerging results
<i
class="pi pi-question-circle entity-details_card_header_info"
pTooltip="Report achievements that were not originally included in the {{ entityAowService.reportingPhaseYear }} Theory of Change, but emerged as valuable results during the year."
tooltipPosition="top"></i>
<p-splitbutton
label="Report"
icon="pi pi-plus-circle"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Chart, ChartData, ChartDataset, ChartOptions } from 'chart.js';
import { ButtonModule } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';
import { SplitButtonModule } from 'primeng/splitbutton';
import { TooltipModule } from 'primeng/tooltip';
import { ResultCreatorModule } from '../../../results/pages/result-creator/result-creator.module';
import { MenuItem } from 'primeng/api';
import { BilateralResultsReviewComponent } from './components/bilateral-results-review/bilateral-results-review.component';
Expand All @@ -36,6 +37,7 @@ import { ResultFrameworkReportingHomeService } from '../result-framework-reporti
ButtonModule,
DialogModule,
SplitButtonModule,
TooltipModule,
ResultCreatorModule,
BilateralResultsReviewComponent
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,12 @@
}
</div>
} @else {
<!-- NOTE (P2-3049): empty cell is intentional when a result has no lead center (led by an external partner).
Future change: fall back to showing the lead partner here instead of leaving it blank. -->
<div
[pTooltip]="subResult.inQA ? 'This result is being QAed' : ''"
[ngClass]="{ center_flex_100: column.center, 'text-center': column.center, 'truncate-multiline': column.attr === 'full_name' }"
[innerHTML]="column.attr === 'lead_center' ? (subResult.lead_center || 'Undefined') : (column.attr !== 'created_date' ? subResult[column.attr] : (subResult[column.attr] | date: 'YYYY-MM-dd'))"></div>
[innerHTML]="column.attr === 'lead_center' ? (subResult.lead_center || '') : (column.attr !== 'created_date' ? subResult[column.attr] : (subResult[column.attr] | date: 'YYYY-MM-dd'))"></div>
}
</a>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-22
30 changes: 30 additions & 0 deletions openspec/changes/fix-results-list-undefined-center/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## Context

The results list table (`results-list.component.html`) renders the **Center** column from each result's `lead_center` field. The current binding (line 124) is:

```html
column.attr === 'lead_center' ? (subResult.lead_center || 'Undefined') : ...
```

When `lead_center` is falsy (null / empty β€” which happens when the result is led by an external partner, not a CGIAR center), the `|| 'Undefined'` branch renders the literal string `"Undefined"`. QA reported this as P2-3049.

## Goals / Non-Goals

**Goals:**
- The Center cell renders **blank** (empty string) when a result has no lead center.
- Document, in code, the future intent to display the lead partner when no center exists.

**Non-Goals:**
- Resolving and displaying the actual lead partner name when `lead_center` is absent (future change).
- Any backend / API / payload changes.
- Touching the IPSR list (it has no `lead_center` column β€” verified).

## Decisions

- **Minimal template fix:** change the fallback `'Undefined'` to `''` in the existing ternary. No new component logic, no pipe, no service change β€” keeps the fix quirurgical and low-risk.
- **Document future scope inline:** add a `TODO (P2-3049)` HTML comment next to the binding so the next developer knows the empty cell is intentional and that the long-term behavior is to fall back to the lead partner.

## Risks / Trade-offs

- **Empty cell vs. placeholder:** showing a blank cell (instead of e.g. a dash) is the explicitly requested behavior. Trade-off accepted: a blank cell reads as "no center" rather than a broken value.
- Very low risk: single-line template change, no behavioral change for results that *do* have a lead center.
24 changes: 24 additions & 0 deletions openspec/changes/fix-results-list-undefined-center/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## Why

In the results list table, the **Center** column shows the literal text `"Undefined"` whenever a result has no lead center. This happens when the result is led by an **external partner** (not a CGIAR center), so the backend returns an empty/null `lead_center`. The template falls back to the hardcoded string `'Undefined'`, which is confusing for users and was reported as a QA bug (P2-3049).

## What Changes

- Replace the hardcoded fallback `|| 'Undefined'` with an **empty string** `''` so the Center cell renders blank instead of `"Undefined"` when there is no lead center.
- Leave a code `TODO (P2-3049)` comment documenting that a future change should display the **lead partner** in this column when no lead center exists (i.e. take partners into account, not only centers).

Out of scope (documented for the future, not implemented now): actually resolving and rendering the lead partner name when `lead_center` is absent.

## Capabilities

### New Capabilities
- `results-list-center-column`: Defines how the Center column of the results list renders a result's lead center, including the empty-state behavior when no lead center exists.

### Modified Capabilities
<!-- none: this is a new spec capturing existing + corrected behavior -->

## Impact

- Frontend only. Single template line + a code comment.
- File: `onecgiar-pr-client/src/app/pages/results/pages/results-outlet/pages/results-list/results-list.component.html` (line 124).
- No backend, API, or data-model changes. No new dependencies.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## ADDED Requirements

### Requirement: Center column empty state

The Center column of the results list SHALL display the result's lead center name when a lead center exists, and SHALL render an empty cell (no text) when the result has no lead center. The column MUST NOT render the literal text `"Undefined"`.

#### Scenario: Result has a lead center

- **WHEN** a result has a non-empty `lead_center` value
- **THEN** the Center column displays that `lead_center` value

#### Scenario: Result has no lead center (led by an external partner)

- **WHEN** a result's `lead_center` is null or empty
- **THEN** the Center column renders an empty cell
- **AND** the literal text `"Undefined"` is never shown

### Requirement: Documented future fallback to lead partner

The code SHALL carry a documented note (TODO referencing P2-3049) indicating that, in a future change, the Center column is expected to fall back to displaying the result's lead partner when no lead center exists.

#### Scenario: Developer reads the Center column binding

- **WHEN** a developer inspects the Center column template binding
- **THEN** a comment documents that the empty cell is intentional and that the future behavior is to display the lead partner instead
9 changes: 9 additions & 0 deletions openspec/changes/fix-results-list-undefined-center/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## 1. Fix the Center column fallback

- [x] 1.1 In `onecgiar-pr-client/src/app/pages/results/pages/results-outlet/pages/results-list/results-list.component.html` (line ~124), change the `lead_center` fallback from `|| 'Undefined'` to `|| ''` so an empty cell is rendered when there is no lead center.
- [x] 1.2 Add a `TODO (P2-3049)` HTML comment next to the binding documenting that the empty cell is intentional and that a future change should display the lead partner when no lead center exists.

## 2. Verify

- [x] 2.1 Verify a result with a lead center still shows the center name. (Confirmed locally: e.g. "ICARDA" renders.)
- [x] 2.2 Verify a result led by an external partner (no lead center) shows a blank cell, not `"Undefined"`. (Confirmed locally: empty cells, no "Undefined" anywhere on the page.)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-22
40 changes: 40 additions & 0 deletions openspec/changes/p2-3053-dynamic-reporting-phase-year/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## Context

The Reporting-by-ToC views render a phase header and an HLO indicator table. The phase year is written as the literal string `2025` in the templates and in the shared table's column config, so opening the 2026 phase did not update them. The active phase year is already available app-wide via `DataControlService.reportingCurrentPhase.phaseYear` (set in `getCurrentPhases()` from `GET_versioning(OPEN, REPORTING)`), and is reached in components through the `ApiService` aggregator (`api.dataControlSE.reportingCurrentPhase.phaseYear`).

## Goals / Non-Goals

**Goals:**
- The phase header and target-column year in the three Reporting-by-ToC views show the active phase year dynamically.
- No hardcoded year remains in the affected templates/configs.

**Non-Goals:**
- No nomenclature changes (Indicator name β†’ HLO title, KPI statement, typology, targets) β€” separate scope, tagged to Santi + Ángel.
- No change to `indicator-details` "2024" label or the backend `achieved_in_2024` field.
- No change to the `outcome-indicator-home` "2022-2024" baseline text.
- No "all P/A" rollout beyond what these shared components already cover.

## Decisions

**Decision 1 β€” Bind to `reportingCurrentPhase.phaseYear` (single existing source).**
Use the already-loaded value rather than re-deriving the year. The header bindings become `Reporting Phase {{ api.dataControlSE.reportingCurrentPhase.phaseYear }}`. Rationale: one source of truth, already correct (2026); avoids the fragility of re-querying.

**Decision 2 β€” Shared table column made dynamic via the component's API reference.**
`aow-hlo-table.component.ts` defines the columns array with `'Expected target 2025'`. Build that title from `phaseYear` (e.g., a getter or computed columns reading `this.api.dataControlSE.reportingCurrentPhase.phaseYear`). Because the component is shared by HLO / Intermediate Outcomes / 2030 Outcomes, the single fix covers all three.

**Decision 3 β€” Guard against a null phase year.**
`phaseYear` can be null briefly before `getCurrentPhases()` resolves. The binding must degrade gracefully (e.g., show "Reporting Phase" with no trailing year, or wait for the value) rather than render "Reporting Phase null". Confirm the phase is loaded on these routes before relying on it.

## Risks / Trade-offs

- [`phaseYear` null on first paint β†’ "Reporting Phase null"] β†’ guard the binding (omit the year until loaded).
- [Specs assert the literal "Expected target 2025"] β†’ update the specs to assert the dynamic value with a mocked `reportingCurrentPhase.phaseYear`.
- [Other hardcoded years exist nearby (indicator-details 2024, baseline 2022-2024)] β†’ explicitly left untouched per scope; documented in the proposal and Jira comment so they are not "accidentally fixed".

## Migration Plan

Pure frontend, no migrations. Deploy with the client build. Rollback = revert the commit.

## Open Questions

- None blocking for the basic scope. The nomenclature/backend items are tracked in the Jira comment tagged to Santi + Ángel.
Loading
Loading