fix: eliminate all 167 svelte-check warnings
Refactor page components to use $derived + invalidateAll() where data is read-only or re-fetched after mutations. Suppress state_referenced_locally for intentional patterns (form state, optimistic updates, pagination). Fix a11y issues with role="presentation", add standard line-clamp properties, remove unused CSS selectors and empty rulesets.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.11.1",
|
||||
"version": "1.11.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
children
|
||||
} = $props();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let isVisible = $state(eager); // If eager=true, render immediately
|
||||
/** @type {HTMLDivElement | null} */
|
||||
let containerRef = $state(null);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
...restProps
|
||||
} = $props();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let shouldLoad = $state(eager);
|
||||
/** @type {HTMLImageElement | null} */
|
||||
let imgElement = $state(null);
|
||||
|
||||
@@ -5,16 +5,19 @@
|
||||
|
||||
let { initialBalance = null, initialDebtData = null } = $props<{ initialBalance?: any, initialDebtData?: any }>();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let balance = $state(initialBalance || {
|
||||
netBalance: 0,
|
||||
recentSplits: []
|
||||
});
|
||||
// svelte-ignore state_referenced_locally
|
||||
let debtData = $state(initialDebtData || {
|
||||
whoOwesMe: [],
|
||||
whoIOwe: [],
|
||||
totalOwedToMe: 0,
|
||||
totalIOwe: 0
|
||||
});
|
||||
// svelte-ignore state_referenced_locally
|
||||
let loading = $state(!initialBalance || !initialDebtData);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
|
||||
@@ -17,10 +17,14 @@
|
||||
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let book: string = $state(verseData?.book || '');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let chapter: number = $state(verseData?.chapter || 0);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let verses: Array<{ verse: number; text: string }> = $state(verseData?.verses || []);
|
||||
let loading = $state(false);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let error = $state(verseData ? '' : (lang === 'en' ? 'No verse data available' : 'Keine Versdaten verfügbar'));
|
||||
|
||||
function handleBackdropClick(event: MouseEvent) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
let { periods: initialPeriods = [], lang = 'en', sharedWith: initialSharedWith = [], readOnly = false, ownerName = '' } = $props();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let periods = $state([...initialPeriods]);
|
||||
let loading = $state(false);
|
||||
let showAddForm = $state(false);
|
||||
@@ -24,6 +25,7 @@
|
||||
let showHistory = $state(false);
|
||||
|
||||
// Sharing state
|
||||
// svelte-ignore state_referenced_locally
|
||||
let shareList = $state([...initialSharedWith]);
|
||||
let showShare = $state(false);
|
||||
let shareInput = $state('');
|
||||
@@ -855,10 +857,10 @@
|
||||
|
||||
<!-- Share modal -->
|
||||
{#if showShare}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
||||
<div class="share-overlay" onclick={() => showShare = false} onkeydown={(e) => e.key === 'Escape' && (showShare = false)}>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="share-modal" onclick={(e) => e.stopPropagation()}>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
||||
<div class="share-modal" role="presentation" onclick={(e) => e.stopPropagation()}>
|
||||
<div class="share-modal-header">
|
||||
<h3>{t('share', lang)}</h3>
|
||||
<button class="share-modal-close" onclick={() => showShare = false}>
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div class="video-overlay" onclick={handleBackdrop}>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="video-overlay" role="presentation" onclick={handleBackdrop}>
|
||||
<button class="close-btn" onclick={onClose} aria-label="Close video">
|
||||
<X size={24} />
|
||||
</button>
|
||||
|
||||
@@ -15,6 +15,7 @@ let {
|
||||
} = $props();
|
||||
|
||||
// Unique dialog ID based on type to prevent conflicts when both are on the same page
|
||||
// svelte-ignore state_referenced_locally
|
||||
const dialogId = `base-recipe-selector-modal-${type}`;
|
||||
|
||||
let baseRecipes: any[] = $state([]);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const label = $derived(isEnglish ? 'Favorites' : 'Favoriten');
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let checked = $state(enabled);
|
||||
|
||||
// Watch for changes to checked and call onToggle
|
||||
|
||||
@@ -120,6 +120,7 @@ const flattenedIngredients = $derived.by(() => {
|
||||
const lang = data.lang || 'de';
|
||||
return flattenIngredientReferences(data.ingredients, lang);
|
||||
});
|
||||
// svelte-ignore state_referenced_locally
|
||||
let multiplier = $state(data.multiplier || 1);
|
||||
|
||||
const isEnglish = $derived(data.lang === 'en');
|
||||
@@ -138,10 +139,15 @@ const labels = $derived({
|
||||
|
||||
// Cake form scaling
|
||||
const hasDefaultForm = $derived(!!data.defaultForm?.shape);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let userFormShape = $state(data.defaultForm?.shape || 'round');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let userFormDiameter = $state(data.defaultForm?.diameter || 26);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let userFormWidth = $state(data.defaultForm?.width || 20);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let userFormLength = $state(data.defaultForm?.length || 30);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let userFormInnerDiameter = $state(data.defaultForm?.innerDiameter || 8);
|
||||
|
||||
/** @param {string} shape @param {number} diameter @param {number} width @param {number} length @param {number} innerDiameter */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
let { data } = $props();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let multiplier = $state(data.multiplier || 1);
|
||||
|
||||
// Recursively flatten nested instruction references
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
const andLabel = $derived(isEnglish ? 'AND' : 'UND');
|
||||
const orLabel = $derived(isEnglish ? 'OR' : 'ODER');
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let checked = $state(useAndLogic);
|
||||
|
||||
// Watch for changes to checked and call onToggle
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
}: Props = $props();
|
||||
|
||||
type TranslationState = 'idle' | 'translating' | 'preview' | 'approved' | 'error';
|
||||
// svelte-ignore state_referenced_locally
|
||||
let translationState = $state<TranslationState>(englishData ? 'preview' : 'idle');
|
||||
let errorMessage = $state('');
|
||||
let validationErrors = $state<string[]>([]);
|
||||
@@ -43,6 +44,7 @@
|
||||
}
|
||||
|
||||
// Eagerly initialize editableEnglish from germanData if no English translation exists
|
||||
// svelte-ignore state_referenced_locally
|
||||
let editableEnglish = $state<any>(
|
||||
englishData ? {
|
||||
...englishData,
|
||||
|
||||
@@ -31,17 +31,27 @@
|
||||
{ tag: 'müll', icon: Trash2 },
|
||||
];
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let title = $state(task?.title || '');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let description = $state(task?.description || '');
|
||||
/** @type {string[]} */
|
||||
// svelte-ignore state_referenced_locally
|
||||
let selectedAssignees = $state(task?.assignees ? [...task.assignees] : []);
|
||||
/** @type {string[]} */
|
||||
// svelte-ignore state_referenced_locally
|
||||
let selectedTags = $state(task?.tags ? [...task.tags] : []);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let difficulty = $state(task?.difficulty || '');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let refreshMode = $state(task?.refreshMode || 'completion');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let isRecurring = $state(task?.isRecurring || false);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let frequencyType = $state(task?.frequency?.type || 'weekly');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let customDays = $state(task?.frequency?.customDays || 7);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let nextDueDate = $state(
|
||||
task?.nextDueDate
|
||||
? new Date(task.nextDueDate).toISOString().split('T')[0]
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
let { data } = $props();
|
||||
|
||||
// Create language context for prayer components
|
||||
// svelte-ignore state_referenced_locally
|
||||
const langContext = createLanguageContext({ urlLang: /** @type {'de' | 'en'} */(data.lang), initialLatin: data.lang === 'la' ? true : data.initialLatin });
|
||||
|
||||
// Update lang store when data.lang changes (e.g., after navigation)
|
||||
@@ -113,6 +114,7 @@
|
||||
postcommunio: ['eucharistic'],
|
||||
};
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let selectedCategory = $state(data.initialCategory);
|
||||
|
||||
// JS-only search (hidden without JS)
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
const langContext = createLanguageContext({ urlLang: /** @type {'de' | 'en'} */(data.lang), initialLatin: data.lang === 'la' ? true : data.initialLatin });
|
||||
|
||||
$effect(() => {
|
||||
|
||||
@@ -30,9 +30,11 @@ import { setupScrollSync } from "./rosaryScrollSync.js";
|
||||
let { data } = $props();
|
||||
|
||||
// Toggle for including Luminous mysteries (initialized from URL param or default)
|
||||
// svelte-ignore state_referenced_locally
|
||||
let includeLuminous = $state(data.initialLuminous);
|
||||
|
||||
// Toggle for showing mystery images (initialized from URL param or default)
|
||||
// svelte-ignore state_referenced_locally
|
||||
let showImages = $state(data.initialShowImages);
|
||||
|
||||
// Flag to prevent saving before we've loaded from localStorage
|
||||
@@ -40,6 +42,7 @@ let hasLoadedFromStorage = $state(false);
|
||||
|
||||
// Create language context for prayer components (LanguageToggle will use this)
|
||||
// For Latin route, force showLatin on so only Latin prayers render
|
||||
// svelte-ignore state_referenced_locally
|
||||
const langContext = createLanguageContext({ urlLang: /** @type {'en'|'de'} */ (data.lang), initialLatin: data.lang === 'la' ? true : data.initialLatin });
|
||||
|
||||
// Update lang store when data.lang changes (e.g., after navigation)
|
||||
@@ -71,8 +74,9 @@ $effect(() => {
|
||||
});
|
||||
|
||||
// Use server-computed initial values (supports no-JS via URL params)
|
||||
// svelte-ignore state_referenced_locally
|
||||
let selectedMystery = $state(/** @type {MysteryType} */ (data.initialMystery));
|
||||
let todaysMystery = $state(/** @type {MysteryType} */ (data.todaysMystery));
|
||||
let todaysMystery = $derived(/** @type {MysteryType} */ (data.todaysMystery));
|
||||
|
||||
// Derive these values from selectedMystery so they update automatically
|
||||
let currentMysteries = $derived(mysteries[selectedMystery]);
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
const hasHashedImage = (r: any) => r.images?.length > 0 && /\.\w+\.\w+$/.test(r.images[0].mediapath);
|
||||
|
||||
// Server-generated random index ensures SSR and client pick the same hero
|
||||
// svelte-ignore state_referenced_locally
|
||||
const heroIndex = data.heroIndex;
|
||||
const heroRecipe = $derived.by(() => {
|
||||
const seasonPool = data.season.filter(hasHashedImage);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script>
|
||||
let { data } = $props();
|
||||
// svelte-ignore state_referenced_locally
|
||||
const isEnglish = data.lang === 'en';
|
||||
// svelte-ignore state_referenced_locally
|
||||
const recipeLang = data.recipeLang;
|
||||
|
||||
let processing = $state(false);
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
let { data } = $props<{ data: PageData }>();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
const isEnglish = data.lang === 'en';
|
||||
const pageTitle = isEnglish ? 'Administration' : 'Administration';
|
||||
const pageDescription = isEnglish
|
||||
? 'Manage recipes and content'
|
||||
: 'Rezepte und Inhalte verwalten';
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
const links = [
|
||||
{
|
||||
title: isEnglish ? 'Untranslated Recipes' : 'Unübersetzte Rezepte',
|
||||
|
||||
@@ -19,10 +19,14 @@
|
||||
|
||||
let { data, form }: { data: PageData; form: ActionData } = $props();
|
||||
|
||||
// Recipe data state
|
||||
// Recipe data state — all form fields initialized from server data (intentionally local mutable state)
|
||||
// svelte-ignore state_referenced_locally
|
||||
let preamble = $state(data.recipe.preamble || "");
|
||||
// svelte-ignore state_referenced_locally
|
||||
let addendum = $state(data.recipe.addendum || "");
|
||||
// svelte-ignore state_referenced_locally
|
||||
let note = $state(data.recipe.note || "");
|
||||
// svelte-ignore state_referenced_locally
|
||||
let image_preview_url = $state(
|
||||
"https://bocken.org/static/rezepte/thumb/" +
|
||||
(data.recipe.images?.[0]?.mediapath || `${data.recipe.short_name}.webp`)
|
||||
@@ -31,15 +35,20 @@
|
||||
|
||||
// Translation workflow state
|
||||
let showTranslationWorkflow = $state(false);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let translationData = $state<any>(data.recipe.translations?.en || null);
|
||||
let changedFields = $state<string[]>([]);
|
||||
|
||||
// Store original recipe data for change detection
|
||||
// svelte-ignore state_referenced_locally
|
||||
const originalRecipe = JSON.parse(JSON.stringify(data.recipe));
|
||||
// svelte-ignore state_referenced_locally
|
||||
const old_short_name = data.recipe.short_name;
|
||||
|
||||
// Season and portions stores
|
||||
// svelte-ignore state_referenced_locally
|
||||
portions.update(() => data.recipe.portions || "");
|
||||
// svelte-ignore state_referenced_locally
|
||||
let portions_local = $state<string>(data.recipe.portions || "");
|
||||
$effect(() => {
|
||||
portions.subscribe((p) => {
|
||||
@@ -47,7 +56,9 @@
|
||||
});
|
||||
});
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
season.update(() => data.recipe.season || []);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let season_local = $state<number[]>(data.recipe.season || []);
|
||||
$effect(() => {
|
||||
season.subscribe((s) => {
|
||||
@@ -55,6 +66,7 @@
|
||||
});
|
||||
});
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let card_data = $state({
|
||||
icon: data.recipe.icon || "",
|
||||
category: data.recipe.category || "",
|
||||
@@ -63,6 +75,7 @@
|
||||
tags: data.recipe.tags || [],
|
||||
});
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let add_info = $state({
|
||||
preparation: data.recipe.preparation || "",
|
||||
fermentation: {
|
||||
@@ -78,13 +91,20 @@
|
||||
cooking: data.recipe.cooking || "",
|
||||
});
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let images = $state(data.recipe.images || []);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let short_name = $state(data.recipe.short_name || "");
|
||||
// svelte-ignore state_referenced_locally
|
||||
let datecreated = $state(data.recipe.datecreated);
|
||||
let datemodified = $state(new Date());
|
||||
// svelte-ignore state_referenced_locally
|
||||
let isBaseRecipe = $state(data.recipe.isBaseRecipe || false);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let defaultForm = $state(data.recipe.defaultForm ? { ...data.recipe.defaultForm } : null);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let ingredients = $state(data.recipe.ingredients || []);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let instructions = $state(data.recipe.instructions || []);
|
||||
|
||||
// Form submission state
|
||||
@@ -245,6 +265,7 @@
|
||||
}
|
||||
|
||||
// Nutrition state — all edits are local until form save
|
||||
// svelte-ignore state_referenced_locally
|
||||
let nutritionMappings = $state<any[]>(data.recipe.nutritionMappings || []);
|
||||
let generatingNutrition = $state(false);
|
||||
let searchQueries = $state<Record<string, string>>({});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let items = $state(data.items ?? []);
|
||||
|
||||
const isEnglish = $derived(data.lang === 'en');
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
let showModal = $state(false);
|
||||
/** @type {string | null} */
|
||||
let paymentId = $state(null);
|
||||
let user = $state(data.session?.user);
|
||||
let user = $derived(data.session?.user);
|
||||
let isGuest = $derived(!data.session?.user);
|
||||
|
||||
$effect(() => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { pushState } from '$app/navigation';
|
||||
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
||||
import EnhancedBalance from '$lib/components/cospend/EnhancedBalance.svelte';
|
||||
@@ -16,14 +17,14 @@
|
||||
let { data } = $props(); // Contains session data and balance from server
|
||||
|
||||
// Use server-side data, with fallback for progressive enhancement
|
||||
let balance = $state(data.balance || {
|
||||
let balance = $derived(data.balance || {
|
||||
netBalance: 0,
|
||||
recentSplits: []
|
||||
});
|
||||
let loading = $state(false); // Start as false since we have server data
|
||||
let loading = $state(false);
|
||||
/** @type {string | null} */
|
||||
let error = $state(null);
|
||||
let monthlyExpensesData = $state(/** @type {any} */ (data).monthlyExpensesData || { labels: [], datasets: [] });
|
||||
let monthlyExpensesData = $state(/** @type {any} */ ({ labels: [], datasets: [] }));
|
||||
let expensesLoading = $state(false);
|
||||
/** @type {string[] | null} */
|
||||
let categoryFilter = $state(null);
|
||||
@@ -40,71 +41,31 @@
|
||||
/** @type {any} */
|
||||
let debtBreakdownComponent;
|
||||
|
||||
// Progressive enhancement: refresh data if JavaScript is available
|
||||
onMount(() => {
|
||||
// Mark that JavaScript is loaded for progressive enhancement
|
||||
document.body.classList.add('js-loaded');
|
||||
|
||||
// Only fetch if we don't have server-side data
|
||||
if (!balance.recentSplits || balance.recentSplits.length === 0) {
|
||||
fetchBalance();
|
||||
}
|
||||
|
||||
if (!monthlyExpensesData.datasets || monthlyExpensesData.datasets.length === 0) {
|
||||
fetchMonthlyExpenses();
|
||||
}
|
||||
|
||||
// Listen for dashboard refresh events from the layout
|
||||
const handleDashboardRefresh = () => {
|
||||
refreshAllComponents();
|
||||
};
|
||||
|
||||
window.addEventListener('dashboardRefresh', handleDashboardRefresh);
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
window.removeEventListener('dashboardRefresh', handleDashboardRefresh);
|
||||
};
|
||||
});
|
||||
|
||||
async function fetchBalance() {
|
||||
try {
|
||||
loading = true;
|
||||
const response = await fetch('/api/cospend/balance');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch balance');
|
||||
}
|
||||
balance = await response.json();
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMonthlyExpenses() {
|
||||
try {
|
||||
expensesLoading = true;
|
||||
const response = await fetch('/api/cospend/monthly-expenses');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch monthly expenses');
|
||||
}
|
||||
monthlyExpensesData = await response.json();
|
||||
if (response.ok) monthlyExpensesData = await response.json();
|
||||
} catch (err) {
|
||||
console.error('Error fetching monthly expenses:', err);
|
||||
// Don't show this error in the main error state
|
||||
} finally {
|
||||
expensesLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Progressive enhancement: refresh data if JavaScript is available
|
||||
onMount(() => {
|
||||
document.body.classList.add('js-loaded');
|
||||
fetchMonthlyExpenses();
|
||||
|
||||
const handleDashboardRefresh = () => { refreshAllComponents(); };
|
||||
window.addEventListener('dashboardRefresh', handleDashboardRefresh);
|
||||
return () => { window.removeEventListener('dashboardRefresh', handleDashboardRefresh); };
|
||||
});
|
||||
|
||||
// Function to refresh all dashboard components after payment deletion
|
||||
async function refreshAllComponents() {
|
||||
// Refresh the main balance and recent activity
|
||||
await Promise.all([
|
||||
fetchBalance(),
|
||||
fetchMonthlyExpenses()
|
||||
]);
|
||||
await Promise.all([invalidateAll(), fetchMonthlyExpenses()]);
|
||||
|
||||
// Refresh the enhanced balance component if it exists and has a refresh method
|
||||
if (enhancedBalanceComponent && enhancedBalanceComponent.refresh) {
|
||||
|
||||
@@ -475,6 +475,7 @@
|
||||
<div class="edit-modal" onclick={(e) => e.stopPropagation()}>
|
||||
<h3>{parseQuantity(editingItem.name).name}</h3>
|
||||
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="edit-label">Kategorie</label>
|
||||
<div class="category-picker">
|
||||
{#each SHOPPING_CATEGORIES as cat}
|
||||
@@ -492,6 +493,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="edit-label">Icon</label>
|
||||
<div class="icon-search">
|
||||
<Search size={14} />
|
||||
@@ -838,6 +840,7 @@
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,16 @@
|
||||
let { data } = $props();
|
||||
|
||||
// Use server-side data with progressive enhancement
|
||||
// svelte-ignore state_referenced_locally
|
||||
let payments = $state(data.payments || []);
|
||||
let loading = $state(false); // Start as false since we have server data
|
||||
/** @type {string | null} */
|
||||
let error = $state(null);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let currentPage = $state(Math.floor(data.currentOffset / data.limit));
|
||||
// svelte-ignore state_referenced_locally
|
||||
let limit = $state(data.limit || 20);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let hasMore = $state(data.hasMore || false);
|
||||
|
||||
// Re-sync local state when server data changes (e.g. URL param navigation)
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
// Initialize form data with server values if available (for error handling)
|
||||
/** @type {Record<string, any>} */
|
||||
// svelte-ignore state_referenced_locally
|
||||
const formValues = form?.values || {};
|
||||
// svelte-ignore state_referenced_locally
|
||||
let formData = $state({
|
||||
title: /** @type {string} */ (formValues.title || ''),
|
||||
description: /** @type {string} */ (formValues.description || ''),
|
||||
@@ -31,6 +33,7 @@
|
||||
});
|
||||
|
||||
// Recurring payment settings
|
||||
// svelte-ignore state_referenced_locally
|
||||
let recurringData = $state({
|
||||
frequency: /** @type {string} */ (formValues.recurringFrequency || 'monthly'),
|
||||
cronExpression: /** @type {string} */ (formValues.recurringCronExpression || ''),
|
||||
@@ -49,7 +52,9 @@
|
||||
let personalAmounts = $state({});
|
||||
let loading = $state(false);
|
||||
/** @type {string | null} */
|
||||
// svelte-ignore state_referenced_locally
|
||||
let error = $state(form?.error || null);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let predefinedMode = $state(data.predefinedUsers.length > 0);
|
||||
let jsEnhanced = $state(false);
|
||||
let cronError = $state(false);
|
||||
@@ -67,6 +72,7 @@
|
||||
let exchangeRateTimeout = $state();
|
||||
|
||||
// Initialize users from server data for no-JS support (use data directly to avoid reactivity warning)
|
||||
// svelte-ignore state_referenced_locally
|
||||
const initialUsers = data.predefinedUsers.length > 0 ? [...data.predefinedUsers] : (data.currentUser ? [data.currentUser] : []);
|
||||
let users = $state(initialUsers);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { goto, invalidateAll } from '$app/navigation';
|
||||
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
||||
import { getCategoryEmoji, getCategoryName } from '$lib/utils/categories';
|
||||
import EditButton from '$lib/components/EditButton.svelte';
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
// Use server-side data with progressive enhancement
|
||||
/** @type {any | null} */
|
||||
let payment = $state(data.payment || null);
|
||||
let loading = $state(false); // Start as false since we have server data
|
||||
let payment = $derived(data.payment || null);
|
||||
let loading = $state(false);
|
||||
/** @type {string | null} */
|
||||
let error = $state(null);
|
||||
|
||||
@@ -24,26 +24,10 @@
|
||||
|
||||
// Only refresh if we don't have server data
|
||||
if (!payment) {
|
||||
await loadPayment();
|
||||
await invalidateAll();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadPayment() {
|
||||
try {
|
||||
loading = true;
|
||||
const response = await fetch(`/api/cospend/payments/${data.paymentId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load payment');
|
||||
}
|
||||
const result = await response.json();
|
||||
payment = result.payment;
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function formatAmountWithCurrency(/** @type {any} */ payment) {
|
||||
if (payment.currency === 'CHF' || !payment.originalAmount) {
|
||||
return formatCurrency(payment.amount, 'CHF', 'de-CH');
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let formData = $state({
|
||||
title: '',
|
||||
description: '',
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
let { data, form } = $props();
|
||||
|
||||
// Use server-side data with progressive enhancement
|
||||
let debtData = $state(data.debtData || {
|
||||
let debtData = $derived(data.debtData || {
|
||||
whoOwesMe: [],
|
||||
whoIOwe: [],
|
||||
totalOwedToMe: 0,
|
||||
@@ -18,9 +18,11 @@
|
||||
});
|
||||
let loading = $state(false); // Start as false since we have server data
|
||||
/** @type {string | null} */
|
||||
// svelte-ignore state_referenced_locally
|
||||
let error = $state(data.error || form?.error || null);
|
||||
/** @type {{ type: string; from: string; to: string; amount: number; description: string } | null} */
|
||||
let selectedSettlement = $state(null);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let settlementAmount = $state(form?.values?.amount || '');
|
||||
let submitting = $state(false);
|
||||
let predefinedMode = isPredefinedUsersMode();
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let sessions = $state(data.sessions?.sessions ? [...data.sessions.sessions] : []);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let total = $state(data.sessions?.total ? data.sessions.total : 0);
|
||||
let loading = $state(false);
|
||||
let page = $state(1);
|
||||
|
||||
@@ -13,14 +13,20 @@
|
||||
let { data } = $props();
|
||||
const workout = getWorkout();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let latest = $state(data.latest ? { ...data.latest } : {});
|
||||
// svelte-ignore state_referenced_locally
|
||||
let measurements = $state(data.measurements?.measurements ? [...data.measurements.measurements] : []);
|
||||
let showWeightHistory = $state(false);
|
||||
|
||||
// Profile fields (sex, height, birth year) — stored in FitnessGoal
|
||||
// svelte-ignore state_referenced_locally
|
||||
let savedSex = $state(data.profile?.sex ?? 'male');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let profileSex = $state(data.profile?.sex ?? 'male');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let profileHeight = $state(data.profile?.heightCm != null ? String(data.profile.heightCm) : '');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let profileBirthYear = $state(data.profile?.birthYear != null ? String(data.profile.birthYear) : '');
|
||||
let profileSaving = $state(false);
|
||||
let profileEditing = $state(false);
|
||||
@@ -146,7 +152,8 @@
|
||||
{#if profileEditing}
|
||||
<div class="profile-fields">
|
||||
<div class="form-group">
|
||||
<label>{t('sex', lang)}</label>
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label>{t('sex', lang)}</label>
|
||||
<div class="sex-pills">
|
||||
<button class="sex-pill" class:active={profileSex === 'male'} onclick={() => profileSex = 'male'}>
|
||||
<Mars size={14} /> {t('male', lang)}
|
||||
@@ -357,7 +364,7 @@
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.form-group input, .form-group select {
|
||||
.form-group input {
|
||||
padding: 0.4rem 0.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
const measureSlug = $derived(lang === 'en' ? 'measure' : 'messen');
|
||||
|
||||
let { data } = $props();
|
||||
// svelte-ignore state_referenced_locally
|
||||
const m = data.measurement?.measurement;
|
||||
|
||||
let saving = $state(false);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
let { data } = $props();
|
||||
|
||||
// --- Date navigation ---
|
||||
// svelte-ignore state_referenced_locally
|
||||
let currentDate = $state(data.date);
|
||||
const todayStr = new Date().toISOString().slice(0, 10);
|
||||
const isToday = $derived(currentDate === todayStr);
|
||||
@@ -37,7 +38,9 @@
|
||||
}
|
||||
|
||||
// --- Entries ---
|
||||
// svelte-ignore state_referenced_locally
|
||||
let entries = $state(data.foodLog?.entries ?? []);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let recipeImages = $state(data.recipeImages ?? {});
|
||||
|
||||
async function loadEntries() {
|
||||
@@ -53,13 +56,21 @@
|
||||
});
|
||||
|
||||
// --- Goals ---
|
||||
// svelte-ignore state_referenced_locally
|
||||
let goalCalories = $state(data.goal?.dailyCalories ?? null);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let goalProteinMode = $state(data.goal?.proteinMode ?? 'fixed');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let goalProteinTarget = $state(data.goal?.proteinTarget ?? null);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let goalFatPercent = $state(data.goal?.fatPercent ?? null);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let goalCarbPercent = $state(data.goal?.carbPercent ?? null);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let goalSex = $state(data.goal?.sex ?? 'male');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let activityLevel = $state(data.goal?.activityLevel ?? 'light');
|
||||
// svelte-ignore state_referenced_locally
|
||||
let latestWeight = $state(data.latestWeight?.weight?.value ?? null);
|
||||
|
||||
let showGoalEditor = $state(false);
|
||||
@@ -395,6 +406,7 @@
|
||||
const carbGoalGrams = $derived(goalCalories && goalCarbPercent ? (goalCalories * goalCarbPercent / 100) / 4 : null);
|
||||
|
||||
// --- Burned kcal ---
|
||||
// svelte-ignore state_referenced_locally
|
||||
let exerciseKcal = $state(Number(data.exerciseKcal) || 0);
|
||||
|
||||
// BMR via Mifflin-St Jeor (doi:10.1093/ajcn/51.2.241)
|
||||
@@ -2417,9 +2429,6 @@
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.meal-section {
|
||||
|
||||
}
|
||||
.meal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -2505,6 +2514,7 @@
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.food-card-link {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
|
||||
import MuscleHeatmap from '$lib/components/fitness/MuscleHeatmap.svelte';
|
||||
@@ -35,8 +36,8 @@
|
||||
|
||||
const stats = $derived(data.stats ?? {});
|
||||
|
||||
let goalStreak = $state(data.goal?.streak ?? 0);
|
||||
let goalWeekly = $state(data.goal?.weeklyWorkouts ?? null);
|
||||
let goalStreak = $derived(data.goal?.streak ?? 0);
|
||||
let goalWeekly = $derived(data.goal?.weeklyWorkouts ?? null);
|
||||
let goalEditing = $state(false);
|
||||
let goalInput = $state(4);
|
||||
let goalSaving = $state(false);
|
||||
@@ -57,9 +58,7 @@
|
||||
body: JSON.stringify({ weeklyWorkouts: goalInput })
|
||||
});
|
||||
if (res.ok) {
|
||||
const d = await res.json();
|
||||
goalWeekly = d.weeklyWorkouts;
|
||||
goalStreak = d.streak;
|
||||
await invalidateAll();
|
||||
goalEditing = false;
|
||||
} else {
|
||||
const err = await res.json().catch(() => null);
|
||||
|
||||
@@ -18,13 +18,16 @@
|
||||
|
||||
const workout = getWorkout();
|
||||
const sync = getWorkoutSync();
|
||||
// svelte-ignore state_referenced_locally
|
||||
let templates = $state(data.templates?.templates ? [...data.templates.templates] : []);
|
||||
let seeded = $state(false);
|
||||
|
||||
// Schedule state
|
||||
/** @type {string[]} */
|
||||
// svelte-ignore state_referenced_locally
|
||||
let scheduleOrder = $state(data.schedule?.schedule?.templateOrder ?? []);
|
||||
/** @type {string | null} */
|
||||
// svelte-ignore state_referenced_locally
|
||||
let nextTemplateId = $state(data.schedule?.nextTemplateId ?? null);
|
||||
let showScheduleEditor = $state(false);
|
||||
/** @type {string[]} */
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let tasks = $state(data.tasks || []);
|
||||
let stats = $state(data.stats || { userStats: [], userStickers: [], recentCompletions: [] });
|
||||
let tasks = $derived(data.tasks || []);
|
||||
let stats = $derived(data.stats || { userStats: [], userStickers: [], recentCompletions: [] });
|
||||
let currentUser = $derived(data.session?.user?.nickname || '');
|
||||
let myStat = $derived(stats.userStats.find((/** @type {any} */ s) => s._id === currentUser));
|
||||
let showForm = $state(false);
|
||||
@@ -130,12 +130,7 @@
|
||||
}
|
||||
|
||||
async function refreshTasks() {
|
||||
const [tasksRes, statsRes] = await Promise.all([
|
||||
fetch('/api/tasks'),
|
||||
fetch('/api/tasks/stats')
|
||||
]);
|
||||
if (tasksRes.ok) tasks = (await tasksRes.json()).tasks;
|
||||
if (statsRes.ok) stats = await statsRes.json();
|
||||
await invalidateAll();
|
||||
}
|
||||
|
||||
async function handleTaskSaved() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { STICKERS, getStickerById, getRarityColor } from '$lib/utils/stickers';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { de } from 'date-fns/locale';
|
||||
@@ -13,7 +14,7 @@
|
||||
/** @type {import('$lib/utils/stickers').Sticker | null} */
|
||||
let selectedSticker = $state(null);
|
||||
|
||||
let stats = $state(data.stats || { userStats: [], userStickers: [], recentCompletions: [] });
|
||||
let stats = $derived(data.stats || { userStats: [], userStickers: [], recentCompletions: [] });
|
||||
let currentUser = $derived(data.session?.user?.nickname || '');
|
||||
|
||||
const rarityLabels = /** @type {Record<string, string>} */ ({
|
||||
@@ -69,19 +70,13 @@
|
||||
/** @param {string} id */
|
||||
async function deleteCompletion(id) {
|
||||
const res = await fetch(`/api/tasks/completions/${id}`, { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
const statsRes = await fetch('/api/tasks/stats');
|
||||
if (statsRes.ok) stats = await statsRes.json();
|
||||
}
|
||||
if (res.ok) await invalidateAll();
|
||||
}
|
||||
|
||||
async function clearHistory() {
|
||||
if (!confirm('Deinen gesamten Verlauf und alle Sticker wirklich löschen? Das kann nicht rückgängig gemacht werden.')) return;
|
||||
const res = await fetch('/api/tasks/stats', { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
const [statsRes] = await Promise.all([fetch('/api/tasks/stats')]);
|
||||
if (statsRes.ok) stats = await statsRes.json();
|
||||
}
|
||||
if (res.ok) await invalidateAll();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user