{#if editingId === m._id}
{:else}
@@ -1283,9 +1339,9 @@
outline: none;
border-color: var(--color-primary);
}
- .edit-input.edit-date {
- flex: 1 1 120px;
- min-width: 110px;
+ .edit-date-wrap {
+ flex: 1 1 140px;
+ min-width: 130px;
}
.edit-num {
display: inline-flex;
@@ -1338,13 +1394,29 @@
color: var(--color-text-primary);
}
.edit-more {
- flex-basis: 100%;
- font-size: 0.68rem;
- color: var(--color-text-tertiary);
+ display: inline-flex;
+ align-items: center;
+ gap: 0.3rem;
+ padding: 0.3rem 0.7rem;
+ border: 1px dashed color-mix(in oklab, var(--color-primary) 40%, transparent);
+ border-radius: var(--radius-pill);
+ background: color-mix(in oklab, var(--color-primary) 5%, transparent);
+ color: var(--color-text-secondary);
+ font-size: 0.7rem;
+ font-weight: 600;
text-decoration: none;
- padding-top: 0.1rem;
+ white-space: nowrap;
+ transition: border-color var(--transition-fast, 120ms), background var(--transition-fast, 120ms), color var(--transition-fast, 120ms);
+ }
+ .edit-more:hover {
+ border-style: solid;
+ border-color: var(--color-primary);
+ background: color-mix(in oklab, var(--color-primary) 10%, transparent);
+ color: var(--color-primary);
+ }
+ @media (max-width: 480px) {
+ .edit-more-label { display: none; }
}
- .edit-more:hover { color: var(--color-primary); }
/* Desktop 2-col layout */
@media (min-width: 1024px) {
diff --git a/src/routes/fitness/[measure=fitnessMeasure]/body-parts/+page.svelte b/src/routes/fitness/[measure=fitnessMeasure]/body-parts/+page.svelte
index 4c70fda6..9f780cb7 100644
--- a/src/routes/fitness/[measure=fitnessMeasure]/body-parts/+page.svelte
+++ b/src/routes/fitness/[measure=fitnessMeasure]/body-parts/+page.svelte
@@ -6,6 +6,7 @@
import { cubicOut } from 'svelte/easing';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
import { toast } from '$lib/js/toast.svelte';
+ import { confirm } from '$lib/js/confirmDialog.svelte';
import DatePicker from '$lib/components/DatePicker.svelte';
import SaveFab from '$lib/components/SaveFab.svelte';
import Toggle from '$lib/components/Toggle.svelte';
@@ -188,11 +189,42 @@
}
saving = true;
try {
- const res = await fetch('/api/fitness/measurements', {
+ const body = { date: formDate, measurements: ms };
+ /** @param {boolean} overwrite */
+ const doPost = (overwrite) => fetch(`/api/fitness/measurements${overwrite ? '?overwrite=1' : ''}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ date: formDate, measurements: ms })
+ body: JSON.stringify(body)
});
+ let res = await doPost(false);
+ if (res.status === 409) {
+ const { conflicts } = await res.json();
+ /** @type {Record} */
+ const partKeyMap = {
+ leftBicep: 'l_bicep', rightBicep: 'r_bicep',
+ leftForearm: 'l_forearm', rightForearm: 'r_forearm',
+ leftThigh: 'l_thigh', rightThigh: 'r_thigh',
+ leftCalf: 'l_calf', rightCalf: 'r_calf'
+ };
+ /** @param {{ key: string, oldVal: unknown, newVal: unknown }} c */
+ const fmtConflict = (c) => {
+ const part = c.key.startsWith('measurements.') ? c.key.slice('measurements.'.length) : c.key;
+ const label = t(partKeyMap[part] ?? part, lang);
+ return `${label} (${c.oldVal} cm → ${c.newVal} cm)`;
+ };
+ const fields = conflicts.map(fmtConflict).join(', ');
+ const ok = await confirm(
+ t('overwrite_message', lang).replace('{fields}', fields),
+ {
+ title: t('overwrite_title', lang),
+ confirmText: t('overwrite_confirm', lang),
+ cancelText: t('cancel', lang),
+ destructive: true
+ }
+ );
+ if (!ok) { saving = false; return; }
+ res = await doPost(true);
+ }
if (res.ok) {
toast.success(lang === 'en' ? 'Measurement saved' : 'Messung gespeichert');
await goto(`/fitness/${measureSlug}`);