import { useI18n } from "../../i18n"; import { useEffect, useId, useMemo, useState, type ReactNode, type RefObject, } from "react"; import { Play, X } from "lucide-react"; import { Button } from "../../components/ui/button"; export interface FleetActionOption { id: ActionId; label: string; group: string; scopeLabel?: string; disabled?: boolean; disabledReason?: string; tone?: "default" | "destructive"; runLabel?: string; } interface FleetActionSelectorProps { actions: readonly FleetActionOption[]; selectedCount: number; hiddenSelectedCount?: number; busy?: boolean; busyLabel?: string; selectRef?: RefObject; onRun: (action: ActionId) => void | Promise; onClear: () => void; renderContext?: (action: ActionId | "") => ReactNode; label?: string; } /** * Selection-aware command picker shared by all Fleet Ledger grids. Eligibility * remains data owned by the calling grid; this module owns only presentation, * explicit execution and accessible unavailable reasons. */ export function FleetActionSelector({ actions, selectedCount, hiddenSelectedCount = 0, busy = false, selectRef, onRun, onClear, renderContext, label = "Batch action", }: FleetActionSelectorProps) { const { t } = useI18n(); const [selectedActionId, setSelectedActionId] = useState(""); const hintId = useId(); const groups = useMemo(() => { const order: string[] = []; const grouped = new Map[]>(); for (const action of actions) { if (!grouped.has(action.group)) { order.push(action.group); grouped.set(action.group, []); } grouped.get(action.group)!.push(action); } return order.map((group) => ({ group, actions: grouped.get(group)! })); }, [actions, t]); const selectedAction = actions.find((action) => action.id === selectedActionId); const unavailableReason = selectedAction?.disabled ? selectedAction.disabledReason ?? t("This action is not available for the current selection.") : null; const actionScope = selectedAction?.scopeLabel; useEffect(() => { if (selectedActionId && !actions.some((action) => action.id === selectedActionId)) { setSelectedActionId(""); } }, [actions, selectedActionId]); const run = async () => { if (!selectedAction || selectedAction.disabled || busy) return; await onRun(selectedAction.id); setSelectedActionId(""); }; return (
{actionScope ? t(actionScope) : t("{count} selected", { count: selectedCount })} {actionScope ? ( {t("Action scope")} ) : hiddenSelectedCount > 0 ? ( {hiddenSelectedCount} {t("hidden by filter")} ) : ( {t("Selected rows")} )}
{renderContext ? renderContext(selectedActionId) : null} {unavailableReason ? ( {t(unavailableReason)} ) : null}
); }