import React, { useState, useEffect } from 'react'; import { Home, Dumbbell, Plus, Minus, Droplet, Check, Camera, ArrowRight, Flame, TrendingUp, Utensils, Download, Trash2, X, Ruler, LogOut, User, Save, Settings, Coffee, Image as ImageIcon, FileText, ChevronDown, Calendar as CalendarIcon, RefreshCw, Edit3, PlusCircle, ToggleLeft, ToggleRight, Edit, Eye, EyeOff } from 'lucide-react'; import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid, Legend } from 'recharts'; import { initializeApp } from 'firebase/app'; import { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut, onAuthStateChanged, setPersistence, browserLocalPersistence } from 'firebase/auth'; import { getFirestore, collection, doc, getDoc, setDoc, addDoc, deleteDoc, updateDoc, onSnapshot, query, orderBy, limit, serverTimestamp, getDocs, writeBatch, where } from 'firebase/firestore'; // --- CONFIGURAÇÃO FIREBASE --- const firebaseConfig = { apiKey: "AIzaSyA0TP1Hh3VFm_eDWWtXuyDGcj0W5Be8K3M", authDomain: "fittrack-pessoal.firebaseapp.com", projectId: "fittrack-pessoal", storageBucket: "fittrack-pessoal.firebasestorage.app", messagingSenderId: "703616634790", appId: "1:703616634790:web:33120b9c817ed862618967" }; // Inicialização segura const app = initializeApp(firebaseConfig); const auth = getAuth(app); setPersistence(auth, browserLocalPersistence).catch(console.error); const db = getFirestore(app); const appId = 'personal-fitness-app'; // --- UTILS --- const compressImage = (file) => { return new Promise((resolve) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = (event) => { const img = new Image(); img.src = event.target.result; img.onload = () => { const canvas = document.createElement('canvas'); const MAX_WIDTH = 600; const scaleSize = MAX_WIDTH / img.width; canvas.width = MAX_WIDTH; canvas.height = img.height * scaleSize; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); resolve(canvas.toDataURL('image/jpeg', 0.6)); }; }; }); }; const WEEKDAYS = ['domingo', 'segunda', 'terca', 'quarta', 'quinta', 'sexta', 'sabado']; const WORKOUT_TYPES = ['A', 'B', 'C', 'D', 'E', 'F']; // Função segura para formatar data (evita bug de -1 dia) const formatDate = (dateString) => { if (!dateString) return '---'; const parts = dateString.split('-'); if (parts.length !== 3) return dateString; const [year, month, day] = parts; return `${day}/${month}/${year}`; }; const getLocalToday = () => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; }; // --- COMPONENTES AUXILIARES --- const Modal = ({ isOpen, onClose, title, children }) => { if (!isOpen) return null; return (

{title}

{children}
); }; // --- TELA DE LOGIN --- const AuthScreen = ({ onLogin }) => { const [isRegistering, setIsRegistering] = useState(false); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const handleAuth = async (e) => { e.preventDefault(); setError(''); setLoading(true); try { if (isRegistering) { await createUserWithEmailAndPassword(auth, email, password); } else { await signInWithEmailAndPassword(auth, email, password); } } catch (err) { let msg = "Erro ao entrar."; if (err.code === 'auth/invalid-email') msg = "E-mail inválido."; if (err.code === 'auth/wrong-password') msg = "Senha incorreta."; if (err.code === 'auth/user-not-found') msg = "Usuário não encontrado."; if (err.code === 'auth/email-already-in-use') msg = "E-mail já cadastrado."; if (err.code === 'auth/weak-password') msg = "Senha muito fraca."; setError(msg); } setLoading(false); }; return (

FitTrack Casal

Painel do Atleta

