recipes: overhaul nutrition editor UI and defer saves to form submission
- Nutrition mappings and global overwrites are now local-only until the recipe is saved, preventing premature DB writes on generate/edit - Generate endpoint supports ?preview=true for non-persisting previews - Show existing nutrition data immediately instead of requiring generate - Replace raw checkboxes with Toggle component for global overwrites, initialized from existing NutritionOverwrite records - Fix search dropdown readability (solid backgrounds, proper theming) - Use fuzzy search (fzf-style) for manual nutrition ingredient lookup - Swap ingredient display: German primary, English in brackets - Allow editing g/u on manually mapped ingredients - Make translation optional: separate save (FAB) and translate buttons - "Vollständig neu übersetzen" now triggers actual full retranslation - Show existing translation inline instead of behind a button - Replace nord0 dark backgrounds with semantic theme variables
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
let { checked = $bindable(false), label = "", accentColor = "var(--color-primary)", href = undefined as string | undefined } = $props<{ checked?: boolean, label?: string, accentColor?: string, href?: string }>();
|
||||
let { checked = $bindable(false), label = "", accentColor = "var(--color-primary)", href = undefined as string | undefined, onchange = undefined as (() => void) | undefined } = $props<{ checked?: boolean, label?: string, accentColor?: string, href?: string, onchange?: () => void }>();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -96,7 +96,7 @@
|
||||
</a>
|
||||
{:else}
|
||||
<label>
|
||||
<input type="checkbox" bind:checked />
|
||||
<input type="checkbox" bind:checked onchange={onchange} />
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
onapproved?: (event: CustomEvent) => void;
|
||||
onskipped?: () => void;
|
||||
oncancelled?: () => void;
|
||||
onforceFullRetranslation?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -26,7 +25,6 @@
|
||||
onapproved,
|
||||
onskipped,
|
||||
oncancelled,
|
||||
onforceFullRetranslation
|
||||
}: Props = $props();
|
||||
|
||||
type TranslationState = 'idle' | 'translating' | 'preview' | 'approved' | 'error';
|
||||
@@ -204,7 +202,7 @@
|
||||
});
|
||||
|
||||
// Handle auto-translate button click
|
||||
async function handleAutoTranslate() {
|
||||
async function handleAutoTranslate(fullRetranslation = false) {
|
||||
translationState = 'translating';
|
||||
errorMessage = '';
|
||||
validationErrors = [];
|
||||
@@ -217,7 +215,7 @@
|
||||
},
|
||||
body: JSON.stringify({
|
||||
recipe: germanData,
|
||||
fields: isEditMode && changedFields.length > 0 ? changedFields : undefined,
|
||||
fields: isEditMode && !fullRetranslation && changedFields.length > 0 ? changedFields : undefined,
|
||||
oldRecipe: oldRecipeData, // For granular item-level change detection
|
||||
existingTranslation: englishData, // To merge with unchanged items
|
||||
}),
|
||||
@@ -358,11 +356,6 @@
|
||||
oncancelled?.();
|
||||
}
|
||||
|
||||
// Handle force full retranslation
|
||||
function handleForceFullRetranslation() {
|
||||
onforceFullRetranslation?.();
|
||||
}
|
||||
|
||||
// Get status badge color
|
||||
function getStatusColor(status: string): string {
|
||||
switch (status) {
|
||||
@@ -921,10 +914,10 @@ button:disabled {
|
||||
<button class="btn-danger" onclick={handleCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn-secondary" onclick={handleForceFullRetranslation}>
|
||||
<button class="btn-secondary" onclick={() => handleAutoTranslate(true)}>
|
||||
Vollständig neu übersetzen
|
||||
</button>
|
||||
<button class="btn-secondary" onclick={handleAutoTranslate}>
|
||||
<button class="btn-secondary" onclick={() => handleAutoTranslate()}>
|
||||
Re-translate
|
||||
</button>
|
||||
<button class="btn-primary" onclick={handleApprove}>
|
||||
|
||||
Reference in New Issue
Block a user