feat: Implement active workout flow with status management
- Added `status`, `total_sets`, and `total_volume` fields to the Workout model. - Introduced `source_kind`, `title_snapshot`, and `image_s3_url_snapshot` fields to the WorkoutItem model. - Created endpoints for managing active workouts, including finishing and discarding workouts. - Updated workout creation to ensure only one active workout exists per user. - Implemented batch addition of workout sets and updates to workout set details. - Enhanced database schema with Alembic migrations to support new fields and constraints. - Added validation to ensure at least one field is provided for workout set updates. - Updated calorie estimation logic to reflect new workout set structure.
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type { CatalogEntity, CatalogKind, Workout } from "../../types";
|
||||
import { useCatalog } from "../catalog/hooks";
|
||||
|
||||
export function AddExerciseDrawer({
|
||||
open,
|
||||
token,
|
||||
workout,
|
||||
onClose,
|
||||
onAdd,
|
||||
pending,
|
||||
}: {
|
||||
open: boolean;
|
||||
token: string;
|
||||
workout: Workout;
|
||||
onClose: () => void;
|
||||
onAdd: (entity: CatalogEntity, kind: CatalogKind, closeAfter: boolean) => void;
|
||||
pending?: boolean;
|
||||
}) {
|
||||
const [kind, setKind] = useState<CatalogKind>("exercise");
|
||||
const [search, setSearch] = useState("");
|
||||
const { exercises, equipment } = useCatalog(token);
|
||||
|
||||
const addedIds = useMemo(() => {
|
||||
const ids = new Set<string>();
|
||||
workout.items.forEach((item) => {
|
||||
if (item.exercise_id) ids.add(item.exercise_id);
|
||||
if (item.equipment_id) ids.add(item.equipment_id);
|
||||
});
|
||||
return ids;
|
||||
}, [workout.items]);
|
||||
|
||||
const list = useMemo(() => {
|
||||
const source = kind === "exercise" ? exercises.data ?? [] : equipment.data ?? [];
|
||||
const needle = search.trim().toLowerCase();
|
||||
return needle ? source.filter((entity) => entity.name.toLowerCase().includes(needle)) : source;
|
||||
}, [kind, search, exercises.data, equipment.data]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div className="drawer-scrim" onClick={onClose}>
|
||||
<aside className="add-drawer" aria-label="Добавить в тренировку" onClick={(event) => event.stopPropagation()}>
|
||||
<header>
|
||||
<div>
|
||||
<p className="eyebrow">Catalog drawer</p>
|
||||
<h2>Добавить в тренировку</h2>
|
||||
</div>
|
||||
<button className="round-close" onClick={onClose} aria-label="Закрыть">×</button>
|
||||
</header>
|
||||
|
||||
<input className="drawer-search" placeholder="Поиск упражнения или тренажера" value={search} onChange={(event) => setSearch(event.target.value)} />
|
||||
<div className="segmented drawer-tabs">
|
||||
<button className={kind === "exercise" ? "active" : ""} onClick={() => setKind("exercise")}>Упражнения</button>
|
||||
<button className={kind === "equipment" ? "active" : ""} onClick={() => setKind("equipment")}>Тренажеры</button>
|
||||
</div>
|
||||
|
||||
<div className="drawer-list">
|
||||
{list.map((entity) => {
|
||||
const added = addedIds.has(entity.id);
|
||||
return (
|
||||
<article className={`drawer-pick ${added ? "already-added" : ""}`} key={`${kind}-${entity.id}`} onClick={() => onAdd(entity, kind, true)}>
|
||||
{entity.image_s3_url ? <img src={entity.image_s3_url} alt="" /> : <span className="drawer-placeholder">{kind === "exercise" ? "EX" : "EQ"}</span>}
|
||||
<div>
|
||||
<h3>{entity.name}</h3>
|
||||
<p>{entity.description || "Без описания"}</p>
|
||||
{added && <b>В тренировке</b>}
|
||||
</div>
|
||||
<button
|
||||
className="plus-chip"
|
||||
disabled={pending}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onAdd(entity, kind, false);
|
||||
}}
|
||||
aria-label="Добавить и оставить drawer открытым"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
{list.length === 0 && <p className="muted">Ничего не найдено.</p>}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user