Views stay mounted so switching tabs no longer loses a half-typed invoice. The form autosaves a draft after 1 s, drafts can be resumed or deleted, and after issuing the form is read-only with Export again, Duplicate and New invoice actions. A failed export keeps the issued invoice and offers a retry instead of issuing again. Adds Ctrl+N/S/Enter/L and a shortcut sheet; F5 and Ctrl+R are blocked in production builds.
63 lines
2.0 KiB
TypeScript
63 lines
2.0 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|
|
|
export interface ShortcutHandlers {
|
|
onNew?: () => void;
|
|
onSaveDraft?: () => void;
|
|
onIssue?: () => void;
|
|
onAddLine?: () => void;
|
|
onHelp?: () => void;
|
|
}
|
|
|
|
const isTyping = (target: EventTarget | null) => {
|
|
const el = target as HTMLElement | null;
|
|
if (!el) return false;
|
|
return el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT" || el.isContentEditable;
|
|
};
|
|
|
|
/**
|
|
* Global keyboard shortcuts. Ctrl combinations work everywhere; the bare `?` is ignored
|
|
* while typing. F5 / Ctrl+R are blocked in production builds because a webview reload
|
|
* would discard in-memory state.
|
|
*/
|
|
export function useShortcuts(handlers: ShortcutHandlers) {
|
|
const ref = useRef(handlers);
|
|
ref.current = handlers;
|
|
|
|
useEffect(() => {
|
|
const onKey = (event: KeyboardEvent) => {
|
|
const ctrl = event.ctrlKey || event.metaKey;
|
|
const key = event.key.toLowerCase();
|
|
|
|
if (import.meta.env.PROD && (event.key === "F5" || (ctrl && key === "r"))) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
const run = (fn?: () => void) => {
|
|
event.preventDefault();
|
|
fn?.();
|
|
};
|
|
|
|
if (ctrl && !event.shiftKey && !event.altKey) {
|
|
if (key === "n") return run(ref.current.onNew);
|
|
if (key === "s") return run(ref.current.onSaveDraft);
|
|
if (key === "l") return run(ref.current.onAddLine);
|
|
if (key === "enter") return run(ref.current.onIssue);
|
|
}
|
|
if (!ctrl && !event.altKey && event.key === "?" && !isTyping(event.target)) {
|
|
run(ref.current.onHelp);
|
|
}
|
|
};
|
|
window.addEventListener("keydown", onKey);
|
|
return () => window.removeEventListener("keydown", onKey);
|
|
}, []);
|
|
}
|
|
|
|
export const SHORTCUT_LIST: { keys: string; action: string }[] = [
|
|
{ keys: "Ctrl + N", action: "New invoice" },
|
|
{ keys: "Ctrl + S", action: "Save draft" },
|
|
{ keys: "Ctrl + Enter", action: "Issue and export PDF" },
|
|
{ keys: "Ctrl + L", action: "Add line item" },
|
|
{ keys: "?", action: "Show this list" },
|
|
];
|