Skip to content

Commit

Permalink
feat(progress): add tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
vre2h committed Feb 14, 2022
1 parent 24acfdb commit 6969f46
Show file tree
Hide file tree
Showing 15 changed files with 1,222 additions and 15 deletions.
12 changes: 1 addition & 11 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@
"no-undef": "error",
"no-unused-vars": "error",
"import/no-unresolved": "error",
"no-console": "error",
"sort-imports": [
"error",
{
"ignoreCase": false,
"ignoreDeclarationSort": false,
"ignoreMemberSort": false,
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
"allowSeparatedGroups": true
}
]
"no-console": "error"
}
}
194 changes: 194 additions & 0 deletions components/progress/Card.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { useMemo, useState } from "react";
import { CustomDate } from "../../services/progress/date.services";

const statuses = {
idle: "idle",
editing: "editing",
};

const AddMealForm = ({ onSave }) => {
const [meal, setMeal] = useState({});

const handleChange = (evt) => {
setMeal((m) => ({
...m,
[evt.target.name]: evt.target.value,
}));
};

return (
<div className="mt-2">
<input
onChange={handleChange}
className="border p-1 mr-2 mb-1"
placeholder="food"
name="food"
value={meal["food"]}
/>
<input
onChange={handleChange}
className="border p-1 mr-2 mb-1 w-20"
placeholder="calories"
name="calories"
value={meal["calories"]}
/>
<input
onChange={handleChange}
className="border p-1 mr-2 mb-1"
placeholder="date"
name="date"
value={meal["date"]}
/>
<span
onClick={() => onSave(meal)}
className="cursor-pointer hover:underline"
>
Save
</span>
</div>
);
};

export default function Card(props) {
const { onSave, ...defaultEvent } = props;
const [status, setStatus] = useState(statuses.idle);
const [event, setEvent] = useState(defaultEvent);
const toggleStatus = () =>
setStatus(status === statuses.editing ? statuses.idle : statuses.editing);

const handleChange = (e) => {
const { name, value, type, checked } = e.target;

if (type === "checkbox") {
setEvent((e) => ({
...e,
[name]: checked,
}));
return;
}

setEvent((e) => ({
...e,
[name]: value,
}));
};

const handleSave = () => {
onSave(event);
toggleStatus();
};

const handleSaveMeal = (meal) => {
setEvent((e) => ({
...e,
meals: e.meals.concat(meal),
}));
onSave(event);
};

const handleDeleteMeal = (idx) => () => {
setEvent((e) => ({
...e,
meals: e.meals.filter((_, iIdx) => iIdx !== idx),
}));
onSave(event);
};

const isEditing = status === statuses.editing;

const totalCalories = useMemo(() => {
return event.meals.reduce((acc, meal) => acc + Number(meal.calories), 0);
}, []);

return (
<div className="bg-white p-6 rounded-lg shadow-lg">
<div className="flex">
{isEditing ? (
<input
placeholder="Date"
className="border p-1"
value={event.date}
onChange={handleChange}
name="date"
/>
) : (
<span className="font-semibold" name="date">
{CustomDate.getDate(event.date)}
</span>
)}
{isEditing ? (
<div className="ml-auto">
<span
onClick={handleSave}
className="hover:underline cursor-pointer"
>
Save
</span>
</div>
) : (
<span onClick={toggleStatus} className="ml-auto cursor-pointer">
✏️
</span>
)}
</div>

<ul className="mt-1 p-l-4">
{event.meals.map(({ date, calories, food }, idx) => (
<li key={date}>
<span>{food}</span>
<span name="calories" className="text-gray-600 text-sm">
{calories} kcal
</span>{" "}
/ <span className="text-gray-500 text-sm">{date}</span>{" "}
{isEditing && (
<span onClick={handleDeleteMeal(idx)} className="cursor-pointer">
🪓
</span>
)}
</li>
))}
{isEditing && <AddMealForm onSave={handleSaveMeal} />}
</ul>

<div className="flex flex-wrap items-center mt-4">
{isEditing ? (
<input
placeholder="Weight"
className="border p-1"
name="weight"
onChange={handleChange}
value={event.weight}
/>
) : (
<span className="bg-blue-200 text-blue-800 text-xs px-2 inline-block rounded-full mr-1 uppercase font-semibold tracking-wide">
{event.weight} kg
</span>
)}
{!isEditing && (
<span className="bg-blue-100 text-blue-600 text-xs px-2 inline-block rounded-full mr-1 uppercase font-semibold tracking-wide">
{event.meals.length} meals / {totalCalories} kcal
</span>
)}
{!isEditing ? (
event.workout && (
<span className="bg-red-200 text-red-800 text-xs px-2 inline-block rounded-full mr-1 uppercase font-semibold tracking-wide">
Workout
</span>
)
) : (
<label className="ml-auto">
<input
className="border p-1"
onChange={handleChange}
value={!event.workout}
type="checkbox"
name="workout"
defaultChecked={event.workout}
/>
<span className="pl-2">Workout</span>
</label>
)}
</div>
</div>
);
}
81 changes: 81 additions & 0 deletions components/progress/Dashboard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { useEffect, useState } from "react";

