Evaluando propuestas

Fit app ganhe saúde

Publicado el 03 Noviembre, 2025 en Programación y Tecnología

Sobre este proyecto

Abierto

/* HealthApp.jsx Single-file React prototype (Tailwind + recharts)

Features included in this prototype:

Onboarding header + quick stats

Body metrics (weight, height) and BMI calculator with suggestions

Daily targets (water, steps, calories)

Activity log and progress chart (Recharts)

Simple reminders placeholder and profile card


How to run:

1. Create a React app (Vite or CRA)


2. Install dependencies: react, react-dom, tailwindcss, recharts npm i react react-dom recharts


3. Ensure Tailwind is configured in the project


4. Save this file as src/HealthApp.jsx and import it from src/main.jsx



Note: This prototype uses local component state and dummy data. For production add backend (GraphQL/REST), auth, data persistence, and notifications. */

Import React, { useState, useMemo } from "react"; import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from "recharts";

export default function HealthApp() { // Sample user profile & metrics const [profile, setProfile] = useState({ name: "Usuário", age: 29, heightCm: 178, weightKg: 78 }); const [water, setWater] = useState({ goalMl: 3000, drankMl: 1600 }); const [steps, setSteps] = useState({ goal: 10000, current: 6240 }); const [activities, setActivities] = useState([ { id: 1, date: "2025-10-28", type: "Caminhada", durationMin: 40, calories: 230 }, { id: 2, date: "2025-10-29", type: "Treino de Força", durationMin: 50, calories: 350 }, { id: 3, date: "2025-10-30", type: "Corrida", durationMin: 25, calories: 300 }, { id: 4, date: "2025-10-31", type: "Yoga", durationMin: 30, calories: 120 }, { id: 5, date: "2025-11-01", type: "Caminhada", durationMin: 60, calories: 360 }, ]);

const bmi = useMemo(() => { const h = profile.heightCm / 100; return +(profile.weightKg / (h * h)).toFixed(1); }, [profile.heightCm, profile.weightKg]);

const bmiCategory = useMemo(() => { if (bmi < 18.5) return "Abaixo do peso"; if (bmi < 25) return "Normal"; if (bmi < 30) return "Sobrepeso"; return "Obesidade"; }, [bmi]);

const progressData = activities.map((a, i) => ({ name: a.date.slice(5), calories: a.calories, steps: Math.min(12000, (a.durationMin * 100) + (i * 200)), }));

// Quick actions function logWater(amountMl) { setWater((w) => ({ ...W, drankMl: Math.min(w.goalMl, w.drankMl + amountMl) })); }

function addActivity(activity) { setActivities((arr) => [activity, ...Arr].slice(0, 20)); }

return ( <div className="min-h-screen bg-gray-50 p-6 font-sans"> <header className="max-w-4xl mx-auto"> <div className="flex items-center justify-between"> <div> <h1 className="text-2xl font-bold">Saúde Corporal</h1> <p className="text-sm text-gray-600">Resumo diário e controle de métricas</p> </div> <div className="flex items-center gap-4"> <div className="text-right"> <div className="text-sm text-gray-500">Olá,</div> <div className="font-medium">{profile.name}</div> </div> <div className="w-12 h-12 rounded-full bg-gradient-to-br from-indigo-500 to-purple-500 flex items-center justify-center text-white">{profile.name[0]}</div> </div> </div> </header>

<main className="max-w-4xl mx-auto mt-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
    {/* Left column - profile & goals */}
    <section className="lg:col-span-1 space-y-4">
      <div className="p-4 bg-white rounded-2xl shadow-sm">
        <h2 className="font-semibold">Perfil</h2>
        <div className="mt-3 text-sm text-gray-700 space-y-2">
          <div>Idade: {profile.age} anos</div>
          <div>Altura: {profile.heightCm} cm</div>
          <div>Peso: {profile.weightKg} kg</div>
          <div>BMI: <span className="font-medium">{bmi} ({bmiCategory})</span></div>
        </div>
        <div className="mt-4 flex gap-2">
          <button className="px-3 py-2 bg-indigo-600 text-white rounded-xl text-sm" onClick={() => setProfile({...Profile, weightKg: Math.max(40, profile.weightKg - 1)})}>-1 kg</button>
          <button className="px-3 py-2 bg-green-600 text-white rounded-xl text-sm" onClick={() => setProfile({...Profile, weightKg: profile.weightKg + 1})}>+1 kg</button>
        </div>
      </div>

      <div className="p-4 bg-white rounded-2xl shadow-sm">
        <h3 className="font-semibold">Hidratação</h3>
        <div className="mt-3">
          <div className="text-sm text-gray-600">{water.drankMl} / {water.goalMl} ml</div>
          <div className="w-full bg-gray-200 rounded-full h-3 mt-2 overflow-hidden">
            <div style={{ width: `${(water.drankMl / water.goalMl) * 100}%` }} className="h-3 bg-gradient-to-r from-blue-400 to-cyan-400" />
          </div>
          <div className="mt-3 flex gap-2">
            <button className="px-2 py-1 bg-blue-50 rounded-md text-sm" onClick={() => logWater(200)}>+200 ml</button>
            <button className="px-2 py-1 bg-blue-50 rounded-md text-sm" onClick={() => logWater(500)}>+500 ml</button>
            <button className="px-2 py-1 bg-red-50 rounded-md text-sm" onClick={() => setWater({...Water, drankMl: 0})}>Reset</button>
          </div>
        </div>
      </div>

      <div className="p-4 bg-white rounded-2xl shadow-sm">
        <h3 className="font-semibold">Metas diárias</h3>
        <div className="mt-3 space-y-2 text-sm text-gray-700">
          <div>Passos: {steps.current} / {steps.goal}</div>
          <div>Consumo calórico estimado: 2000 kcal</div>
        </div>
        <div className="mt-3">
          <button className="px-3 py-2 bg-indigo-600 text-white rounded-xl text-sm" onClick={() => setSteps({...Steps, current: Math.min(steps.goal, steps.current + 1000)})}>+1000 passos</button>
        </div>
      </div>
    </section>

    {/* Middle column - main dashboard */}
    <section className="lg:col-span-2 space-y-6">
      <div className="bg-white rounded-2xl p-4 shadow-sm">
        <div className="flex items-center justify-between">
          <h2 className="font-semibold">Resumo da semana</h2>
          <div className="text-sm text-gray-500">Últimos 7 dias</div>
        </div>

        <div className="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
          <StatCard title="Calorias queimadas" value={`${activities.reduce((s, a) => s + a.calories, 0)} kcal`} />
          <StatCard title="Atividades" value={`${activities.length}`} />
          <StatCard title="Média de passos" value={`${Math.round(activities.reduce((s, a)=> s + (a.durationMin*120),0)/activities.length)}`} />
        </div>

        <div className="mt-6 h-48">
          <ResponsiveContainer width="100%" height="100%">
            <LineChart data={progressData} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}>
              <CartesianGrid strokeDasharray="3 3" />
              <XAxis dataKey="name" />
              <YAxis />
              <Tooltip />
              <Line type="monotone" dataKey="calories" stroke="#8884d8" strokeWidth={2} />
            </LineChart>
          </ResponsiveContainer>
        </div>
      </div>

      <div className="bg-white rounded-2xl p-4 shadow-sm">
        <h3 className="font-semibold">Registro de atividades</h3>
        <div className="mt-3 space-y-3">
          {activities.map((a) => (
            <div key={a.id} className="flex items-center justify-between p-3 rounded-lg bg-gray-50">
              <div>
                <div className="font-medium">{a.type}</div>
                <div className="text-sm text-gray-500">{a.date} • {a.durationMin} min • {a.calories} kcal</div>
              </div>
              <div className="text-sm text-gray-600">{a.durationMin}m</div>
            </div>
          ))}
        </div>

        <div className="mt-4 flex gap-2">
          <button className="px-3 py-2 bg-green-600 text-white rounded-xl text-sm" onClick={() => addActivity({ id: Date.now(), date: new Date().toISOString().slice(0,10), type: "Caminhada", durationMin: 30, calories: 190 })}>Adicionar caminhada 30m</button>
          <button className="px-3 py-2 bg-yellow-500 text-white rounded-xl text-sm" onClick={() => addActivity({ id: Date.now()+1, date: new Date().toISOString().slice(0,10), type: "Corrida", durationMin: 20, calories: 260 })}>Adicionar corrida 20m</button>
        </div>
      </div>

      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
        <div className="bg-white rounded-2xl p-4 shadow-sm">
          <h4 className="font-semibold">Dicas rápidas</h4>
          <ul className="mt-3 list-disc list-inside text-sm text-gray-700">
            <li>Beba água regularmente — meta pessoal: {water.goalMl} ml/dia.</Li>
            <li>Mantenha atividade física: 150 min de intensidade moderada/semana.</Li>
            <li>Durma 7–9 horas por noite.</Li>
          </ul>
        </div>

        <div className="bg-white rounded-2xl p-4 shadow-sm">
          <h4 className="font-semibold">Lembretes</h4>
          <div className="mt-3 text-sm text-gray-700">Sem lembretes ativos. (Aqui você pode integrar notificações)</div>
        </div>
      </div>
    </section>
  </main>

  <footer className="max-w-4xl mx-auto mt-6 text-center text-sm text-gray-500">Protótipo • Personalize e conecte ao backend para produção</footer>
</div>

); }

