Add boto3 dependency and update exercise/machine assets

- Added boto3 as a dependency in pyproject.toml and uv.lock.
- Introduced multiple new exercise images in various formats (jpg, webp, avif, png).
- Added new machine images to enhance the workout assets library.
This commit is contained in:
Artem Kashaev
2026-05-29 15:50:33 +05:00
parent 7b34ce1a98
commit 800dee31b2
120 changed files with 1151 additions and 167 deletions
@@ -1,8 +1,17 @@
import { useMemo, useState } from "react";
import type { CatalogEntity, CatalogKind, Workout } from "../../types";
import type { ActivityCategory, CatalogEntity, CatalogKind, Workout } from "../../types";
import { categoryLabels, measurementLabels } from "../catalog/meta";
import { useCatalog } from "../catalog/hooks";
const categoryTabs: Array<ActivityCategory | "all" | "arms"> = ["all", "chest", "back", "legs", "shoulders", "arms", "core", "cardio"];
function categoryTabLabel(category: ActivityCategory | "all" | "arms") {
if (category === "all") return "Все";
if (category === "arms") return "Руки";
return categoryLabels[category];
}
export function AddExerciseDrawer({
open,
token,
@@ -20,11 +29,13 @@ export function AddExerciseDrawer({
}) {
const [kind, setKind] = useState<CatalogKind>("exercise");
const [search, setSearch] = useState("");
const { exercises, equipment } = useCatalog(token);
const [category, setCategory] = useState<ActivityCategory | "all" | "arms">("all");
const { exercises, machines } = useCatalog(token);
const addedIds = useMemo(() => {
const ids = new Set<string>();
workout.items.forEach((item) => {
if (item.activity_source_id) ids.add(item.activity_source_id);
if (item.exercise_id) ids.add(item.exercise_id);
if (item.equipment_id) ids.add(item.equipment_id);
});
@@ -32,10 +43,15 @@ export function AddExerciseDrawer({
}, [workout.items]);
const list = useMemo(() => {
const source = kind === "exercise" ? exercises.data ?? [] : equipment.data ?? [];
const source = kind === "exercise" ? exercises.data ?? [] : machines.data ?? [];
const needle = search.trim().toLowerCase();
return needle ? source.filter((entity) => entity.name.toLowerCase().includes(needle)) : source;
}, [kind, search, exercises.data, equipment.data]);
return source.filter((entity) => {
if (category === "arms" && entity.category !== "biceps" && entity.category !== "triceps") return false;
if (category !== "all" && category !== "arms" && entity.category !== category) return false;
if (!needle) return true;
return `${entity.title} ${entity.description ?? ""}`.toLowerCase().includes(needle);
});
}, [category, kind, search, exercises.data, machines.data]);
if (!open) return null;
@@ -53,7 +69,12 @@ export function AddExerciseDrawer({
<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>
<button className={kind === "machine" ? "active" : ""} onClick={() => setKind("machine")}>Тренажеры</button>
</div>
<div className="chip-row drawer-categories">
{categoryTabs.map((tab) => (
<button key={tab} className={category === tab ? "active" : ""} onClick={() => setCategory(tab)}>{categoryTabLabel(tab)}</button>
))}
</div>
<div className="drawer-list">
@@ -61,10 +82,10 @@ export function AddExerciseDrawer({
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>}
{entity.image_s3_url ? <img src={entity.image_s3_url} alt="" /> : <span className="drawer-placeholder">{kind === "exercise" ? "EX" : "MC"}</span>}
<div>
<h3>{entity.name}</h3>
<p>{entity.description || "Без описания"}</p>
<h3>{entity.title}</h3>
<p>{categoryLabels[entity.category]} · {measurementLabels[entity.measurement_type]}</p>
{added && <b>В тренировке</b>}
</div>
<button