import { useRouter } from "next/navigation"; import { FormEvent, useEffect, useRef, useState } from "react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { useI18n } from "@/features/i18n/model/useI18n"; import { DEBRIEF_STATUS_OPTIONS } from "@/shared/config/i18n"; import { findRouteById } from "@/shared/config/routes"; import { saveCurrentVoyage, saveToHistory } from "@/shared/lib/store"; import { Voyage, VoyageStatus } from "@/shared/types"; const FINISH_HOLD_MS = 1000; const HOLD_STAGE_ONE_MS = 100; const HOLD_STAGE_ONE_PROGRESS = 0.2; type FlightHudWidgetProps = { voyage: Voyage | null; isPaused: boolean; formattedTime: string; isCountdownCompleted: boolean; handlePauseToggle: () => void; handleFinish: () => Voyage | null; }; export function FlightHudWidget({ voyage, isPaused, formattedTime, isCountdownCompleted, handlePauseToggle, handleFinish, }: FlightHudWidgetProps) { const { t } = useI18n(); const router = useRouter(); const [isDebriefOpen, setIsDebriefOpen] = useState(false); const [finishedVoyage, setFinishedVoyage] = useState(null); const [status, setStatus] = useState(null); const [progress, setProgress] = useState(""); const [holdProgress, setHoldProgress] = useState(0); const holdStartAtRef = useRef(null); const holdRafRef = useRef(null); const isHoldCompletedRef = useRef(false); const openDebriefModal = () => { const endedVoyage = handleFinish(); if (!endedVoyage) return; setFinishedVoyage(endedVoyage); setStatus(null); setProgress(""); setIsDebriefOpen(true); }; const handleDebriefSubmit = (event: FormEvent) => { event.preventDefault(); if (!status || !finishedVoyage) return; const finalVoyage: Voyage = { ...finishedVoyage, status, debriefProgress: progress, }; saveToHistory(finalVoyage); saveCurrentVoyage(null); setIsDebriefOpen(false); router.push("/log"); }; const stopHoldLoop = () => { if (holdRafRef.current !== null) { cancelAnimationFrame(holdRafRef.current); holdRafRef.current = null; } holdStartAtRef.current = null; }; const resetHold = () => { stopHoldLoop(); isHoldCompletedRef.current = false; setHoldProgress(0); }; const openDebriefByHold = () => { isHoldCompletedRef.current = true; stopHoldLoop(); setHoldProgress(1); requestAnimationFrame(() => { resetHold(); openDebriefModal(); }); }; const tickHoldProgress = (timestamp: number) => { if (holdStartAtRef.current === null) return; const elapsed = timestamp - holdStartAtRef.current; const nextProgress = (() => { if (elapsed <= HOLD_STAGE_ONE_MS) { return Math.min( HOLD_STAGE_ONE_PROGRESS, (elapsed / HOLD_STAGE_ONE_MS) * HOLD_STAGE_ONE_PROGRESS, ); } const stageTwoElapsed = elapsed - HOLD_STAGE_ONE_MS; const stageTwoDuration = FINISH_HOLD_MS - HOLD_STAGE_ONE_MS; const stageTwoProgressRatio = Math.min(1, stageTwoElapsed / stageTwoDuration); return Math.min( 1, HOLD_STAGE_ONE_PROGRESS + stageTwoProgressRatio * (1 - HOLD_STAGE_ONE_PROGRESS), ); })(); setHoldProgress(nextProgress); if (nextProgress >= 1) { openDebriefByHold(); return; } holdRafRef.current = requestAnimationFrame(tickHoldProgress); }; const startHoldToFinish = () => { if (isDebriefOpen) return; resetHold(); holdStartAtRef.current = performance.now(); setHoldProgress(0); holdRafRef.current = requestAnimationFrame(tickHoldProgress); }; const cancelHoldToFinish = () => { if (isHoldCompletedRef.current) return; resetHold(); }; useEffect(() => { return () => { if (holdRafRef.current !== null) { cancelAnimationFrame(holdRafRef.current); } }; }, []); if (!voyage) return null; const route = findRouteById(voyage.routeId); const routeName = route ? t(route.nameKey, undefined, voyage.routeName) : voyage.routeName; const statusOptions = DEBRIEF_STATUS_OPTIONS.map((option) => ({ value: option.value as VoyageStatus, label: t(option.labelKey), desc: t(option.descKey), })); return ( <>
{routeName} ยท {isPaused ? t("flight.badge.paused") : t("flight.badge.cruising")}
{formattedTime}

{t("flight.missionLabel")}

{voyage.missionText}

{t("flight.debrief.title")} {t("flight.debrief.description")}
{statusOptions.map((opt) => ( ))}
setProgress(event.target.value)} placeholder={t("debrief.reflection.placeholder")} className="w-full rounded-lg border border-slate-800 bg-slate-900/30 px-4 py-3 text-slate-200 outline-none transition-all focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500" />
); }