feat(storage): migrate to IndexedDB with localStorage fallback and async app flow

- Add IndexedDB service with schema, indexes, and player stats
- Migrate GameService to async IndexedDB and auto-migrate from localStorage
- Update hooks and App handlers to async; add error handling and UX feedback
- Convert remaining JSX components to TSX
- Add test utility for IndexedDB and migration checks
- Extend game types with sync fields for future online sync
This commit is contained in:
Frank Schwenk
2025-10-30 09:36:17 +01:00
parent e89ae1039d
commit 8085d2ecc8
20 changed files with 1288 additions and 277 deletions

View File

@@ -49,10 +49,10 @@ export default function GameList({
}
};
const filterButtons: Array<{ key: GameFilter; label: string; ariaLabel: string }> = [
{ key: 'all', label: 'Alle', ariaLabel: 'Alle Spiele anzeigen' },
{ key: 'active', label: 'Aktiv', ariaLabel: 'Nur aktive Spiele anzeigen' },
{ key: 'completed', label: 'Abgeschlossen', ariaLabel: 'Nur abgeschlossene Spiele anzeigen' },
const filterButtons = [
{ key: 'all' as const, label: 'Alle', ariaLabel: 'Alle Spiele anzeigen' },
{ key: 'active' as const, label: 'Aktiv', ariaLabel: 'Nur aktive Spiele anzeigen' },
{ key: 'completed' as const, label: 'Abgeschlossen', ariaLabel: 'Nur abgeschlossene Spiele anzeigen' },
];
return (
@@ -87,15 +87,26 @@ export default function GameList({
<div
className={styles['game-info']}
onClick={() => onShowGameDetail(game.id)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onShowGameDetail(game.id);
}
}}
role="button"
tabIndex={0}
aria-label={`Details für Spiel ${playerNames}`}
aria-describedby={`game-${game.id}-description`}
>
<div className={styles['game-type']}>
{game.gameType}{game.raceTo ? ` | ${game.raceTo}` : ''}
</div>
<div className={styles['player-names']}>{playerNames}</div>
<div className={styles['game-scores']}>{scores}</div>
<div id={`game-${game.id}-description`} className="sr-only">
{game.gameType} Spiel zwischen {playerNames} mit dem Stand {scores}.
{game.status === 'completed' ? 'Abgeschlossen' : 'Aktiv'}
</div>
</div>
<Button
variant="danger"