fix(ui): hide planning details by default

This commit is contained in:
nessar committed 2026-08-31 00:23:09 +02:00
1 parent d7a13b0b1e
commit fb0c86da3f
4 files changed
+362 -316

No files matched your search

@@ -137,322 +137,333 @@
}
@if (selectedExplanation(); as selected) {
<section class="placement-explanation" aria-labelledby="placement-explanation-title">
<header class="explanation-heading">
<div>
<p class="eyebrow">{{ tr().planning.whyPlacement }}</p>
<h3 id="placement-explanation-title">
{{ selected.placement.date }} · {{ balanceLabel(selected) }}
</h3>
</div>
<nav
class="placement-stepper"
[attr.aria-label]="tr().planning.placementNavigation"
>
<button
type="button"
data-step="first"
[disabled]="selectedPlacementIndex() === 0"
[attr.aria-label]="tr().planning.first"
(click)="stepPlacement('first')"
<details class="plan-details" (toggle)="togglePlanDetails($event)">
<summary>{{ tr().planning.whyPlacement }}</summary>
<section class="placement-explanation" aria-labelledby="placement-explanation-title">
<header class="explanation-heading">
<div>
<h3 id="placement-explanation-title">
{{ selected.placement.date }} · {{ balanceLabel(selected) }}
</h3>
</div>
<nav
class="placement-stepper"
[attr.aria-label]="tr().planning.placementNavigation"
>
<span aria-hidden="true">⏮</span>
</button>
<button
type="button"
data-step="previous"
[disabled]="selectedPlacementIndex() === 0"
[attr.aria-label]="tr().planning.previous"
(click)="stepPlacement('previous')"
>
<span aria-hidden="true">◀</span>
</button>
<output>
{{ tr().planning.placement }} {{ selectedPlacementIndex() + 1 }}
{{ tr().planning.of }} {{ planExplanation()!.placements.length }}
</output>
<button
type="button"
data-step="next"
[disabled]="
selectedPlacementIndex() === planExplanation()!.placements.length - 1
"
[attr.aria-label]="tr().planning.next"
(click)="stepPlacement('next')"
>
<span aria-hidden="true">▶</span>
</button>
<button
type="button"
data-step="last"
[disabled]="
selectedPlacementIndex() === planExplanation()!.placements.length - 1
"
[attr.aria-label]="tr().planning.last"
(click)="stepPlacement('last')"
>
<span aria-hidden="true">⏭</span>
</button>
</nav>
</header>
@if (planExplanation()!.incomplete; as interrupted) {
<div class="incomplete-incumbent" role="status">
<strong>{{ tr().planning.incumbentExplanation }}</strong>
<p>{{ interrupted.reason.message }}</p>
<p>
{{ tr().planning.completedStages }}:
{{
interrupted.completedStages.length
? interrupted.completedStages.join(', ')
: tr().planning.none
}}
</p>
<p>
{{ tr().planning.uncompletedStages }}:
{{
interrupted.uncompletedStages.length
? interrupted.uncompletedStages.join(', ')
: tr().planning.none
}}
</p>
</div>
}
<button
type="button"
data-step="first"
[disabled]="selectedPlacementIndex() === 0"
[attr.aria-label]="tr().planning.first"
(click)="stepPlacement('first')"
>
<span aria-hidden="true">⏮</span>
</button>
<button
type="button"
data-step="previous"
[disabled]="selectedPlacementIndex() === 0"
[attr.aria-label]="tr().planning.previous"
(click)="stepPlacement('previous')"
>
<span aria-hidden="true">◀</span>
</button>
<output>
{{ tr().planning.placement }} {{ selectedPlacementIndex() + 1 }}
{{ tr().planning.of }} {{ planExplanation()!.placements.length }}
</output>
<button
type="button"
data-step="next"
[disabled]="
selectedPlacementIndex() ===
planExplanation()!.placements.length - 1
"
[attr.aria-label]="tr().planning.next"
(click)="stepPlacement('next')"
>
<span aria-hidden="true">▶</span>
</button>
<button
type="button"
data-step="last"
[disabled]="
selectedPlacementIndex() ===
planExplanation()!.placements.length - 1
"
[attr.aria-label]="tr().planning.last"
(click)="stepPlacement('last')"
>
<span aria-hidden="true">⏭</span>
</button>
</nav>
</header>
@if (planExplanation()!.incomplete; as interrupted) {
<div class="incomplete-incumbent" role="status">
<strong>{{ tr().planning.incumbentExplanation }}</strong>
<p>{{ interrupted.reason.message }}</p>
<p>
{{ tr().planning.completedStages }}:
{{
interrupted.completedStages.length
? interrupted.completedStages.join(', ')
: tr().planning.none
}}
</p>
<p>
{{ tr().planning.uncompletedStages }}:
{{
interrupted.uncompletedStages.length
? interrupted.uncompletedStages.join(', ')
: tr().planning.none
}}
</p>
</div>
}
<dl class="explanation-facts compact-facts">
<div>
<dt>{{ tr().planning.balance }}</dt>
<dd>
{{ balanceLabel(selected) }} <code>{{ selected.placement.balanceId }}</code>
@if (selected.balance) {
<br />{{ planningDate(selected.balance.availableFrom) }} –
{{ planningDate(selected.balance.expiryDate) }}
}
</dd>
</div>
<div>
<dt>{{ tr().planning.rules }}</dt>
<dd>
@if (selected.ruleIds.length) {
@for (ruleId of selected.ruleIds; track ruleId; let last = $last) {
{{ ruleLabel(ruleId) }} <code>{{ ruleId }}</code
>{{ last ? '' : ', ' }}
<dl class="explanation-facts compact-facts">
<div>
<dt>{{ tr().planning.balance }}</dt>
<dd>
{{ balanceLabel(selected) }}
<code>{{ selected.placement.balanceId }}</code>
@if (selected.balance) {
<br />{{ planningDate(selected.balance.availableFrom) }} –
{{ planningDate(selected.balance.expiryDate) }}
}
} @else {
{{ tr().planning.noRules }}
}
</dd>
</div>
<div>
<dt>{{ tr().planning.supporting }}</dt>
<dd>{{ selected.supporting ? tr().planning.yes : tr().planning.no }}</dd>
</div>
<div>
<dt>{{ tr().planning.remaining }}</dt>
<dd>
{{ selected.remainingQuantity }}
@if (selected.remainingBalance) {
<br /><small>
{{ formatIdentifier(selected.remainingBalance.classification) }} ·
{{ selected.remainingBalance.reason.message }}
</small>
}
</dd>
</div>
@if (selected.timeOffPeriod; as period) {
<div>
<dt>{{ tr().planning.timeOffPeriod }}</dt>
<dd>{{ period.start }} – {{ period.end }} ({{ period.duration }})</dd>
</dd>
</div>
}
@if (selected.firstDecisiveObjective; as decisive) {
<div>
<dt>{{ tr().planning.firstDecisive }}</dt>
<dd>{{ formatIdentifier(decisive) }}</dd>
<dt>{{ tr().planning.rules }}</dt>
<dd>
@if (selected.ruleIds.length) {
@for (ruleId of selected.ruleIds; track ruleId; let last = $last) {
{{ ruleLabel(ruleId) }} <code>{{ ruleId }}</code
>{{ last ? '' : ', ' }}
}
} @else {
{{ tr().planning.noRules }}
}
</dd>
</div>
}
</dl>
<div>
<dt>{{ tr().planning.supporting }}</dt>
<dd>{{ selected.supporting ? tr().planning.yes : tr().planning.no }}</dd>
</div>
<div>
<dt>{{ tr().planning.remaining }}</dt>
<dd>
{{ selected.remainingQuantity }}
@if (selected.remainingBalance) {
<br /><small>
{{
formatIdentifier(selected.remainingBalance.classification)
}}
·
{{ selected.remainingBalance.reason.message }}
</small>
}
</dd>
</div>
@if (selected.timeOffPeriod; as period) {
<div>
<dt>{{ tr().planning.timeOffPeriod }}</dt>
<dd>{{ period.start }} – {{ period.end }} ({{ period.duration }})</dd>
</div>
}
@if (selected.firstDecisiveObjective; as decisive) {
<div>
<dt>{{ tr().planning.firstDecisive }}</dt>
<dd>{{ formatIdentifier(decisive) }}</dd>
</div>
}
</dl>
<div class="explanation-columns">
<section>
<h4>{{ tr().planning.contributions }}</h4>
<div class="explanation-columns">
<section>
<h4>{{ tr().planning.contributions }}</h4>
<ul>
@for (
contribution of selected.contributions;
track contribution.kind + contribution.code
) {
<li>
<strong>{{ contribution.label }}</strong>
@if (contribution.value !== undefined) {
<span> · {{ contribution.value }}</span>
}
@if (contribution.dates?.length) {
<span> · {{ contribution.dates!.join(', ') }}</span>
}
</li>
}
</ul>
</section>
<section>
<h4>{{ tr().planning.objectiveVector }}</h4>
<ol class="objective-vector">
@for (
objective of selected.objectiveVector.objectives;
track objective.id
) {
<li>
<span>{{ objectiveLabel(objective) }}</span>
<strong>{{ objective.value }}</strong>
<small>{{ formatIdentifier(objective.direction) }}</small>
</li>
}
</ol>
</section>
<section>
<h4>{{ tr().planning.finalMetrics }}</h4>
<dl class="metric-list">
<div>
<dt>{{ tr().planning.unplacedAtRisk }}</dt>
<dd>{{ selected.finalMetrics.unplacedAtRisk }}</dd>
</div>
<div>
<dt>{{ tr().planning.holidayWeekValue }}</dt>
<dd>{{ selected.finalMetrics.holidayWeekValue }}</dd>
</div>
<div>
<dt>{{ tr().planning.daysGained }}</dt>
<dd>{{ selected.finalMetrics.nonWorkingDayGain }}</dd>
</div>
<div>
<dt>{{ tr().planning.longestWorkSequence }}</dt>
<dd>{{ selected.finalMetrics.longestWorkSequence }}</dd>
</div>
<div>
<dt>{{ tr().planning.shortSequenceDeficit }}</dt>
<dd>{{ selected.finalMetrics.shortSequenceDeficit }}</dd>
</div>
<div>
<dt>{{ tr().planning.supportingPlacements }}</dt>
<dd>{{ selected.finalMetrics.supportingPlacements }}</dd>
</div>
<div>
<dt>{{ tr().planning.preferenceSatisfaction }}</dt>
<dd>
{{
selected.finalMetrics.preferenceSatisfaction.join(', ') ||
tr().planning.none
}}
</dd>
</div>
</dl>
</section>
</div>
<details class="comparison-details">
<summary>{{ tr().planning.removedRestoredComparison }}</summary>
<p>{{ tr().planning.comparisonHelp }}</p>
<ul>
@for (
contribution of selected.contributions;
track contribution.kind + contribution.code
) {
@for (delta of selected.comparison.delta; track delta.id) {
<li>
<strong>{{ contribution.label }}</strong>
@if (contribution.value !== undefined) {
<span> · {{ contribution.value }}</span>
}
@if (contribution.dates?.length) {
<span> · {{ contribution.dates!.join(', ') }}</span>
}
<strong>{{ formatIdentifier(delta.id) }}:</strong> {{ delta.value }}
</li>
}
</ul>
</section>
<section>
<h4>{{ tr().planning.objectiveVector }}</h4>
<ol class="objective-vector">
@for (
objective of selected.objectiveVector.objectives;
track objective.id
) {
<li>
<span>{{ objectiveLabel(objective) }}</span>
<strong>{{ objective.value }}</strong>
<small>{{ formatIdentifier(objective.direction) }}</small>
</li>
}
</ol>
</section>
<section>
<h4>{{ tr().planning.finalMetrics }}</h4>
<dl class="metric-list">
<div>
<dt>{{ tr().planning.unplacedAtRisk }}</dt>
<dd>{{ selected.finalMetrics.unplacedAtRisk }}</dd>
</div>
<div>
<dt>{{ tr().planning.holidayWeekValue }}</dt>
<dd>{{ selected.finalMetrics.holidayWeekValue }}</dd>
</div>
<div>
<dt>{{ tr().planning.daysGained }}</dt>
<dd>{{ selected.finalMetrics.nonWorkingDayGain }}</dd>
</div>
<div>
<dt>{{ tr().planning.longestWorkSequence }}</dt>
<dd>{{ selected.finalMetrics.longestWorkSequence }}</dd>
</div>
<div>
<dt>{{ tr().planning.shortSequenceDeficit }}</dt>
<dd>{{ selected.finalMetrics.shortSequenceDeficit }}</dd>
</div>
<div>
<dt>{{ tr().planning.supportingPlacements }}</dt>
<dd>{{ selected.finalMetrics.supportingPlacements }}</dd>
</div>
<div>
<dt>{{ tr().planning.preferenceSatisfaction }}</dt>
<dd>
{{
selected.finalMetrics.preferenceSatisfaction.join(', ') ||
tr().planning.none
}}
</dd>
</div>
</dl>
</section>
</div>
</details>
<details class="comparison-details">
<summary>{{ tr().planning.removedRestoredComparison }}</summary>
<p>{{ tr().planning.comparisonHelp }}</p>
<ul>
@for (delta of selected.comparison.delta; track delta.id) {
<li>
<strong>{{ formatIdentifier(delta.id) }}:</strong> {{ delta.value }}
</li>
}
</ul>
</details>
<section class="counterfactual-panel" aria-labelledby="counterfactual-title">
<h4 id="counterfactual-title">{{ tr().planning.counterfactual }}</h4>
<p>{{ tr().planning.hypothetical }}</p>
@if (counterfactual(); as analysis) {
<p>
<strong
>{{ analysis.placement.date }} ·
{{ analysis.placement.balanceId }}</strong
>
</p>
}
@if (counterfactualLoading()) {
<p role="status">{{ tr().planning.counterfactualLoading }}</p>
} @else if (counterfactualError()) {
<p class="inline-error" role="alert">
{{ tr().planning.counterfactualError }} {{ counterfactualError() }}
</p>
} @else if (selectedCounterfactualResult(); as result) {
<div class="counterfactual-result">
<h5>{{ result.date }}</h5>
<p class="status-badge" [class.ineligible]="result.status === 'ineligible'">
{{
result.status === 'eligible'
? tr().planning.eligible
: tr().planning.ineligible
}}
<section class="counterfactual-panel" aria-labelledby="counterfactual-title">
<h4 id="counterfactual-title">{{ tr().planning.counterfactual }}</h4>
<p>{{ tr().planning.hypothetical }}</p>
@if (counterfactual(); as analysis) {
<p>
<strong
>{{ analysis.placement.date }} ·
{{ analysis.placement.balanceId }}</strong
>
</p>
@if (result.historicalStatus) {
<p>
<strong>{{ tr().planning.historical }}:</strong>
{{ traceStatusLabel(result.historicalStatus) }}
</p>
}
@if (result.reason) {
<p>
<strong>{{ tr().planning.reason }}:</strong>
{{ result.reason.message }}
</p>
}
@if (result.comparison; as comparison) {
<p>
<strong>{{ tr().planning.firstDecisive }}:</strong>
}
@if (counterfactualLoading()) {
<p role="status">{{ tr().planning.counterfactualLoading }}</p>
} @else if (counterfactualError()) {
<p class="inline-error" role="alert">
{{ tr().planning.counterfactualError }} {{ counterfactualError() }}
</p>
} @else if (selectedCounterfactualResult(); as result) {
<div class="counterfactual-result">
<h5>{{ result.date }}</h5>
<p
class="status-badge"
[class.ineligible]="result.status === 'ineligible'"
>
{{
comparison.firstDecisiveObjective
? formatIdentifier(comparison.firstDecisiveObjective)
: tr().planning.none
result.status === 'eligible'
? tr().planning.eligible
: tr().planning.ineligible
}}
</p>
<ul>
@for (delta of comparison.delta; track delta.id) {
<li>
<strong>{{ formatIdentifier(delta.id) }}:</strong>
{{ delta.value }}
</li>
}
</ul>
<details>
<summary>{{ tr().planning.fullComparison }}</summary>
<div class="comparison-vectors">
<div>
<strong>{{ tr().planning.before }}</strong>
<ol>
@for (
objective of comparison.before.objectives;
track objective.id
) {
<li>
{{ objectiveLabel(objective) }}:
{{ objective.value }}
</li>
}
</ol>
@if (result.historicalStatus) {
<p>
<strong>{{ tr().planning.historical }}:</strong>
{{ traceStatusLabel(result.historicalStatus) }}
</p>
}
@if (result.reason) {
<p>
<strong>{{ tr().planning.reason }}:</strong>
{{ result.reason.message }}
</p>
}
@if (result.comparison; as comparison) {
<p>
<strong>{{ tr().planning.firstDecisive }}:</strong>
{{
comparison.firstDecisiveObjective
? formatIdentifier(comparison.firstDecisiveObjective)
: tr().planning.none
}}
</p>
<ul>
@for (delta of comparison.delta; track delta.id) {
<li>
<strong>{{ formatIdentifier(delta.id) }}:</strong>
{{ delta.value }}
</li>
}
</ul>
<details>
<summary>{{ tr().planning.fullComparison }}</summary>
<div class="comparison-vectors">
<div>
<strong>{{ tr().planning.before }}</strong>
<ol>
@for (
objective of comparison.before.objectives;
track objective.id
) {
<li>
{{ objectiveLabel(objective) }}:
{{ objective.value }}
</li>
}
</ol>
</div>
<div>
<strong>{{ tr().planning.after }}</strong>
<ol>
@for (
objective of comparison.after.objectives;
track objective.id
) {
<li>
{{ objectiveLabel(objective) }}:
{{ objective.value }}
</li>
}
</ol>
</div>
</div>
<div>
<strong>{{ tr().planning.after }}</strong>
<ol>
@for (
objective of comparison.after.objectives;
track objective.id
) {
<li>
{{ objectiveLabel(objective) }}:
{{ objective.value }}
</li>
}
</ol>
</div>
</div>
</details>
}
</div>
}
</details>
}
</div>
}
</section>
</section>
</section>
</details>
} @else if (explanationError()) {
<p class="inline-error" role="alert">
{{ tr().planning.explanationError }} {{ explanationError()!.message }}
@@ -482,17 +493,20 @@
<span class="legend-swatch today" aria-hidden="true"></span
>{{ tr().dayTypes.today }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">◎</span
>{{ tr().planning.currentPlacement }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">✓</span>{{ tr().planning.eligible }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">×</span
>{{ tr().planning.ineligible }}
</li>
@if (planDetailsOpen() || traceReplay()) {
<li>
<span class="legend-symbol" aria-hidden="true">◎</span
>{{ tr().planning.currentPlacement }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">✓</span
>{{ tr().planning.eligible }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">×</span
>{{ tr().planning.ineligible }}
</li>
}
</ul>
</div>