78 lines
2.8 KiB
TypeScript
78 lines
2.8 KiB
TypeScript
import type { PlayerState } from "@deadhead/schema";
|
|
import { formatLocalTime } from "../screens/DispatchSheet";
|
|
import { errorMessage, useGameAction, useLoadedPlayer } from "../state/actions";
|
|
import { Button, Icon, Panel } from "../ui";
|
|
|
|
/** A sleep threshold in words: "12 h", "24 h", "48 h", "3 days" (CR-024 §2). */
|
|
export function sleepLabel(hours: number): string {
|
|
return hours > 48 && hours % 24 === 0 ? `${hours / 24} days` : `${hours} h`;
|
|
}
|
|
|
|
/** The sleep time on the device, e.g. "Mar 4, 14:05". */
|
|
export function formatSleepTime(time: number): string {
|
|
const date = new Date(time).toLocaleDateString(undefined, { month: "short", day: "numeric" });
|
|
return `${date}, ${formatLocalTime(time)}`;
|
|
}
|
|
|
|
/**
|
|
* The fleet has gone quiet (decision 0.5.137) and it matters: some pilot is
|
|
* Independent, so there is work waiting on the player's orders.
|
|
*/
|
|
export function fleetQuiet(state: PlayerState): boolean {
|
|
return state.fleetSleep.asleep && state.pilots.some((p) => p.mode === "independent");
|
|
}
|
|
|
|
/** "Back to work": a manual decision that wakes the fleet (decision 0.5.138). */
|
|
function BackToWork() {
|
|
const wake = useGameAction("backToWork");
|
|
return (
|
|
<>
|
|
<Button size="sm" className="mt-3" disabled={wake.isPending} onClick={() => wake.mutate({})}>
|
|
<Icon name="send" className="size-4" />
|
|
Back to work
|
|
</Button>
|
|
{wake.isError && (
|
|
<p role="alert" className="mt-2 text-rose-300">
|
|
{errorMessage(wake.error)}
|
|
</p>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
/** The "while you were away" banner: when the fleet went quiet, and why. */
|
|
export function SleepBanner() {
|
|
const state = useLoadedPlayer();
|
|
if (!fleetQuiet(state)) return null;
|
|
const { sleepsAt, afterHours } = state.fleetSleep;
|
|
return (
|
|
<Panel tone="amber" className="text-sm" aria-label="Fleet asleep">
|
|
<p className="flex items-start gap-2 text-amber-100">
|
|
<Icon name="rest" className="mt-0.5 size-4 shrink-0" />
|
|
Your fleet went quiet on {formatSleepTime(sleepsAt)} after {sleepLabel(afterHours)} without
|
|
orders.
|
|
</p>
|
|
<BackToWork />
|
|
</Panel>
|
|
);
|
|
}
|
|
|
|
/** The Bridge's status while the fleet sleeps. */
|
|
export function FleetAsleep() {
|
|
const state = useLoadedPlayer();
|
|
if (!fleetQuiet(state)) return null;
|
|
return (
|
|
<Panel tone="amber" className="text-sm" aria-label="Fleet asleep">
|
|
<p className="flex items-center gap-2 font-display font-semibold tracking-wide text-amber-100">
|
|
<Icon name="rest" className="size-4 shrink-0" />
|
|
Fleet asleep — waiting for your orders
|
|
</p>
|
|
<p className="mt-1 text-ink-300">
|
|
Independent pilots start no new jobs after {sleepLabel(state.fleetSleep.afterHours)} without
|
|
orders. Any decision wakes them.
|
|
</p>
|
|
<BackToWork />
|
|
</Panel>
|
|
);
|
|
}
|