From d3658f490d8d7bd3ff702f3cca44bcc56c321cea Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 27 Apr 2026 15:01:32 +0200 Subject: [PATCH] Add coach handoff actions to dashboard --- .../HyperTwistCoachDashboardWidget.cpp | 185 ++++++++++++++++++ .../HyperTwistCoachDashboardWidget.h | 33 ++++ 2 files changed, 218 insertions(+) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index bed72b3..e070438 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -603,6 +603,33 @@ void UHyperTwistCoachDashboardWidget::HandleDismissRecapClicked() ClearRetainedRunRecap(); } +void UHyperTwistCoachDashboardWidget::HandleStartQueuedRunClicked() +{ + StartNextQueuedCoachRun(DefaultCoachMaxCases); + SyncRetainedRunRecap(); + SyncSelectedQueueEntry(); + SyncSelectedAttempt(); + UpdateDashboardPresentation(); +} + +void UHyperTwistCoachDashboardWidget::HandleStartFollowUpRunClicked() +{ + StartCoachFollowUpRun(DefaultCoachMaxCases); + SyncRetainedRunRecap(); + SyncSelectedQueueEntry(); + SyncSelectedAttempt(); + UpdateDashboardPresentation(); +} + +void UHyperTwistCoachDashboardWidget::HandleStartRecommendedRunClicked() +{ + StartCoachRecommendedRun(DefaultCoachMaxCases); + SyncRetainedRunRecap(); + SyncSelectedQueueEntry(); + SyncSelectedAttempt(); + UpdateDashboardPresentation(); +} + void UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked() { CompleteActiveTrainingRun(false); @@ -834,6 +861,18 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() TEXT("CoachRunRecapDetail"), 2 ); + CoachHandoffHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachHandoffHeader"), + 2 + ); + CoachHandoffStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachHandoffStatus"), + 2 + ); UHorizontalBox* RunRecapActionRow = WidgetTree->ConstructWidget( UHorizontalBox::StaticClass(), TEXT("CoachDashboardRunRecapActions") @@ -867,6 +906,50 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() ); DismissRecapButtonLabel = DismissRecapLabelRaw; } + UHorizontalBox* CoachHandoffActionRow = WidgetTree->ConstructWidget( + UHorizontalBox::StaticClass(), + TEXT("CoachDashboardHandoffActions") + ); + if (CoachHandoffActionRow != nullptr) + { + if (UVerticalBoxSlot* CoachHandoffActionRowSlot = RootLayout->AddChildToVerticalBox(CoachHandoffActionRow)) + { + CoachHandoffActionRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f)); + } + + UTextBlock* StartQueuedRunLabelRaw = nullptr; + StartQueuedRunButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + CoachHandoffActionRow, + TEXT("StartQueuedRunButton"), + TEXT("StartQueuedRunButtonLabel"), + TEXT("Start Queue"), + StartQueuedRunLabelRaw + ); + StartQueuedRunButtonLabel = StartQueuedRunLabelRaw; + + UTextBlock* StartFollowUpRunLabelRaw = nullptr; + StartFollowUpRunButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + CoachHandoffActionRow, + TEXT("StartFollowUpRunButton"), + TEXT("StartFollowUpRunButtonLabel"), + TEXT("Start Follow-Up"), + StartFollowUpRunLabelRaw + ); + StartFollowUpRunButtonLabel = StartFollowUpRunLabelRaw; + + UTextBlock* StartRecommendedRunLabelRaw = nullptr; + StartRecommendedRunButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + CoachHandoffActionRow, + TEXT("StartRecommendedRunButton"), + TEXT("StartRecommendedRunButtonLabel"), + TEXT("Start Recommended"), + StartRecommendedRunLabelRaw + ); + StartRecommendedRunButtonLabel = StartRecommendedRunLabelRaw; + } LastAttemptTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( WidgetTree, RootLayout, @@ -1384,6 +1467,18 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() { DismissRecapButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleDismissRecapClicked); } + if (StartQueuedRunButton != nullptr) + { + StartQueuedRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleStartQueuedRunClicked); + } + if (StartFollowUpRunButton != nullptr) + { + StartFollowUpRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleStartFollowUpRunClicked); + } + if (StartRecommendedRunButton != nullptr) + { + StartRecommendedRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleStartRecommendedRunClicked); + } if (CompleteRunButton != nullptr) { CompleteRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked); @@ -1770,6 +1865,58 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() || CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed || CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted); const bool bCanDismissStoredRecap = !bHasCurrentRunRecap && bHasRetainedRunRecap; + const bool bCanUseCoachHandoff = !bHasLiveAttemptTimer && !bHasActiveRun; + FString CoachHandoffStatusLine; + if (bHasLiveAttemptTimer) + { + CoachHandoffStatusLine = + TEXT("Coach handoff: disabled while a live attempt is running or under inspection."); + } + else if (bHasActiveRun) + { + CoachHandoffStatusLine = + TEXT("Coach handoff: finish, clear, or complete the active run before launching another coach-directed run."); + } + else + { + const FString QueueLine = CachedCoachPanelState.bCanStartQueuedRun + ? FString::Printf( + TEXT("queue ready from %s"), + CachedCoachPanelState.NextQueueSourceLabel.IsEmpty() + ? TEXT("n/a") + : *CachedCoachPanelState.NextQueueSourceLabel + ) + : TEXT("queue not ready"); + const FString FollowUpLine = CachedCoachPanelState.bCanStartCoachFollowUpRun + ? FString::Printf( + TEXT("follow-up %s (%d cases)"), + CachedCoachFollowUpBrief.Headline.IsEmpty() ? TEXT("ready") : *CachedCoachFollowUpBrief.Headline, + CachedCoachFollowUpBrief.FocusCaseIds.Num() + ) + : TEXT("follow-up not ready"); + const FString RecommendedLine = CachedCoachPanelState.bCanStartCoachRecommendedRun + ? FString::Printf( + TEXT("recommended %s (%d cases)"), + CachedCoachBrief.Headline.IsEmpty() ? TEXT("ready") : *CachedCoachBrief.Headline, + CachedCoachBrief.FocusCaseIds.Num() + ) + : TEXT("recommended not ready"); + CoachHandoffStatusLine = FString::Printf( + TEXT("Coach handoff: %s | %s | %s"), + *QueueLine, + *FollowUpLine, + *RecommendedLine + ); + } + const FString QueuedRunLabel = CachedCoachPanelState.NextQueueSourceLabel.IsEmpty() + ? TEXT("Start Queue") + : FString::Printf(TEXT("Queue: %s"), *CachedCoachPanelState.NextQueueSourceLabel); + const FString FollowUpRunLabel = CachedCoachFollowUpBrief.PrimaryActionLabel.IsEmpty() + ? TEXT("Start Follow-Up") + : CachedCoachFollowUpBrief.PrimaryActionLabel; + const FString RecommendedRunLabel = CachedCoachBrief.PrimaryActionLabel.IsEmpty() + ? TEXT("Start Recommended") + : CachedCoachBrief.PrimaryActionLabel; if (HeadlineTextBlock != nullptr) { @@ -1899,6 +2046,44 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { DismissRecapButton->SetIsEnabled(bCanDismissStoredRecap); } + if (CoachHandoffHeaderTextBlock != nullptr) + { + CoachHandoffHeaderTextBlock->SetText(FText::FromString(TEXT("[Coach Handoff]"))); + } + if (CoachHandoffStatusTextBlock != nullptr) + { + CoachHandoffStatusTextBlock->SetText(FText::FromString(CoachHandoffStatusLine)); + } + if (StartQueuedRunButtonLabel != nullptr) + { + StartQueuedRunButtonLabel->SetText(FText::FromString(QueuedRunLabel)); + } + if (StartQueuedRunButton != nullptr) + { + StartQueuedRunButton->SetIsEnabled(bCanUseCoachHandoff && CachedCoachPanelState.bCanStartQueuedRun); + } + if (StartFollowUpRunButtonLabel != nullptr) + { + StartFollowUpRunButtonLabel->SetText(FText::FromString(FollowUpRunLabel)); + } + if (StartFollowUpRunButton != nullptr) + { + StartFollowUpRunButton->SetIsEnabled( + bCanUseCoachHandoff + && CachedCoachPanelState.bCanStartCoachFollowUpRun + && CachedCoachPanelState.bHasFollowUpGuidance + ); + } + if (StartRecommendedRunButtonLabel != nullptr) + { + StartRecommendedRunButtonLabel->SetText(FText::FromString(RecommendedRunLabel)); + } + if (StartRecommendedRunButton != nullptr) + { + StartRecommendedRunButton->SetIsEnabled( + bCanUseCoachHandoff && CachedCoachPanelState.bCanStartCoachRecommendedRun + ); + } if (LastAttemptTextBlock != nullptr) { if (ReviewedAttempt != nullptr) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index 6a8013c..6f9c924 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -163,6 +163,12 @@ protected: UPROPERTY(Transient) TObjectPtr RunRecapDetailTextBlock = nullptr; + UPROPERTY(Transient) + TObjectPtr CoachHandoffHeaderTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr CoachHandoffStatusTextBlock = nullptr; + UPROPERTY(Transient) TObjectPtr RepeatRunButton = nullptr; @@ -175,6 +181,24 @@ protected: UPROPERTY(Transient) TObjectPtr DismissRecapButtonLabel = nullptr; + UPROPERTY(Transient) + TObjectPtr StartQueuedRunButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr StartQueuedRunButtonLabel = nullptr; + + UPROPERTY(Transient) + TObjectPtr StartFollowUpRunButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr StartFollowUpRunButtonLabel = nullptr; + + UPROPERTY(Transient) + TObjectPtr StartRecommendedRunButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr StartRecommendedRunButtonLabel = nullptr; + UPROPERTY(Transient) TObjectPtr LastAttemptTextBlock = nullptr; @@ -406,6 +430,15 @@ protected: UFUNCTION() void HandleDismissRecapClicked(); + UFUNCTION() + void HandleStartQueuedRunClicked(); + + UFUNCTION() + void HandleStartFollowUpRunClicked(); + + UFUNCTION() + void HandleStartRecommendedRunClicked(); + UFUNCTION() void HandleCompleteRunClicked();