- @if (selected.ruleIds.length) {
- @for (ruleId of selected.ruleIds; track ruleId; let last = $last) {
- {{ ruleLabel(ruleId) }} {{ ruleId }}{{ last ? '' : ', ' }}
+
+
+
- {{ tr().planning.balance }}
+ -
+ {{ balanceLabel(selected) }}
+
{{ selected.placement.balanceId }}
+ @if (selected.balance) {
+
{{ planningDate(selected.balance.availableFrom) }} –
+ {{ planningDate(selected.balance.expiryDate) }}
}
- } @else {
- {{ tr().planning.noRules }}
- }
-
-
-
-
- {{ tr().planning.supporting }}
- - {{ selected.supporting ? tr().planning.yes : tr().planning.no }}
-
-
-
- {{ tr().planning.remaining }}
- -
- {{ selected.remainingQuantity }}
- @if (selected.remainingBalance) {
-
- {{ formatIdentifier(selected.remainingBalance.classification) }} ·
- {{ selected.remainingBalance.reason.message }}
-
- }
-
-
- @if (selected.timeOffPeriod; as period) {
-
-
- {{ tr().planning.timeOffPeriod }}
- - {{ period.start }} – {{ period.end }} ({{ period.duration }})
+
- }
- @if (selected.firstDecisiveObjective; as decisive) {
-
- {{ tr().planning.firstDecisive }}
- - {{ formatIdentifier(decisive) }}
+ - {{ tr().planning.rules }}
+ -
+ @if (selected.ruleIds.length) {
+ @for (ruleId of selected.ruleIds; track ruleId; let last = $last) {
+ {{ ruleLabel(ruleId) }}
{{ ruleId }}{{ last ? '' : ', ' }}
+ }
+ } @else {
+ {{ tr().planning.noRules }}
+ }
+
- }
-
+
+
{{ tr().planning.supporting }}
+ {{ selected.supporting ? tr().planning.yes : tr().planning.no }}
+
+
+
{{ tr().planning.remaining }}
+
+ {{ selected.remainingQuantity }}
+ @if (selected.remainingBalance) {
+
+ {{
+ formatIdentifier(selected.remainingBalance.classification)
+ }}
+ ·
+ {{ selected.remainingBalance.reason.message }}
+
+ }
+
+
+ @if (selected.timeOffPeriod; as period) {
+
+
{{ tr().planning.timeOffPeriod }}
+ {{ period.start }} – {{ period.end }} ({{ period.duration }})
+
+ }
+ @if (selected.firstDecisiveObjective; as decisive) {
+
+
{{ tr().planning.firstDecisive }}
+ {{ formatIdentifier(decisive) }}
+
+ }
+
-
-
- {{ tr().planning.contributions }}
+
+
+ {{ tr().planning.contributions }}
+
+ @for (
+ contribution of selected.contributions;
+ track contribution.kind + contribution.code
+ ) {
+ -
+ {{ contribution.label }}
+ @if (contribution.value !== undefined) {
+ · {{ contribution.value }}
+ }
+ @if (contribution.dates?.length) {
+ · {{ contribution.dates!.join(', ') }}
+ }
+
+ }
+
+
+
+ {{ tr().planning.objectiveVector }}
+
+ @for (
+ objective of selected.objectiveVector.objectives;
+ track objective.id
+ ) {
+ -
+ {{ objectiveLabel(objective) }}
+ {{ objective.value }}
+ {{ formatIdentifier(objective.direction) }}
+
+ }
+
+
+
+ {{ tr().planning.finalMetrics }}
+
+
+
- {{ tr().planning.unplacedAtRisk }}
+ - {{ selected.finalMetrics.unplacedAtRisk }}
+
+
+
- {{ tr().planning.holidayWeekValue }}
+ - {{ selected.finalMetrics.holidayWeekValue }}
+
+
+
- {{ tr().planning.daysGained }}
+ - {{ selected.finalMetrics.nonWorkingDayGain }}
+
+
+
- {{ tr().planning.longestWorkSequence }}
+ - {{ selected.finalMetrics.longestWorkSequence }}
+
+
+
- {{ tr().planning.shortSequenceDeficit }}
+ - {{ selected.finalMetrics.shortSequenceDeficit }}
+
+
+
- {{ tr().planning.supportingPlacements }}
+ - {{ selected.finalMetrics.supportingPlacements }}
+
+
+
- {{ tr().planning.preferenceSatisfaction }}
+ -
+ {{
+ selected.finalMetrics.preferenceSatisfaction.join(', ') ||
+ tr().planning.none
+ }}
+
+
+
+
+
+
+
+ {{ tr().planning.removedRestoredComparison }}
+ {{ tr().planning.comparisonHelp }}
- @for (
- contribution of selected.contributions;
- track contribution.kind + contribution.code
- ) {
+ @for (delta of selected.comparison.delta; track delta.id) {
-
- {{ contribution.label }}
- @if (contribution.value !== undefined) {
- · {{ contribution.value }}
- }
- @if (contribution.dates?.length) {
- · {{ contribution.dates!.join(', ') }}
- }
+ {{ formatIdentifier(delta.id) }}: {{ delta.value }}
}
-
-
- {{ tr().planning.objectiveVector }}
-
- @for (
- objective of selected.objectiveVector.objectives;
- track objective.id
- ) {
- -
- {{ objectiveLabel(objective) }}
- {{ objective.value }}
- {{ formatIdentifier(objective.direction) }}
-
- }
-
-
-
- {{ tr().planning.finalMetrics }}
-
-
-
- {{ tr().planning.unplacedAtRisk }}
- - {{ selected.finalMetrics.unplacedAtRisk }}
-
-
-
- {{ tr().planning.holidayWeekValue }}
- - {{ selected.finalMetrics.holidayWeekValue }}
-
-
-
- {{ tr().planning.daysGained }}
- - {{ selected.finalMetrics.nonWorkingDayGain }}
-
-
-
- {{ tr().planning.longestWorkSequence }}
- - {{ selected.finalMetrics.longestWorkSequence }}
-
-
-
- {{ tr().planning.shortSequenceDeficit }}
- - {{ selected.finalMetrics.shortSequenceDeficit }}
-
-
-
- {{ tr().planning.supportingPlacements }}
- - {{ selected.finalMetrics.supportingPlacements }}
-
-
-
- {{ tr().planning.preferenceSatisfaction }}
- -
- {{
- selected.finalMetrics.preferenceSatisfaction.join(', ') ||
- tr().planning.none
- }}
-
-
-
-
-
+
-
- {{ tr().planning.removedRestoredComparison }}
- {{ tr().planning.comparisonHelp }}
-
- @for (delta of selected.comparison.delta; track delta.id) {
- -
- {{ formatIdentifier(delta.id) }}: {{ delta.value }}
-
- }
-
-
-
-
- {{ tr().planning.counterfactual }}
- {{ tr().planning.hypothetical }}
- @if (counterfactual(); as analysis) {
-
- {{ analysis.placement.date }} ·
- {{ analysis.placement.balanceId }}
-
- }
- @if (counterfactualLoading()) {
- {{ tr().planning.counterfactualLoading }}
- } @else if (counterfactualError()) {
-
- {{ tr().planning.counterfactualError }} {{ counterfactualError() }}
-
- } @else if (selectedCounterfactualResult(); as result) {
-
-
{{ result.date }}
-
- {{
- result.status === 'eligible'
- ? tr().planning.eligible
- : tr().planning.ineligible
- }}
+
+ {{ tr().planning.counterfactual }}
+ {{ tr().planning.hypothetical }}
+ @if (counterfactual(); as analysis) {
+
+ {{ analysis.placement.date }} ·
+ {{ analysis.placement.balanceId }}
- @if (result.historicalStatus) {
-
- {{ tr().planning.historical }}:
- {{ traceStatusLabel(result.historicalStatus) }}
-
- }
- @if (result.reason) {
-
- {{ tr().planning.reason }}:
- {{ result.reason.message }}
-
- }
- @if (result.comparison; as comparison) {
-
- {{ tr().planning.firstDecisive }}:
+ }
+ @if (counterfactualLoading()) {
+
{{ tr().planning.counterfactualLoading }}
+ } @else if (counterfactualError()) {
+
+ {{ tr().planning.counterfactualError }} {{ counterfactualError() }}
+
+ } @else if (selectedCounterfactualResult(); as result) {
+
+
{{ result.date }}
+
{{
- comparison.firstDecisiveObjective
- ? formatIdentifier(comparison.firstDecisiveObjective)
- : tr().planning.none
+ result.status === 'eligible'
+ ? tr().planning.eligible
+ : tr().planning.ineligible
}}
-
- @for (delta of comparison.delta; track delta.id) {
- -
- {{ formatIdentifier(delta.id) }}:
- {{ delta.value }}
-
- }
-
-
- {{ tr().planning.fullComparison }}
-
-
-
{{ tr().planning.before }}
-
- @for (
- objective of comparison.before.objectives;
- track objective.id
- ) {
- -
- {{ objectiveLabel(objective) }}:
- {{ objective.value }}
-
- }
-
+ @if (result.historicalStatus) {
+
+ {{ tr().planning.historical }}:
+ {{ traceStatusLabel(result.historicalStatus) }}
+
+ }
+ @if (result.reason) {
+
+ {{ tr().planning.reason }}:
+ {{ result.reason.message }}
+
+ }
+ @if (result.comparison; as comparison) {
+
+ {{ tr().planning.firstDecisive }}:
+ {{
+ comparison.firstDecisiveObjective
+ ? formatIdentifier(comparison.firstDecisiveObjective)
+ : tr().planning.none
+ }}
+
+
+ @for (delta of comparison.delta; track delta.id) {
+ -
+ {{ formatIdentifier(delta.id) }}:
+ {{ delta.value }}
+
+ }
+
+
+ {{ tr().planning.fullComparison }}
+
+
+
{{ tr().planning.before }}
+
+ @for (
+ objective of comparison.before.objectives;
+ track objective.id
+ ) {
+ -
+ {{ objectiveLabel(objective) }}:
+ {{ objective.value }}
+
+ }
+
+
+
+
{{ tr().planning.after }}
+
+ @for (
+ objective of comparison.after.objectives;
+ track objective.id
+ ) {
+ -
+ {{ objectiveLabel(objective) }}:
+ {{ objective.value }}
+
+ }
+
+
-
-
{{ tr().planning.after }}
-
- @for (
- objective of comparison.after.objectives;
- track objective.id
- ) {
- -
- {{ objectiveLabel(objective) }}:
- {{ objective.value }}
-
- }
-
-
-
-
- }
-
- }
+
+ }
+
+ }
+
-
+
} @else if (explanationError()) {
{{ tr().planning.explanationError }} {{ explanationError()!.message }}
@@ -482,17 +493,20 @@
{{ tr().dayTypes.today }}
-
- ◎{{ tr().planning.currentPlacement }}
-
-
- ✓{{ tr().planning.eligible }}
-
-
- ×{{ tr().planning.ineligible }}
-
+ @if (planDetailsOpen() || traceReplay()) {
+
+ ◎{{ tr().planning.currentPlacement }}
+
+
+ ✓{{ tr().planning.eligible }}
+
+
+ ×{{ tr().planning.ineligible }}
+
+ }
diff --git a/src/app/components/calendar-year/calendar-year.scss b/src/app/components/calendar-year/calendar-year.scss
index 824c8fe..d5548a1 100644
--- a/src/app/components/calendar-year/calendar-year.scss
+++ b/src/app/components/calendar-year/calendar-year.scss
@@ -160,6 +160,7 @@
.reason-list,
.outcome-explanation,
+.plan-details,
.placement-explanation,
.trace-explorer {
margin: 0;
@@ -169,6 +170,12 @@
background: var(--ui-card-bg, #fff);
}
+.plan-details .placement-explanation {
+ margin-block-start: 1rem;
+ padding: 0;
+ border: 0;
+}
+
.outcome-explanation,
.placement-explanation {
display: grid;
@@ -360,6 +367,7 @@ code {
}
.comparison-details summary,
+.plan-details > summary,
.trace-explorer > summary,
.candidate-list summary {
cursor: pointer;
diff --git a/src/app/components/calendar-year/calendar-year.ts b/src/app/components/calendar-year/calendar-year.ts
index 5978157..f07f02d 100644
--- a/src/app/components/calendar-year/calendar-year.ts
+++ b/src/app/components/calendar-year/calendar-year.ts
@@ -61,6 +61,7 @@ export class CalendarYear {
protected readonly traceLoading = signal(false);
protected readonly traceResolved = signal(false);
protected readonly selectedPlacementIndex = signal(0);
+ protected readonly planDetailsOpen = signal(false);
protected readonly counterfactual = signal(null);
protected readonly selectedCounterfactualDate = signal(null);
protected readonly counterfactualLoading = signal(false);
@@ -146,7 +147,7 @@ export class CalendarYear {
if (this.counterfactual())
return this.selectedCounterfactualDate() ?? this.counterfactual()!.placement.date;
if (this.traceReplay()) return this.selectedTraceDecision()?.dates[0] ?? null;
- return this.selectedExplanation()?.placement.date ?? null;
+ return this.planDetailsOpen() ? (this.selectedExplanation()?.placement.date ?? null) : null;
});
protected readonly dateStatuses = computed>>(() => {
@@ -356,6 +357,7 @@ export class CalendarYear {
this.traceLoading.set(true);
this.traceResolved.set(false);
this.selectedPlacementIndex.set(0);
+ this.planDetailsOpen.set(false);
this.clearCounterfactual();
this.tracePhaseIndex.set(0);
this.traceDecisionIndex.set(0);
@@ -370,8 +372,6 @@ export class CalendarYear {
void calculation.explanation.then((explanation) => {
if (version !== this.calculationVersion) return;
this.planningExplanation.set(explanation ?? null);
- if (explanation?.kind === 'PlanExplanation' && explanation.placements.length)
- this.selectPlacement(0);
});
void calculation.explanationError.then((reason) => {
if (version === this.calculationVersion) this.explanationError.set(reason ?? null);
@@ -433,6 +433,13 @@ export class CalendarYear {
void this.analyzeSelection(explanation.placements[selected].placement);
}
+ protected togglePlanDetails(event: Event): void {
+ const open = (event.currentTarget as HTMLDetailsElement).open;
+ this.planDetailsOpen.set(open);
+ if (open) this.selectPlacement(this.selectedPlacementIndex());
+ else this.clearCounterfactual();
+ }
+
protected stepPlacement(step: 'first' | 'previous' | 'next' | 'last'): void {
const last = (this.planExplanation()?.placements.length ?? 1) - 1;
const current = this.selectedPlacementIndex();
@@ -448,7 +455,7 @@ export class CalendarYear {
}
protected selectCalendarDate(date: string): void {
- if (!this.traceReplay()) {
+ if (!this.traceReplay() && this.planDetailsOpen()) {
const index = this.planExplanation()?.placements.findIndex(
(explanation) => explanation.placement.date === date,
);