feat: add global start page setting across all apps

- Add GeneralSettings types (startPages, weekStartsOn, soundsEnabled)
- Create app-routes.ts with available routes for 12 apps
- Extend UserSettingsStore with general settings support
- Update GlobalSettingsSection with start page selector UI
- Add start page redirect logic to all app layouts:
  - Clock, Calendar, Todo, Zitare, Picture
  - Manadeck, Presi, Chat, Manacore
- Create user-settings stores for Clock and Todo apps

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-12-04 16:31:04 +01:00
parent 5b3c5ff4fb
commit bbe540c3f1
16 changed files with 602 additions and 22 deletions

View file

@ -1,32 +1,54 @@
<script lang="ts">
import type { UserSettingsStore, NavPosition, ThemeMode } from '@manacore/shared-theme';
import type {
UserSettingsStore,
NavPosition,
ThemeMode,
WeekStartDay,
} from '@manacore/shared-theme';
import { getAvailableRoutes, getDefaultRoute } from '@manacore/shared-theme';
import SettingsSection from './SettingsSection.svelte';
import SettingsCard from './SettingsCard.svelte';
interface Props {
/** User settings store instance */
userSettings: UserSettingsStore;
/** App ID for start page selection */
appId?: string;
/** Whether to show navigation settings */
showNavigation?: boolean;
/** Whether to show theme settings */
showTheme?: boolean;
/** Whether to show language settings */
showLanguage?: boolean;
/** Whether to show general settings (start page, sounds, week start) */
showGeneral?: boolean;
/** Section title */
title?: string;
/** Section description */
description?: string;
/** Translation function (optional, falls back to German) */
t?: (key: string) => string;
}
let {
userSettings,
appId,
showNavigation = true,
showTheme = true,
showLanguage = true,
showGeneral = true,
title = 'App-Einstellungen',
description = 'Diese Einstellungen gelten für alle Mana Apps',
t = (key: string) => key,
}: Props = $props();
// Available routes for start page selection
const availableRoutes = $derived(appId ? getAvailableRoutes(appId) : []);
const defaultRoute = $derived(appId ? getDefaultRoute(appId) : '/');
const currentStartPage = $derived(
appId ? userSettings.general?.startPages?.[appId] || defaultRoute : '/'
);
// Navigation position handler
async function handleNavPositionChange(position: NavPosition) {
await userSettings.updateGlobal({
@ -60,6 +82,24 @@
await userSettings.updateGlobal({ locale });
}
// Start page handler
async function handleStartPageChange(e: Event) {
const target = e.target as HTMLSelectElement;
if (appId) {
await userSettings.setStartPage(appId, target.value);
}
}
// Week start handler
async function handleWeekStartChange(day: WeekStartDay) {
await userSettings.updateGeneral({ weekStartsOn: day });
}
// Sounds handler
async function handleSoundsChange(enabled: boolean) {
await userSettings.updateGeneral({ soundsEnabled: enabled });
}
const colorSchemes = [
{ id: 'ocean', label: 'Ozean', color: 'bg-blue-500' },
{ id: 'nature', label: 'Natur', color: 'bg-green-500' },
@ -102,7 +142,9 @@
{#if showNavigation}
<!-- Navigation Settings -->
<div class="space-y-4">
<h3 class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider">
<h3
class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider"
>
Navigation
</h3>
@ -135,10 +177,14 @@
</div>
</div>
<div class="flex items-center justify-between py-2 border-t border-[hsl(var(--border))]">
<div
class="flex items-center justify-between py-2 border-t border-[hsl(var(--border))]"
>
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Sidebar eingeklappt</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">Standard-Zustand der Sidebar</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Standard-Zustand der Sidebar
</p>
</div>
<button
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {userSettings
@ -161,15 +207,21 @@
{#if showTheme}
<!-- Theme Settings -->
<div class="space-y-4 {showNavigation ? 'pt-4 border-t border-[hsl(var(--border))]' : ''}">
<h3 class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider">
<div
class="space-y-4 {showNavigation ? 'pt-4 border-t border-[hsl(var(--border))]' : ''}"
>
<h3
class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider"
>
Erscheinungsbild
</h3>
<div class="flex items-center justify-between py-2">
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Farbmodus</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">Hell, Dunkel oder automatisch</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Hell, Dunkel oder automatisch
</p>
</div>
<div class="flex gap-1">
{#each ['light', 'dark', 'system'] as mode}
@ -186,10 +238,14 @@
</div>
</div>
<div class="flex items-center justify-between py-2 border-t border-[hsl(var(--border))]">
<div
class="flex items-center justify-between py-2 border-t border-[hsl(var(--border))]"
>
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Farbschema</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">Akzentfarbe der Benutzeroberfläche</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Akzentfarbe der Benutzeroberfläche
</p>
</div>
<div class="flex gap-2">
{#each colorSchemes as scheme}
@ -214,14 +270,18 @@
? 'pt-4 border-t border-[hsl(var(--border))]'
: ''}"
>
<h3 class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider">
<h3
class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider"
>
Sprache
</h3>
<div class="flex items-center justify-between py-2">
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Anzeigesprache</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">Sprache der Benutzeroberfläche</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Sprache der Benutzeroberfläche
</p>
</div>
<div class="flex gap-1">
{#each languages as lang}
@ -239,6 +299,104 @@
</div>
</div>
{/if}
{#if showGeneral}
<!-- General Settings -->
<div
class="space-y-4 {showLanguage || showTheme || showNavigation
? 'pt-4 border-t border-[hsl(var(--border))]'
: ''}"
>
<h3
class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider"
>
Allgemein
</h3>
{#if appId && availableRoutes.length > 1}
<!-- Start Page Selector -->
<div class="flex items-center justify-between py-2">
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Startseite</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Welche Seite beim Öffnen der App angezeigt wird
</p>
</div>
<select
class="px-3 py-1.5 text-sm font-medium rounded-lg bg-[hsl(var(--muted))] text-[hsl(var(--foreground))] border-none cursor-pointer appearance-none pr-8 bg-[url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23666%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpolyline%20points%3D%226%209%2012%2015%2018%209%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E')] bg-no-repeat bg-[right_0.5rem_center] bg-[length:1rem]"
value={currentStartPage}
onchange={handleStartPageChange}
>
{#each availableRoutes as route}
<option value={route.path}>
{t(route.labelKey)}
</option>
{/each}
</select>
</div>
{/if}
<!-- Week Start Day -->
<div
class="flex items-center justify-between py-2 {appId && availableRoutes.length > 1
? 'border-t border-[hsl(var(--border))]'
: ''}"
>
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Wochenstart</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Erster Tag der Woche in Kalendern
</p>
</div>
<div class="flex gap-2">
<button
class="px-3 py-1.5 text-sm font-medium rounded-lg transition-colors {userSettings
.general?.weekStartsOn === 'monday'
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 text-[hsl(var(--foreground))]'}"
onclick={() => handleWeekStartChange('monday')}
>
Montag
</button>
<button
class="px-3 py-1.5 text-sm font-medium rounded-lg transition-colors {userSettings
.general?.weekStartsOn === 'sunday'
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 text-[hsl(var(--foreground))]'}"
onclick={() => handleWeekStartChange('sunday')}
>
Sonntag
</button>
</div>
</div>
<!-- Sounds Toggle -->
<div
class="flex items-center justify-between py-2 border-t border-[hsl(var(--border))]"
>
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Sounds</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Sound-Effekte in allen Apps
</p>
</div>
<button
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {(userSettings
.general?.soundsEnabled ?? true)
? 'bg-[hsl(var(--primary))]'
: 'bg-gray-200 dark:bg-gray-700'}"
onclick={() => handleSoundsChange(!(userSettings.general?.soundsEnabled ?? true))}
>
<span
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform {(userSettings
.general?.soundsEnabled ?? true)
? 'translate-x-6'
: 'translate-x-1'}"
></span>
</button>
</div>
</div>
{/if}
</div>
{#if userSettings.syncing}