import { addEvent, getEvents } from "../../services/progress/events.services";

import { useAuthentication } from "../../hooks/useAuthentication.hooks";
import Card from "./Card";

export default function Dashboard() {
const { user } = useAuthentication();
const [events, setEvents] = useState({
data: [],
loading: true,
error: "",
});

useEffect(() => {
getEvents()
.then((e) => {
setEvents((events) => ({
...events,
loading: false,
data: e,
}));
})
.catch((e) => {
setEvents((events) => ({
...events,
loading: false,
e: e.message,
}));
});
}, []);

const saveEvent = (event) => {
addEvent(event);
};

if (events.loading) {
return <div>Loading...</div>;
}

return (
<div className="m-4">
<header className="my-4 flex justify-between items-center">
<h1 className="text-xl font-bold">Dashboard</h1>
<p>Ahoy, {user.email}</p>
</header>

<div className="mb-4">
<h2>Today</h2>
<Card
onSave={saveEvent}
{...{
meals: [],
date: new Date().toString(),
workout: false,
weight: "",
}}
/>
</div>

<hr className="mb-8" />

<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{events.data.map((event) => {
return (
<div className="" key={event.date}>
<Card
onSave={saveEvent}
meals={event.meals}
weight={event.weight}
date={event.date}
workout={event.workout}
/>
</div>
);
})}
</div>
</div>
);
}
69 changes: 69 additions & 0 deletions components/progress/Login.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useState } from "react";
import { useAuthentication } from "../../hooks/useAuthentication.hooks";

export default function Login() {
const [email, setEmail] = useState("");
const [pass, setPass] = useState("");
const [loading, setLoading] = useState(false);
const { signin } = useAuthentication();

const handleSubmit = async () => {
setLoading(true);

await signin(email, pass)
.then(() => {
setPass("");
setEmail("");
})
.catch((e) => {
alert(e.message);
})
.finally(() => {
setLoading(false);
});
};

if (loading) {
return <div>Loading...</div>;
}

return (
<div className="w-full h-screen mx-auto flex flex-col justify-center items-center">
<h2 className="text-2xl font-medium mb-2">Login:</h2>

<div className="flex flex-col w-full p-4 sm:w-3/4 md:w-1/3 md:p-0">
<input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="border p-1 w-full"
/>
<input
label="Password"
type="password"
value={pass}
style={{ margin: "10px 0" }}
onChange={(e) => setPass(e.target.value)}
className="border p-1 w-full"
/>
<div
style={{
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<button
className="flex border items-center text-sm my-4 mx-auto px-4 py-2 rounded-md font-medium text-gray-900 dark:text-gray-100 hover:bg-gray-200"
type="submit"
onClick={handleSubmit}
>
Log In
</button>
</div>
</div>
</div>
);
}
3 changes: 3 additions & 0 deletions constants/progress/documents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const Documents = {
events: "events",
};
3 changes: 3 additions & 0 deletions constants/progress/operators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const Operators = {
less: "<",
};
4 changes: 4 additions & 0 deletions constants/progress/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const Routes = {
login: () => "/login",
dashboard: () => "/",
};
Loading

0 comments on commit 6969f46

Please sign in to comment.