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 (
);
};
// --- 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
);
};
// --- 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)}
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 => (
))}
{editExercises.map((ex, idx) => (
))}
{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 (
);
})}
{(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 &&
}
{!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 (
);
};
// 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">
{/* 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" />
);
}