{error &&
{error}
}
setEmail(e.target.value)} />
setPassword(e.target.value)} />
); }; // --- COMPONENTES DA UI --- const TabButton = ({ active, onClick, icon: Icon, label, isMain }) => ( ); const Card = ({ children, title, className = "" }) => (
{title &&

{title}

} {children}
); // --- TELAS PRINCIPAIS --- // 1. DASHBOARD const Dashboard = ({ user, currentDate, setCurrentDate }) => { const [data, setData] = useState({ habits: { cardio: false, creatine: false, water: 0 }, meals: [] }); const [loading, setLoading] = useState(true); const [showWaterInput, setShowWaterInput] = useState(false); const [customWater, setCustomWater] = useState(''); const dateKey = currentDate.toISOString().split('T')[0]; useEffect(() => { if(!user) return; const unsub = onSnapshot(doc(db, 'artifacts', appId, 'users', user.uid, 'daily_logs', dateKey), (doc) => { if (doc.exists()) setData({ habits: { cardio: false, creatine: false, water: 0, ...(doc.data().habits || {}) }, meals: doc.data().meals || [] }); else setData({ habits: { cardio: false, creatine: false, water: 0 }, meals: [] }); setLoading(false); }); return () => unsub(); }, [user, dateKey]); const saveData = async (newData) => { if(!user) return; try { await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'daily_logs', dateKey), { ...newData, date: dateKey }, { merge: true }); } catch (e) { console.error(e); } }; const toggleHabit = (id) => saveData({ ...data, habits: { ...data.habits, [id]: !data.habits[id] } }); const updateWater = (v) => saveData({ ...data, habits: { ...data.habits, water: Math.max(0, (data.habits.water || 0) + v) } }); const handleCustomWater = () => { const val = parseInt(customWater); if (val > 0) { updateWater(val); setCustomWater(''); setShowWaterInput(false); }}; const totals = (data.meals || []).reduce((acc, m) => ({ p: acc.p + (parseInt(m.p)||0), c: acc.c + (parseInt(m.c)||0), f: acc.f + (parseInt(m.f)||0) }), { p: 0, c: 0, f: 0 }); const totalCals = (totals.p * 4) + (totals.c * 4) + (totals.f * 9); const changeDate = (d) => { const n = new Date(currentDate); n.setDate(n.getDate() + d); setCurrentDate(n); }; if (loading) return
Carregando...
; return (
{dateKey === new Date().toISOString().split('T')[0] ? "HOJE" : "DATA"}

{new Intl.DateTimeFormat('pt-BR', { day: 'numeric', month: 'short' }).format(currentDate)}

Calorias

{totalCals} kcal

Prot{totals.p}g
Carb{totals.c}g
Gord{totals.f}g
{[{ id: 'cardio', label: 'Cardio', icon: '🏃' }, { id: 'creatine', label: 'Creatina', icon: '💊' }].map(h => ( ))}

Hidratação

{(data.habits.water || 0) / 1000}L
{showWaterInput ? (
setCustomWater(e.target.value)} autoFocus />
) : (
)}
); }; // 2. TREINO (SISTEMA FLEXÍVEL ABCD + ATIVAR/DESATIVAR) const WorkoutView = ({ user, currentDate, setCurrentDate }) => { const [template, setTemplate] = useState(null); const [dailyLog, setDailyLog] = useState(null); const [logs, setLogs] = useState({}); const [loading, setLoading] = useState(true); const [currentWorkoutType, setCurrentWorkoutType] = useState(null); // 'A', 'B', 'C', 'D', 'Rest' const [availableWorkouts, setAvailableWorkouts] = useState({}); // { A: {isActive: true, description: ''}, ... } // Estados para o Gerenciador de Treinos const [showManager, setShowManager] = useState(false); const [editType, setEditType] = useState('A'); const [editExercises, setEditExercises] = useState([]); const [editMeta, setEditMeta] = useState({ isActive: true, description: '' }); // Metadados em edição const [savingTemplate, setSavingTemplate] = useState(false); const dateKey = currentDate.toISOString().split('T')[0]; // Carregar configurações de treino do usuário useEffect(() => { if (!user) return; const fetchWorkoutsMeta = async () => { // Busca todos os treinos para saber quais estão ativos e suas descrições const q = query(collection(db, 'artifacts', appId, 'users', user.uid, 'workouts')); const snap = await getDocs(q); const meta = {}; snap.forEach(doc => { const d = doc.data(); meta[doc.id] = { isActive: d.isActive !== false, // Padrão true se não existir description: d.description || '' }; }); // Se não tiver nenhum (primeiro acesso), cria padrão na memória para UI if (Object.keys(meta).length === 0) { WORKOUT_TYPES.forEach(t => meta[t] = { isActive: true, description: '' }); } setAvailableWorkouts(meta); }; fetchWorkoutsMeta(); }, [user, showManager]); // Recarrega ao fechar o manager useEffect(() => { if (!user) return; setLoading(true); const fetchDailyLog = onSnapshot(doc(db, 'artifacts', appId, 'users', user.uid, 'daily_logs', dateKey), async (docSnap) => { if (docSnap.exists()) { const d = docSnap.data(); setDailyLog(d); setLogs(d.workout_data || {}); if (d.workout_type) { setCurrentWorkoutType(d.workout_type); if (d.exercises_snapshot) { setTemplate({ exercises: d.exercises_snapshot }); } else if (d.workout_type !== 'Rest') { const tplRef = doc(db, 'artifacts', appId, 'users', user.uid, 'workouts', d.workout_type); const tplSnap = await getDoc(tplRef); if (tplSnap.exists()) setTemplate(tplSnap.data()); } else { setTemplate({ isRestDay: true }); } } else { setCurrentWorkoutType(null); setTemplate(null); } } else { setDailyLog(null); setLogs({}); setCurrentWorkoutType(null); setTemplate(null); } setLoading(false); }); return () => fetchDailyLog(); }, [user, dateKey]); // Carregar template para edição no modal useEffect(() => { if (!showManager || !user) return; const loadEditTemplate = async () => { const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'workouts', editType); const snap = await getDoc(docRef); if (snap.exists()) { const d = snap.data(); setEditExercises(d.exercises || []); setEditMeta({ isActive: d.isActive !== false, description: d.description || '' }); } else { setEditExercises([]); setEditMeta({ isActive: true, description: '' }); } }; loadEditTemplate(); }, [showManager, editType, user]); const selectWorkoutForToday = async (type) => { setCurrentWorkoutType(type); setLoading(true); if (type === 'Rest') { const payload = { workout_type: 'Rest', exercises_snapshot: null, workout_data: {} }; await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'daily_logs', dateKey), payload, { merge: true }); setTemplate({ isRestDay: true }); } else { const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'workouts', type); const snap = await getDoc(docRef); let exercises = []; if (snap.exists()) { exercises = snap.data().exercises || []; } else { exercises = Array.from({ length: 6 }, (_, i) => ({ name: `Exercício ${i+1}`, sets: "3x12" })); await setDoc(docRef, { exercises, isActive: true, description: '' }); } const payload = { workout_type: type, exercises_snapshot: exercises, workout_data: {} }; await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'daily_logs', dateKey), payload, { merge: true }); setTemplate({ exercises }); } setLoading(false); }; // --- FUNÇÕES DO GERENCIADOR DE TREINOS --- const addEditExercise = () => setEditExercises([...editExercises, { name: "", sets: "" }]); const removeEditExercise = (idx) => setEditExercises(editExercises.filter((_, i) => i !== idx)); const updateEditExercise = (idx, field, val) => { const newEx = [...editExercises]; newEx[idx][field] = val; setEditExercises(newEx); }; const saveEditTemplate = async () => { setSavingTemplate(true); try { await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'workouts', editType), { exercises: editExercises, isActive: editMeta.isActive, description: editMeta.description }); alert(`Configurações do Treino ${editType} salvas!`); } catch (e) { alert("Erro ao salvar: " + e.message); } setSavingTemplate(false); }; // --- FUNÇÕES DE LOG --- const handleCheck = async (exerciseName) => { const newData = { ...logs, [exerciseName]: { ...logs[exerciseName], done: !logs[exerciseName]?.done } }; setLogs(newData); const payload = { workout_data: newData }; if (!dailyLog?.exercises_snapshot && template?.exercises) { payload.exercises_snapshot = template.exercises; } await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'daily_logs', dateKey), payload, { merge: true }); }; const handleWeightChange = async (exerciseName, weight) => { const newData = { ...logs, [exerciseName]: { ...logs[exerciseName], weight: weight } }; setLogs(newData); const payload = { workout_data: newData }; if (!dailyLog?.exercises_snapshot && template?.exercises) { payload.exercises_snapshot = template.exercises; } await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'daily_logs', dateKey), payload, { merge: true }); }; const changeDate = (d) => { const n = new Date(currentDate); n.setDate(n.getDate() + d); setCurrentDate(n); }; // Filtra quais treinos estão ativos para mostrar na seleção const activeWorkoutsList = WORKOUT_TYPES.filter(t => availableWorkouts[t]?.isActive !== false); return (
{/* Modal de Gerenciamento */} setShowManager(false)} title="Gerenciar Treinos">
{WORKOUT_TYPES.map(t => ( ))}
Ativar este treino?
setEditMeta({...editMeta, description: e.target.value})} className="w-full p-2 mt-1 border rounded-lg text-sm" placeholder="Sem descrição" />
{editExercises.map((ex, idx) => (
updateEditExercise(idx, 'name', e.target.value)} /> updateEditExercise(idx, 'sets', e.target.value)} />
))} {editExercises.length === 0 &&
Nenhum exercício cadastrado.
}
{currentWorkoutType ? (currentWorkoutType === 'Rest' ? 'Descanso' : `Treino ${currentWorkoutType}`) : '---'} {new Intl.DateTimeFormat('pt-BR', { day: 'numeric', month: 'numeric' }).format(currentDate)}
{!currentWorkoutType ? (

Qual o treino de hoje?

{activeWorkoutsList.map(t => ( ))}
) : currentWorkoutType === 'Rest' ? (

Dia de Descanso

Aproveite para recuperar!

) : ( <>
Executando Treino {currentWorkoutType} {availableWorkouts[currentWorkoutType]?.description && {availableWorkouts[currentWorkoutType].description}}
{(template?.exercises || []).map((ex, idx) => { const log = logs[ex.name] || {}; return (

{ex.name}

{ex.sets}

Carga (kg): handleWeightChange(ex.name, e.target.value)} onBlur={(e) => handleWeightChange(ex.name, e.target.value)} />
); })} {(template?.exercises || []).length === 0 &&
Treino vazio.
Use a engrenagem para adicionar exercícios.
}
)}
); }; // 3. REFEIÇÕES const MealsView = ({ user, currentDate, setCurrentDate }) => { const [data, setData] = useState({ meals: [] }); const [showForm, setShowForm] = useState(false); const [newMeal, setNewMeal] = useState({ name: '', p: '', c: '', f: '' }); const dateKey = currentDate.toISOString().split('T')[0]; useEffect(() => { if(!user) return; const unsub = onSnapshot(doc(db, 'artifacts', appId, 'users', user.uid, 'daily_logs', dateKey), (doc) => { if (doc.exists()) setData({ meals: [], ...doc.data() }); else setData({ meals: [] }); }); return () => unsub(); }, [user, dateKey]); const addMeal = async () => { if (!newMeal.name) return; const mealToAdd = { id: Date.now().toString(), name: newMeal.name, p: parseInt(newMeal.p)||0, c: parseInt(newMeal.c)||0, f: parseInt(newMeal.f)||0 }; try { await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'daily_logs', dateKey), { ...data, meals: [...(data.meals||[]), mealToAdd], date: dateKey }, { merge: true }); setNewMeal({ name: '', p: '', c: '', f: '' }); setShowForm(false); } catch (e) { console.error(e); } }; const removeMeal = async (id) => { try { await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'daily_logs', dateKey), { ...data, meals: data.meals.filter(m => m.id !== id) }, { merge: true }); } catch (e) { console.error(e); }}; const changeDate = (d) => { const n = new Date(currentDate); n.setDate(n.getDate() + d); setCurrentDate(n); }; const getCals = (m) => (m.p*4 + m.c*4 + m.f*9); return (
REFEIÇÕES

