front-back integrated

This commit is contained in:
2026-09-30 02:25:03 +03:00
parent 93ca76c6e1
commit 1f81632083
13 changed files with 298 additions and 27 deletions
+102 -6
View File
@@ -1,4 +1,20 @@
import { state, saveState } from './state.js';
import { state, saveState, generateUUID } from './state.js';
import { fetchWorkoutTypes, fetchMetricTypes, fetchClients, syncData } from './api.js';
import { renderMainScreen } from './render/main-screen.js';
export async function initAppData() {
try {
// Загружаем справочники с бэкенда
state.workoutTypes = await fetchWorkoutTypes();
state.metricTypes = await fetchMetricTypes();
state.clients = await fetchClients();
saveState();
console.log('Справочники загружены с бэкенда');
} catch (e) {
console.warn('Не удалось загрузить справочники (возможно, бэк недоступен):', e);
// Не ломаем приложение, работаем офлайн с тем, что есть
}
}
export function getDirtyWorkouts() {
return state.workouts.filter(w => !w.synced);
@@ -10,9 +26,89 @@ export async function sync() {
alert('Нет данных для синхронизации');
return;
}
// TODO: заменить на fetch('/api/sync', ...)
console.log('Syncing:', JSON.stringify(dirty, null, 2));
dirty.forEach(w => w.synced = true);
saveState();
alert(`Отправлено тренировок: ${dirty.length}`);
const btn = document.querySelector('.header-actions .btn-secondary');
if (btn) {
btn.disabled = true;
btn.textContent = '⏳ Отправка...';
}
try {
// Маппинг данных из формата фронтенда в формат бэкенда
const payload = {
workouts: dirty.map(w => {
// Находим ID типа тренировки по его имени
const wt = state.workoutTypes.find(t => t.name === w.type);
return {
sync_id: w.sync_id || generateUUID(),
client_id: state.currentClientId || null,
workout_type_id: wt ? wt.id : null,
start_time: w.startTime,
end_time: w.endTime,
notes: w.notes || "",
workout_exercises: (w.exercises || []).map((ex, exIdx) => ({
sync_id: ex.sync_id || generateUUID(),
exercise_id: ex.exercise_id || null,
exercise_name: ex.name,
sort_order: exIdx,
sets: (ex.sets || []).map((s, sIdx) => ({
sync_id: s.sync_id || generateUUID(),
is_dropset: s.isDropset ? 1 : 0,
weight: s.isDropset ? null : (parseFloat(s.weight) || null),
reps: s.isDropset ? null : (parseInt(s.reps) || null),
rating: s.rating || 3,
notes: s.notes || "",
sort_order: sIdx,
timestamp: s.timestamp || new Date().toISOString(),
dropset_items: s.isDropset ? (s.subSets || []).map((item, idx) => ({
weight: parseFloat(item.weight) || 0,
reps: parseInt(item.reps) || 0,
sort_order: idx
})) : []
}))
})),
cardio_sessions: (w.cardio || []).map((c, cIdx) => ({
sync_id: c.sync_id || generateUUID(),
cardio_type: c.type,
purpose: c.purpose,
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: cIdx
}))
};
})
};
console.log('📤 Отправляем на синхронизацию:', payload);
const response = await syncData(payload);
console.log('✅ Ответ синхронизации:', response);
// Помечаем локально как синхронизированные
dirty.forEach(w => w.synced = true);
saveState();
// Перерисовываем интерфейс (чтобы обновить бейджи и read-only режим)
if (typeof window.renderMainScreen === 'function') {
window.renderMainScreen();
} else {
// Fallback если функция не экспортирована в window
location.reload();
}
alert(`Успешно синхронизировано: ${response.created.workouts || 0} тренировок`);
} catch (e) {
console.error('❌ Sync error:', e);
alert('Ошибка синхронизации: ' + e.message);
} finally {
if (btn) {
btn.disabled = false;
btn.textContent = '🔄';
}
}
}