function StatCard({ title, value }) { return ( <div className="p-3 bg-gray-50 rounded-xl text-center"> <div className="text-xs text-gray-500">{title}</div> <div className="mt-2 font-semibold text-lg">{value}</div> </div> ); }

Contexto general del proyecto

/* HealthApp.jsx Single-file React prototype (Tailwind + recharts) Features included in this prototype: Onboarding header + quick stats Body metrics (weight, height) and BMI calculator with suggestions Daily targets (water, steps, calories) Activity log and progress chart (Recharts) Simple reminders placeholder and profile card How to run: 1. Create a React app (Vite or CRA) 2. Install dependencies: react, react-dom, tailwindcss, recharts npm i react react-dom recharts 3. Ensure Tailwind is configured in the project 4. Save this file as src/HealthApp.jsx and import it from src/main.jsx Note: This prototype uses local component state and dummy data. For production add backend (GraphQL/REST), auth, data persistence, and notifications. */ Import React, { useState, useMemo } from "react"; import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from "recharts"; export default function HealthApp() { // Sample user profile & metrics const [profile, setProfile] = useState({ name: "Usuário", age: 29, heightCm: 178, weightKg: 78 }); const [water, setWater] = useState({ goalMl: 3000, drankMl: 1600 }); const [steps, setSteps] = useState({ goal: 10000, current: 6240 }); const [activities, setActivities] = useState([ { id: 1, date: "2025-10-28", type: "Caminhada", durationMin: 40, calories: 230 }, { id: 2, date: "2025-10-29", type: "Treino de Força", durationMin: 50, calories: 350 }, { id: 3, date: "2025-10-30", type: "Corrida", durationMin: 25, calories: 300 }, { id: 4, date: "2025-10-31", type: "Yoga", durationMin: 30, calories: 120 }, { id: 5, date: "2025-11-01", type: "Caminhada", durationMin: 60, calories: 360 }, ]); const bmi = useMemo(() => { const h = profile.heightCm / 100; return +(profile.weightKg / (h * h)).toFixed(1); }, [profile.heightCm, profile.weightKg]); const bmiCategory = useMemo(() => { if (bmi < 18.5) return "Abaixo do peso"; if (bmi < 25) return "Normal"; if (bmi < 30) return "Sobrepeso"; return "Obesidade"; }, [bmi]); const progressData = activities.map((a, i) => ({ name: a.date.slice(5), calories: a.calories, steps: Math.min(12000, (a.durationMin * 100) + (i * 200)), })); // Quick actions function logWater(amountMl) { setWater((w) => ({ ...W, drankMl: Math.min(w.goalMl, w.drankMl + amountMl) })); } function addActivity(activity) { setActivities((arr) => [activity, ...Arr].slice(0, 20)); } return ( <div className="min-h-screen bg-gray-50 p-6 font-sans"> <header className="max-w-4xl mx-auto"> <div className="flex items-center justify-between"> <div> <h1 className="text-2xl font-bold">Saúde Corporal</h1> <p className="text-sm text-gray-600">Resumo diário e controle de métricas</p> </div> <div className="flex items-center gap-4"> <div className="text-right"> <div className="text-sm text-gray-500">Olá,</div> <div className="font-medium">{profile.name}</div> </div> <div className="w-12 h-12 rounded-full bg-gradient-to-br from-indigo-500 to-purple-500 flex items-center justify-center text-white">{profile.name[0]}</div> </div> </div> </header> <main className="max-w-4xl mx-auto mt-6 grid grid-cols-1 lg:grid-cols-3 gap-6"> {/* Left column - profile & goals */} <section className="lg:col-span-1 space-y-4"> <div className="p-4 bg-white rounded-2xl shadow-sm"> <h2 className="font-semibold">Perfil</h2> <div className="mt-3 text-sm text-gray-700 space-y-2"> <div>Idade: {profile.age} anos</div> <div>Altura: {profile.heightCm} cm</div> <div>Peso: {profile.weightKg} kg</div> <div>BMI: <span className="font-medium">{bmi} ({bmiCategory})</span></div> </div> <div className="mt-4 flex gap-2"> <button className="px-3 py-2 bg-indigo-600 text-white rounded-xl text-sm" onClick={() => setProfile({...Profile, weightKg: Math.max(40, profile.weightKg - 1)})}>-1 kg</button> <button className="px-3 py-2 bg-green-600 text-white rounded-xl text-sm" onClick={() => setProfile({...Profile, weightKg: profile.weightKg + 1})}>+1 kg</button> </div> </div> <div className="p-4 bg-white rounded-2xl shadow-sm"> <h3 className="font-semibold">Hidratação</h3> <div className="mt-3"> <div className="text-sm text-gray-600">{water.drankMl} / {water.goalMl} ml</div> <div className="w-full bg-gray-200 rounded-full h-3 mt-2 overflow-hidden"> <div style={{ width: `${(water.drankMl / water.goalMl) * 100}%` }} className="h-3 bg-gradient-to-r from-blue-400 to-cyan-400" /> </div> <div className="mt-3 flex gap-2"> <button className="px-2 py-1 bg-blue-50 rounded-md text-sm" onClick={() => logWater(200)}>+200 ml</button> <button className="px-2 py-1 bg-blue-50 rounded-md text-sm" onClick={() => logWater(500)}>+500 ml</button> <button className="px-2 py-1 bg-red-50 rounded-md text-sm" onClick={() => setWater({...Water, drankMl: 0})}>Reset</button> </div> </div> </div> <div className="p-4 bg-white rounded-2xl shadow-sm"> <h3 className="font-semibold">Metas diárias</h3> <div className="mt-3 space-y-2 text-sm text-gray-700"> <div>Passos: {steps.current} / {steps.goal}</div> <div>Consumo calórico estimado: 2000 kcal</div> </div> <div className="mt-3"> <button className="px-3 py-2 bg-indigo-600 text-white rounded-xl text-sm" onClick={() => setSteps({...Steps, current: Math.min(steps.goal, steps.current + 1000)})}>+1000 passos</button> </div> </div> </section> {/* Middle column - main dashboard */} <section className="lg:col-span-2 space-y-6"> <div className="bg-white rounded-2xl p-4 shadow-sm"> <div className="flex items-center justify-between"> <h2 className="font-semibold">Resumo da semana</h2> <div className="text-sm text-gray-500">Últimos 7 dias</div> </div> <div className="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4"> <StatCard title="Calorias queimadas" value={`${activities.reduce((s, a) => s + a.calories, 0)} kcal`} /> <StatCard title="Atividades" value={`${activities.length}`} /> <StatCard title="Média de passos" value={`${Math.round(activities.reduce((s, a)=> s + (a.durationMin*120),0)/activities.length)}`} /> </div> <div className="mt-6 h-48"> <ResponsiveContainer width="100%" height="100%"> <LineChart data={progressData} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}> <CartesianGrid strokeDasharray="3 3" /> <XAxis dataKey="name" /> <YAxis /> <Tooltip /> <Line type="monotone" dataKey="calories" stroke="#8884d8" strokeWidth={2} /> </LineChart> </ResponsiveContainer> </div> </div> <div className="bg-white rounded-2xl p-4 shadow-sm"> <h3 className="font-semibold">Registro de atividades</h3> <div className="mt-3 space-y-3"> {activities.map((a) => ( <div key={a.id} className="flex items-center justify-between p-3 rounded-lg bg-gray-50"> <div> <div className="font-medium">{a.type}</div> <div className="text-sm text-gray-500">{a.date} • {a.durationMin} min • {a.calories} kcal</div> </div> <div className="text-sm text-gray-600">{a.durationMin}m</div> </div> ))} </div> <div className="mt-4 flex gap-2"> <button className="px-3 py-2 bg-green-600 text-white rounded-xl text-sm" onClick={() => addActivity({ id: Date.now(), date: new Date().toISOString().slice(0,10), type: "Caminhada", durationMin: 30, calories: 190 })}>Adicionar caminhada 30m</button> <button className="px-3 py-2 bg-yellow-500 text-white rounded-xl text-sm" onClick={() => addActivity({ id: Date.now()+1, date: new Date().toISOString().slice(0,10), type: "Corrida", durationMin: 20, calories: 260 })}>Adicionar corrida 20m</button> </div> </div> <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="bg-white rounded-2xl p-4 shadow-sm"> <h4 className="font-semibold">Dicas rápidas</h4> <ul className="mt-3 list-disc list-inside text-sm text-gray-700"> <li>Beba água regularmente — meta pessoal: {water.goalMl} ml/dia.</Li> <li>Mantenha atividade física: 150 min de intensidade moderada/semana.</Li> <li>Durma 7–9 horas por noite.</Li> </ul> </div> <div className="bg-white rounded-2xl p-4 shadow-sm"> <h4 className="font-semibold">Lembretes</h4> <div className="mt-3 text-sm text-gray-700">Sem lembretes ativos. (Aqui você pode integrar notificações)</div> </div> </div> </section> </main> <footer className="max-w-4xl mx-auto mt-6 text-center text-sm text-gray-500">Protótipo • Personalize e conecte ao backend para produção</footer> </div> ); } function StatCard({ title, value }) { return ( <div className="p-3 bg-gray-50 rounded-xl text-center"> <div className="text-xs text-gray-500">{title}</div> <div className="mt-2 font-semibold text-lg">{value}</div> </div> ); }

Categoría Programación y Tecnología
Subcategoría Programación Web
¿Cuál es el alcance del proyecto? Cambio mediano
¿Es un proyecto o una posición? Un proyecto
Actualmente tengo Tengo las especificaciones
Disponibilidad requerida Según se necesite
Integraciones de API Social media (Facebook, Twitter, etc.)
Roles necesarios Programador

Plazo de Entrega: No definido

Habilidades necesarias