{new Intl.DateTimeFormat('pt-BR', { day: 'numeric', month: 'short' }).format(currentDate)}

{(data.meals || []).map(m => (

{m.name}

P: {m.p}gC: {m.c}gG: {m.f}g
{getCals(m)} kcal
))}
{showForm &&
setNewMeal({...newMeal, name: e.target.value})} />
setNewMeal({...newMeal, p: e.target.value})} /> setNewMeal({...newMeal, c: e.target.value})} /> setNewMeal({...newMeal, f: e.target.value})} />
} {!showForm &&
}
); }; // 4. MEDIDAS const Measurements = ({ user, setView }) => { const [newCheckin, setNewCheckin] = useState({ weight: '', waist: '', chest: '', arm: '', photos: { front: null, side: null, back: null } }); const [saving, setSaving] = useState(false); const [date, setDate] = useState(getLocalToday()); // Usa data local agora const labels = { weight: 'Peso (kg)', waist: 'Cintura (cm)', chest: 'Tórax (cm)', arm: 'Braço (cm)' }; const handlePhotoUpload = async (e, type) => { const file = e.target.files[0]; if (!file) return; const compressed = await compressImage(file); setNewCheckin(prev => ({ ...prev, photos: { ...prev.photos, [type]: compressed } })); }; const saveCheckin = async () => { if(!user) return; setSaving(true); try { await addDoc(collection(db, 'artifacts', appId, 'users', user.uid, 'checkins'), { date: date, weight: parseFloat(newCheckin.weight)||0, waist: parseFloat(newCheckin.waist)||0, chest: parseFloat(newCheckin.chest)||0, arm: parseFloat(newCheckin.arm)||0, photos: newCheckin.photos, createdAt: serverTimestamp() }); alert('Salvo!'); setView('evolution'); } catch (e) { alert(e.message); } setSaving(false); }; return (

