2026-01-30 14:18:05 +01:00
|
|
|
/**
|
|
|
|
|
* Keyboard Shortcuts Registry and Utilities
|
|
|
|
|
* Central configuration for all keyboard shortcuts in the application
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Platform detection
|
|
|
|
|
export const isMac = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Modifier key display based on platform
|
|
|
|
|
*/
|
|
|
|
|
export const modifierKey = isMac ? '⌘' : 'Ctrl';
|
|
|
|
|
export const altKey = isMac ? '⌥' : 'Alt';
|
|
|
|
|
export const shiftKey = '⇧';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shortcut definition
|
|
|
|
|
*/
|
|
|
|
|
export interface ShortcutDefinition {
|
|
|
|
|
id: string;
|
|
|
|
|
keys: string; // react-hotkeys-hook format: 'mod+k', 'mod+shift+n'
|
|
|
|
|
label: string; // Danish display name
|
|
|
|
|
description?: string;
|
|
|
|
|
category: ShortcutCategory;
|
|
|
|
|
scope?: ShortcutScope;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type ShortcutCategory =
|
|
|
|
|
| 'global'
|
|
|
|
|
| 'navigation'
|
|
|
|
|
| 'bogforing'
|
|
|
|
|
| 'faktura'
|
|
|
|
|
| 'bank'
|
|
|
|
|
| 'kunder'
|
|
|
|
|
| 'produkter';
|
|
|
|
|
|
|
|
|
|
export type ShortcutScope =
|
|
|
|
|
| 'global' // Works everywhere
|
|
|
|
|
| 'page' // Only on specific page
|
|
|
|
|
| 'modal' // Only in modals
|
|
|
|
|
| 'table'; // Only when table is focused
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* All keyboard shortcuts in the application
|
|
|
|
|
*/
|
|
|
|
|
export const shortcuts: Record<string, ShortcutDefinition> = {
|
|
|
|
|
// Global shortcuts
|
|
|
|
|
openCommandPalette: {
|
|
|
|
|
id: 'openCommandPalette',
|
|
|
|
|
keys: 'mod+k',
|
|
|
|
|
label: 'Kommandopalette',
|
|
|
|
|
description: 'Hurtig navigation og actions',
|
|
|
|
|
category: 'global',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
showShortcuts: {
|
|
|
|
|
id: 'showShortcuts',
|
|
|
|
|
keys: 'mod+/',
|
|
|
|
|
label: 'Vis genveje',
|
|
|
|
|
description: 'Vis alle tastaturgenveje',
|
|
|
|
|
category: 'global',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
closeModal: {
|
|
|
|
|
id: 'closeModal',
|
|
|
|
|
keys: 'escape',
|
|
|
|
|
label: 'Luk',
|
|
|
|
|
description: 'Luk aktiv modal eller palette',
|
|
|
|
|
category: 'global',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Navigation shortcuts (G then X pattern)
|
|
|
|
|
goToDashboard: {
|
|
|
|
|
id: 'goToDashboard',
|
|
|
|
|
keys: 'g d',
|
|
|
|
|
label: 'Dashboard',
|
|
|
|
|
category: 'navigation',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
goToKassekladde: {
|
|
|
|
|
id: 'goToKassekladde',
|
|
|
|
|
keys: 'g k',
|
|
|
|
|
label: 'Kassekladde',
|
|
|
|
|
category: 'navigation',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
goToKontooversigt: {
|
|
|
|
|
id: 'goToKontooversigt',
|
|
|
|
|
keys: 'g o',
|
|
|
|
|
label: 'Kontooversigt',
|
|
|
|
|
category: 'navigation',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
goToFakturaer: {
|
|
|
|
|
id: 'goToFakturaer',
|
|
|
|
|
keys: 'g f',
|
|
|
|
|
label: 'Fakturaer',
|
|
|
|
|
category: 'navigation',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
goToKreditnotaer: {
|
|
|
|
|
id: 'goToKreditnotaer',
|
|
|
|
|
keys: 'g c',
|
|
|
|
|
label: 'Kreditnotaer',
|
|
|
|
|
category: 'navigation',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
goToBankafstemning: {
|
|
|
|
|
id: 'goToBankafstemning',
|
|
|
|
|
keys: 'g b',
|
|
|
|
|
label: 'Bankafstemning',
|
|
|
|
|
category: 'navigation',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
goToKunder: {
|
|
|
|
|
id: 'goToKunder',
|
|
|
|
|
keys: 'g u',
|
|
|
|
|
label: 'Kunder',
|
|
|
|
|
category: 'navigation',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
goToProdukter: {
|
|
|
|
|
id: 'goToProdukter',
|
|
|
|
|
keys: 'g p',
|
|
|
|
|
label: 'Produkter',
|
|
|
|
|
category: 'navigation',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
goToMomsindberetning: {
|
|
|
|
|
id: 'goToMomsindberetning',
|
|
|
|
|
keys: 'g m',
|
|
|
|
|
label: 'Momsindberetning',
|
|
|
|
|
category: 'navigation',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
goToIndstillinger: {
|
|
|
|
|
id: 'goToIndstillinger',
|
|
|
|
|
keys: 'g i',
|
|
|
|
|
label: 'Indstillinger',
|
|
|
|
|
category: 'navigation',
|
|
|
|
|
scope: 'global',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Bogforing shortcuts (Kassekladde)
|
|
|
|
|
newDraft: {
|
|
|
|
|
id: 'newDraft',
|
|
|
|
|
keys: 'mod+n',
|
|
|
|
|
label: 'Ny kassekladde',
|
|
|
|
|
category: 'bogforing',
|
|
|
|
|
scope: 'page',
|
|
|
|
|
},
|
|
|
|
|
postDraft: {
|
|
|
|
|
id: 'postDraft',
|
|
|
|
|
keys: 'mod+enter',
|
Audit v3: VAT alignment, security, encoding, UX, compliance
VAT System Alignment (LEGAL - Critical):
- Align frontend VAT codes with backend (S25→U25, K25→I25, etc.)
- Add missing codes: UEU, IVV, IVY, REP
- Fix output VAT account 5710→5611 to match StandardDanishAccounts
- Invoice posting now checks fiscal year status before allowing send
- Disallow custom invoice number override (always use auto-numbering)
Security:
- Fix open redirect in AuthController (validate returnUrl is local)
- Store seller CVR/name/address on invoice events (Momsloven §52)
Backend Compliance:
- Add description validation at posting (Bogføringsloven §7)
- SAF-T: add DefaultCurrencyCode, TaxAccountingBasis to header
- SAF-T: add TaxTable to MasterFiles with all VAT codes
- SAF-T: always write balance elements even when zero
- Add financial income account 9100 Renteindtægter
Danish Encoding (~25 fixes):
- Kassekladde: Bogført, Bogføring, Vælg, være, på, Tilføj, Differens
- AttachmentUpload: træk, Understøtter, påkrævet, Bogføringsloven
- keyboardShortcuts: Bogfør, Bogføring display name
- ShortcutsHelpModal: åbne
- DataTable: Genindlæs
- documentProcessing: være
- CloseFiscalYearWizard: årsafslutning
Bugs Fixed:
- Non-null assertion crashes in Kunder.tsx and Produkter.tsx (company!.id)
- StatusBadge typo "Succces"→"Succes"
- HTML entity ø in Kassekladde→proper UTF-8
- AmountText showSign prop was dead code (true || showSign)
UX Improvements:
- Add PageHeader to Bankafstemning and Dashboard loading/empty states
- Responsive columns in Bankafstemning (xs/sm/lg breakpoints)
- Disable misleading buttons: Settings preferences, Kontooversigt edit,
Loenforstaelse export — with tooltips explaining status
- Add DemoDataDisclaimer to UserSettings
- Fix breadcrumb self-references on 3 pages
- Replace Dashboard fake progress bar with honest message
- Standardize date format DD-MM-YYYY in Bankafstemning and Ordrer
- Replace Input type="number" with InputNumber in Ordrer
Quality:
- Remove 8 redundant console.error statements
- Fix Kreditnotaer breadcrumb "Salg"→"Fakturering" for consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:15:45 +01:00
|
|
|
label: 'Bogfør kladde',
|
2026-01-30 14:18:05 +01:00
|
|
|
category: 'bogforing',
|
|
|
|
|
scope: 'page',
|
|
|
|
|
},
|
|
|
|
|
addLine: {
|
|
|
|
|
id: 'addLine',
|
|
|
|
|
keys: 'mod+shift+l',
|
|
|
|
|
label: 'Tilføj linje',
|
|
|
|
|
category: 'bogforing',
|
|
|
|
|
scope: 'page',
|
|
|
|
|
},
|
|
|
|
|
saveDraft: {
|
|
|
|
|
id: 'saveDraft',
|
|
|
|
|
keys: 'mod+s',
|
|
|
|
|
label: 'Gem kladde',
|
|
|
|
|
category: 'bogforing',
|
|
|
|
|
scope: 'page',
|
|
|
|
|
},
|
|
|
|
|
discardDraft: {
|
|
|
|
|
id: 'discardDraft',
|
|
|
|
|
keys: 'mod+backspace',
|
|
|
|
|
label: 'Kasser kladde',
|
|
|
|
|
category: 'bogforing',
|
|
|
|
|
scope: 'page',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
syncBank: {
|
|
|
|
|
id: 'syncBank',
|
|
|
|
|
keys: 'mod+r',
|
|
|
|
|
label: 'Synkroniser bank',
|
|
|
|
|
description: 'Hent nye transaktioner fra bank',
|
|
|
|
|
category: 'bank',
|
|
|
|
|
scope: 'page',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Faktura shortcuts
|
|
|
|
|
newInvoice: {
|
|
|
|
|
id: 'newInvoice',
|
|
|
|
|
keys: 'mod+i',
|
|
|
|
|
label: 'Ny faktura',
|
|
|
|
|
category: 'faktura',
|
|
|
|
|
scope: 'page',
|
|
|
|
|
},
|
|
|
|
|
newCreditNote: {
|
|
|
|
|
id: 'newCreditNote',
|
|
|
|
|
keys: 'mod+shift+i',
|
|
|
|
|
label: 'Ny kreditnota',
|
|
|
|
|
category: 'faktura',
|
|
|
|
|
scope: 'page',
|
|
|
|
|
},
|
|
|
|
|
newCustomer: {
|
|
|
|
|
id: 'newCustomer',
|
|
|
|
|
keys: 'mod+shift+k',
|
|
|
|
|
label: 'Ny kunde',
|
|
|
|
|
category: 'kunder',
|
|
|
|
|
scope: 'page',
|
|
|
|
|
},
|
|
|
|
|
newProduct: {
|
|
|
|
|
id: 'newProduct',
|
|
|
|
|
keys: 'mod+shift+p',
|
|
|
|
|
label: 'Nyt produkt',
|
|
|
|
|
category: 'produkter',
|
|
|
|
|
scope: 'page',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Bank shortcuts
|
|
|
|
|
matchTransaction: {
|
|
|
|
|
id: 'matchTransaction',
|
|
|
|
|
keys: 'mod+m',
|
|
|
|
|
label: 'Match transaktion',
|
|
|
|
|
category: 'bank',
|
|
|
|
|
scope: 'page',
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get shortcuts by category
|
|
|
|
|
*/
|
|
|
|
|
export function getShortcutsByCategory(category: ShortcutCategory): ShortcutDefinition[] {
|
|
|
|
|
return Object.values(shortcuts).filter(s => s.category === category);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get all shortcuts grouped by category
|
|
|
|
|
*/
|
|
|
|
|
export function getShortcutsGrouped(): Record<ShortcutCategory, ShortcutDefinition[]> {
|
|
|
|
|
const grouped: Record<ShortcutCategory, ShortcutDefinition[]> = {
|
|
|
|
|
global: [],
|
|
|
|
|
navigation: [],
|
|
|
|
|
bogforing: [],
|
|
|
|
|
faktura: [],
|
|
|
|
|
bank: [],
|
|
|
|
|
kunder: [],
|
|
|
|
|
produkter: [],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Object.values(shortcuts).forEach(shortcut => {
|
|
|
|
|
grouped[shortcut.category].push(shortcut);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return grouped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Format shortcut keys for display
|
|
|
|
|
* Converts 'mod+k' to '⌘K' on Mac or 'Ctrl+K' on Windows
|
|
|
|
|
*/
|
|
|
|
|
export function formatShortcut(keys: string): string {
|
|
|
|
|
return keys
|
|
|
|
|
.split(' ')
|
|
|
|
|
.map(part =>
|
|
|
|
|
part
|
|
|
|
|
.replace(/mod/g, modifierKey)
|
|
|
|
|
.replace(/shift/g, shiftKey)
|
|
|
|
|
.replace(/alt/g, altKey)
|
|
|
|
|
.replace(/\+/g, '')
|
|
|
|
|
.toUpperCase()
|
|
|
|
|
)
|
|
|
|
|
.join(' ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Format shortcut for tooltip display
|
|
|
|
|
* Returns formatted string like "⌘K" or "Ctrl+K"
|
|
|
|
|
*/
|
|
|
|
|
export function formatShortcutForTooltip(shortcutId: string): string | null {
|
|
|
|
|
const shortcut = shortcuts[shortcutId];
|
|
|
|
|
if (!shortcut) return null;
|
|
|
|
|
return formatShortcut(shortcut.keys);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Category display names in Danish
|
|
|
|
|
*/
|
|
|
|
|
export const categoryNames: Record<ShortcutCategory, string> = {
|
|
|
|
|
global: 'Globale',
|
|
|
|
|
navigation: 'Navigation',
|
Audit v3: VAT alignment, security, encoding, UX, compliance
VAT System Alignment (LEGAL - Critical):
- Align frontend VAT codes with backend (S25→U25, K25→I25, etc.)
- Add missing codes: UEU, IVV, IVY, REP
- Fix output VAT account 5710→5611 to match StandardDanishAccounts
- Invoice posting now checks fiscal year status before allowing send
- Disallow custom invoice number override (always use auto-numbering)
Security:
- Fix open redirect in AuthController (validate returnUrl is local)
- Store seller CVR/name/address on invoice events (Momsloven §52)
Backend Compliance:
- Add description validation at posting (Bogføringsloven §7)
- SAF-T: add DefaultCurrencyCode, TaxAccountingBasis to header
- SAF-T: add TaxTable to MasterFiles with all VAT codes
- SAF-T: always write balance elements even when zero
- Add financial income account 9100 Renteindtægter
Danish Encoding (~25 fixes):
- Kassekladde: Bogført, Bogføring, Vælg, være, på, Tilføj, Differens
- AttachmentUpload: træk, Understøtter, påkrævet, Bogføringsloven
- keyboardShortcuts: Bogfør, Bogføring display name
- ShortcutsHelpModal: åbne
- DataTable: Genindlæs
- documentProcessing: være
- CloseFiscalYearWizard: årsafslutning
Bugs Fixed:
- Non-null assertion crashes in Kunder.tsx and Produkter.tsx (company!.id)
- StatusBadge typo "Succces"→"Succes"
- HTML entity ø in Kassekladde→proper UTF-8
- AmountText showSign prop was dead code (true || showSign)
UX Improvements:
- Add PageHeader to Bankafstemning and Dashboard loading/empty states
- Responsive columns in Bankafstemning (xs/sm/lg breakpoints)
- Disable misleading buttons: Settings preferences, Kontooversigt edit,
Loenforstaelse export — with tooltips explaining status
- Add DemoDataDisclaimer to UserSettings
- Fix breadcrumb self-references on 3 pages
- Replace Dashboard fake progress bar with honest message
- Standardize date format DD-MM-YYYY in Bankafstemning and Ordrer
- Replace Input type="number" with InputNumber in Ordrer
Quality:
- Remove 8 redundant console.error statements
- Fix Kreditnotaer breadcrumb "Salg"→"Fakturering" for consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:15:45 +01:00
|
|
|
bogforing: 'Bogføring',
|
2026-01-30 14:18:05 +01:00
|
|
|
faktura: 'Fakturering',
|
|
|
|
|
bank: 'Bank',
|
|
|
|
|
kunder: 'Kunder',
|
|
|
|
|
produkter: 'Produkter',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Navigation routes mapping
|
|
|
|
|
*/
|
|
|
|
|
export const navigationRoutes: Record<string, string> = {
|
|
|
|
|
goToDashboard: '/',
|
|
|
|
|
goToKassekladde: '/kassekladde',
|
|
|
|
|
goToKontooversigt: '/kontooversigt',
|
|
|
|
|
goToFakturaer: '/fakturaer',
|
|
|
|
|
goToKreditnotaer: '/kreditnotaer',
|
|
|
|
|
goToBankafstemning: '/bankafstemning',
|
|
|
|
|
goToKunder: '/kunder',
|
|
|
|
|
goToProdukter: '/produkter',
|
|
|
|
|
goToMomsindberetning: '/momsindberetning',
|
|
|
|
|
goToIndstillinger: '/indstillinger',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Command palette items
|
|
|
|
|
*/
|
|
|
|
|
export interface CommandItem {
|
|
|
|
|
id: string;
|
|
|
|
|
label: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
icon?: string;
|
|
|
|
|
shortcut?: string;
|
|
|
|
|
category: 'navigation' | 'action' | 'recent';
|
|
|
|
|
action: () => void;
|
|
|
|
|
}
|