import { state, saveState, generateUUID } from "./state.js"; import { syncData } from "./api.js"; import { renderMainScreen } from "./render/main-screen.js"; export async function initAppData() { try { const { fetchWorkoutTypes, fetchMetricTypes } = await import("./api.js"); state.workoutTypes = await fetchWorkoutTypes(); state.metricTypes = await fetchMetricTypes(); saveState(); console.log("Справочники загружены с бэкенда"); } catch (e) { console.warn("Не удалось загрузить справочники (возможно, бэк недоступен):", e); } } export function getDirtyWorkouts() { return state.workouts.filter((w) => !w.synced); } function ensureSyncId(obj) { if (!obj) return null; if (!obj.sync_id) { obj.sync_id = generateUUID(); } return obj.sync_id; } function findWorkoutTypeByName(name) { if (!name) return null; return state.workoutTypes.find((t) => t.name === name) || null; } function findExerciseRefByName(name) { const normalized = String(name || "").trim().toLowerCase(); if (!normalized) return null; return ( state.exerciseRef.find( (e) => String(e.name || "").trim().toLowerCase() === normalized ) || null ); } function findClientByLocalId(clientId) { if (clientId === undefined || clientId === null || clientId === "") return null; return ( state.clients.find((c) => String(c.id) === String(clientId)) || null ); } function buildClientsPayload() { return state.clients.map((c) => { ensureSyncId(c); return { sync_id: c.sync_id, full_name: c.full_name || "Без имени", birth_date: c.birth_date || null, goal: c.goal || "", notes: c.notes || "", color: c.color || null, is_active: c.is_active === undefined ? 1 : c.is_active, }; }); } function buildExercisesPayload() { return state.exerciseRef.map((e) => { ensureSyncId(e); const wt = findWorkoutTypeByName(e.workoutType); return { sync_id: e.sync_id, name: e.name, workout_type_id: wt ? wt.id : null, }; }); } function buildSetsPayload(sets) { return (sets || []).map((s, setIndex) => { ensureSyncId(s); const isDropset = !!s.isDropset; return { sync_id: s.sync_id, is_dropset: isDropset ? 1 : 0, weight: isDropset ? null : (parseFloat(s.weight) || null), reps: isDropset ? null : (parseInt(s.reps, 10) || null), rating: s.rating || 3, notes: s.notes || "", sort_order: s.sort_order ?? setIndex, timestamp: s.timestamp || new Date().toISOString(), dropset_items: isDropset ? (s.subSets || []).map((item, itemIndex) => ({ weight: parseFloat(item.weight) || 0, reps: parseInt(item.reps, 10) || 0, sort_order: item.sort_order ?? itemIndex, })) : [], }; }); } function buildWorkoutExercisesPayload(exercises) { return (exercises || []).map((ex, exIndex) => { ensureSyncId(ex); const exerciseRef = findExerciseRefByName(ex.name); return { sync_id: ex.sync_id, exercise_sync_id: exerciseRef ? exerciseRef.sync_id : null, exercise_id: ex.exercise_id || null, exercise_name: ex.name, sort_order: ex.sort_order ?? exIndex, sets: buildSetsPayload(ex.sets), }; }); } function buildCardioPayload(cardioList) { return (cardioList || []).map((c, cIndex) => { ensureSyncId(c); return { sync_id: c.sync_id, cardio_type: c.type || "другое", purpose: c.purpose || "separate", duration: c.duration || null, distance: c.distance || null, speed: c.speed || null, incline: c.incline || null, resistance: c.resistance || null, notes: c.notes || "", sort_order: c.sort_order ?? cIndex, }; }); } function buildWorkoutsPayload(dirty) { return dirty.map((w) => { ensureSyncId(w); const client = findClientByLocalId(w.client_id); const wt = findWorkoutTypeByName(w.type); return { sync_id: w.sync_id, client_id: null, client_sync_id: client ? client.sync_id : null, workout_type_id: wt ? wt.id : null, start_time: w.startTime, end_time: w.endTime || null, notes: w.notes || "", workout_exercises: buildWorkoutExercisesPayload(w.exercises), cardio_sessions: buildCardioPayload(w.cardio), }; }); } export async function sync() { const dirty = getDirtyWorkouts(); if (dirty.length === 0) { alert("Нет данных для синхронизации"); return; } const btn = document.querySelector(".header-actions .btn-secondary"); if (btn) { btn.disabled = true; btn.textContent = "⏳"; } try { // Генерируем sync_id локально и сохраняем, чтобы при повторе не плодить дубли state.clients.forEach(ensureSyncId); state.exerciseRef.forEach(ensureSyncId); dirty.forEach((w) => { ensureSyncId(w); (w.exercises || []).forEach((ex) => { ensureSyncId(ex); (ex.sets || []).forEach(ensureSyncId); }); (w.cardio || []).forEach(ensureSyncId); }); saveState(); const payload = { clients: buildClientsPayload(), exercises: buildExercisesPayload(), workouts: buildWorkoutsPayload(dirty), body_metrics: [], // динамические замеры подключим следующим этапом }; console.log("📤 Отправляем на синхронизацию:", payload); const response = await syncData(payload); console.log("✅ Ответ синхронизации:", response); dirty.forEach((w) => { w.synced = true; }); saveState(); if (typeof window.renderMainScreen === "function") { window.renderMainScreen(); } else { renderMainScreen(); } const createdWorkouts = response.created?.workouts || 0; const updatedWorkouts = response.updated?.workouts || 0; const warnings = response.warnings || []; let message = `Синхронизировано: создано ${createdWorkouts}, обновлено ${updatedWorkouts}`; if (warnings.length) { message += `\n\nПредупреждения:\n${warnings.join("\n")}`; } alert(message); } catch (e) { console.error("❌ Sync error:", e); alert("Ошибка синхронизации: " + e.message); } finally { if (btn) { btn.disabled = false; btn.textContent = "🔄"; } } }