Medidas

setDate(e.target.value)} />
{['weight', 'waist', 'chest', 'arm'].map(f => (
setNewCheckin({...newCheckin, [f]: e.target.value})} />
))}
{['front', 'side', 'back'].map(p => (
{p === 'front' ? 'Frente' : p === 'side' ? 'Lado' : 'Costas'}
))}
); }; // 5. EVOLUÇÃO const Evolution = ({ user }) => { const [subView, setSubView] = useState('stats'); const [checkins, setCheckins] = useState([]); const [logs, setLogs] = useState([]); const [dateA, setDateA] = useState(''); const [dateB, setDateB] = useState(''); const [deleteId, setDeleteId] = useState(null); const [reportPeriod, setReportPeriod] = useState('daily'); const [editingCheckin, setEditingCheckin] = useState(null); // ID do checkin em edição const [editForm, setEditForm] = useState({}); const [privacyMode, setPrivacyMode] = useState(true); useEffect(() => { if(!user) return; const q1 = query(collection(db, 'artifacts', appId, 'users', user.uid, 'checkins'), orderBy('date', 'asc')); const unsub1 = onSnapshot(q1, (snap) => setCheckins(snap.docs.map(d => ({ id: d.id, ...d.data() })))); const q2 = query(collection(db, 'artifacts', appId, 'users', user.uid, 'daily_logs'), orderBy('date', 'desc'), limit(50)); const unsub2 = onSnapshot(q2, (snap) => setLogs(snap.docs.map(d => d.data()))); return () => { unsub1(); unsub2(); }; }, [user]); const confirmDelete = async () => { if (deleteId) { try { await deleteDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'checkins', deleteId)); setDeleteId(null); } catch (e) { alert("Erro: " + e.message); } } }; const handleEditClick = (c) => { setEditForm({ date: c.date, weight: c.weight, waist: c.waist, chest: c.chest, arm: c.arm }); setEditingCheckin(c.id); }; const saveEditCheckin = async () => { if (!editingCheckin) return; try { await updateDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'checkins', editingCheckin), { date: editForm.date, weight: parseFloat(editForm.weight) || 0, waist: parseFloat(editForm.waist) || 0, chest: parseFloat(editForm.chest) || 0, arm: parseFloat(editForm.arm) || 0 }); setEditingCheckin(null); } catch (e) { alert("Erro ao salvar edição: " + e.message); } }; const generateReport = () => { let text = `=== RELATÓRIO FITTRACK (${reportPeriod.toUpperCase()}) ===\n\n`; const now = new Date(); const todayStr = getLocalToday(); // Usa data local para filtro let filteredLogs = logs; let filteredCheckins = checkins; // Filter Logic applied to BOTH logs and checkins if (reportPeriod === 'daily') { filteredLogs = logs.filter(l => l.date === todayStr); filteredCheckins = checkins.filter(c => c.date === todayStr); } else if (reportPeriod === 'weekly') { const oneWeekAgo = new Date(); oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); const weekStr = oneWeekAgo.toISOString().split('T')[0]; filteredLogs = logs.filter(l => l.date >= weekStr); filteredCheckins = checkins.filter(c => c.date >= weekStr); } else if (reportPeriod === 'monthly') { const oneMonthAgo = new Date(); oneMonthAgo.setDate(oneMonthAgo.getDate() - 30); const monthStr = oneMonthAgo.toISOString().split('T')[0]; filteredLogs = logs.filter(l => l.date >= monthStr); filteredCheckins = checkins.filter(c => c.date >= monthStr); } if (filteredLogs.length === 0 && filteredCheckins.length === 0) { alert("Nenhum dado encontrado para este período."); return; } text += "=== MEDIDAS ===\n"; if (filteredCheckins.length > 0) { filteredCheckins.forEach(c => { // Formatação manual no relatório também text += `Data: ${formatDate(c.date)} | Peso: ${c.weight}kg | Cintura: ${c.waist}cm | Tórax: ${c.chest}cm | Braço: ${c.arm}cm\n`; }); } else { text += "Nenhuma medição neste período.\n"; } text += "\n=== DIÁRIO DE TREINO E DIETA ===\n"; filteredLogs.forEach(l => { // Formatação segura text += `\n--------------------------------------------------\n`; text += `DATA: ${formatDate(l.date)}\n`; // HÁBITOS text += `\n[HÁBITOS]\n`; text += `Água: ${l.habits?.water || 0}ml\n`; text += `Creatina: ${l.habits?.creatine ? 'SIM' : 'NÃO'}\n`; text += `Cardio: ${l.habits?.cardio ? 'SIM' : 'NÃO'}\n`; // ALIMENTAÇÃO const meals = l.meals || []; const totals = meals.reduce((acc, m) => ({ p: acc.p + (parseInt(m.p)||0), c: acc.c + (parseInt(m.c)||0), f: acc.f + (parseInt(m.f)||0) }), { p: 0, c: 0, f: 0 }); const totalCals = (totals.p * 4) + (totals.c * 4) + (totals.f * 9); text += `\n[ALIMENTAÇÃO]\n`; if (meals.length > 0) { meals.forEach(m => { text += ` - ${m.name}: ${m.p}g P / ${m.c}g C / ${m.f}g G\n`; }); text += ` TOTAL: ${totalCals} kcal (P:${totals.p}g C:${totals.c}g G:${totals.f}g)\n`; } else { text += ` Sem registros de refeição.\n`; } // TREINO text += `\n[TREINO]\n`; const workoutData = l.workout_data || {}; text += ` Tipo: ${l.workout_type === 'Rest' ? 'Descanso' : ('Treino ' + (l.workout_type || '?'))}\n`; const exercisesDone = Object.entries(workoutData).filter(([_, data]) => data.done); if (l.isRestDayOverride === true) { text += ` Dia de Descanso (Registrado).\n`; } else if (exercisesDone.length > 0) { exercisesDone.forEach(([name, data]) => { text += ` - ${name}: Carga ${data.weight || 0}kg\n`; }); } else { text += ` Não registrou cargas/conclusão.\n`; } }); const blob = new Blob([text], { type: 'text/plain' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `Relatorio_${reportPeriod}_${todayStr}.txt`; a.click(); }; // Calculate diffs vs First Checkin const firstCheckin = checkins.length > 0 ? checkins[0] : null; const getDiff = (val, field) => { if (!firstCheckin || !val) return null; const diff = (val - firstCheckin[field]).toFixed(1); const sign = diff > 0 ? '+' : ''; return `${sign}${diff}`; }; const checkinsReverse = [...checkins].reverse(); return (
{/* Modal de Exclusão */} setDeleteId(null)} title="Excluir Medição?">

Tem certeza? Essa ação não pode ser desfeita.

{/* Modal de Edição */} setEditingCheckin(null)} title="Editar Medição">
setEditForm({...editForm, date: e.target.value})} className="w-full p-2 border rounded-lg" />
setEditForm({...editForm, weight: e.target.value})} className="w-full p-2 border rounded-lg" />
setEditForm({...editForm, waist: e.target.value})} className="w-full p-2 border rounded-lg" />
setEditForm({...editForm, chest: e.target.value})} className="w-full p-2 border rounded-lg" />
setEditForm({...editForm, arm: e.target.value})} className="w-full p-2 border rounded-lg" />
{/* Header com Navegação */}
{[ { id: 'stats', label: 'Gráfico', icon: TrendingUp }, { id: 'photos', label: 'Fotos', icon: ImageIcon }, { id: 'logs', label: 'Diário', icon: FileText } ].map(tab => ( ))}

Evolução

{/* SUB-ABA: GRÁFICO E LISTA */} {subView === 'stats' && (
{checkins.length > 1 ? (
{/* Usa formatação manual na lista também */} formatDate(d).substring(0,5)} fontSize={10} axisLine={false} tickLine={false} />
) : (
Adicione mais medições para ver o gráfico.
)}
{checkinsReverse.map(c => (
{/* Usa formatação manual na lista também */} {formatDate(c.date)}
{c.photos?.front ? ( <>
) : (
)}
{[{l:'Peso', k:'weight', u:'kg'}, {l:'Cintura', k:'waist', u:'cm'}, {l:'Tórax', k:'chest', u:'cm'}, {l:'Braço', k:'arm', u:'cm'}].map(m => (
{m.l}
{c[m.k]}{m.u} {firstCheckin && c.id !== firstCheckin.id && ( {getDiff(c[m.k], m.k)} )}
))}
))}
)} {/* SUB-ABA: COMPARADOR DE FOTOS */} {subView === 'photos' && (
{dateA && dateB ? (
{['front', 'side', 'back'].map(pos => { const c1 = checkins.find(c => c.id === dateA); const c2 = checkins.find(c => c.id === dateB); if(!c1 || !c2) return null; return (

{pos === 'front' ? 'Frente' : pos === 'side' ? 'Lado' : 'Costas'}

DEPOIS
) })}
) : (

Selecione duas datas acima
para comparar.

)}
)} {/* SUB-ABA: DIÁRIO */} {subView === 'logs' && (
{logs.map(log => { const cals = log.meals ? log.meals.reduce((acc, m) => acc + (m.p*4 + m.c*4 + m.f*9), 0) : 0; const trained = log.workout_data && Object.values(log.workout_data).some(x => x.done); return (

{formatDate(log.date)}

{cals} kcal

{log.workout_type && log.workout_type !== 'Rest' && ( Treino {log.workout_type} )} {trained && !log.workout_type && Treinou} {log.habits?.cardio && Cardio} {log.habits?.creatine && Creatina} {log.habits?.water > 0 && ( {(log.habits.water / 1000)}L )}
) })} {logs.length === 0 &&
Nenhum registro de diário.
}
)}
); }; // --- APP PRINCIPAL --- export default function App() { const [user, setUser] = useState(null); const [view, setView] = useState('dashboard'); const [currentDate, setCurrentDate] = useState(new Date()); useEffect(() => { const unsub = onAuthStateChanged(auth, setUser); return () => unsub(); }, []); if (!user) return ; return (
{view === 'dashboard' && } {view === 'workout' && } {view === 'meals' && } {view === 'measurements' && } {view === 'evolution' && }
setView('dashboard')} icon={Home} label="Hoje" /> setView('workout')} icon={Dumbbell} label="Treino" />
setView('meals')} icon={Utensils} label="Refeição" isMain={true} />
setView('measurements')} icon={Ruler} label="Medidas" /> setView('evolution')} icon={TrendingUp} label="Evolução" />
); }