포트폴리오 ↗
개인 프로젝트›웹 앱1 / 4

PISTE운동 스케줄 · 세트 기록

·읽는 시간 약 9분
종류웹 앱
스택
fastapireacttypescriptvitepostgresqldockerweb-app
GitHubMINI_Piste ↗
목차 6개 단원
  1. ·서론
  2. 01왜 만들었나
  3. 02구조 및 아키텍처
  4. 03핵심 구현 디테일
  5. 04화면으로 보는 기능
  6. 05한계 · 설치 · 사용 흐름

서론

운동을 다시 시작하면서 플랜은 마크다운 문서로 짜 두었는데, 헬스장에서 폰으로 그 문서를 열어 "오늘 뭐 하지, 몇 kg 들지"를 찾는 게 생각보다 번거로웠습니다. 지난번 무게는 메모 앱에, 체중은 또 다른 앱에 흩어져 있었고, "3세트 모두 12회를 채우면 다음엔 한 단계 올린다" 같은 규칙도 매번 머릿속으로 계산해야 했습니다.

PISTE는 FastAPI + React + Vite 기반의 개인용 운동 스케줄러입니다. 요일별 플랜에 맞춰 오늘 할 운동과 종목별 추천 무게를 보여 주고, 세트를 기록하면 다음 무게를 정해 줍니다. PC에서 Docker Compose로 띄워 두고 같은 와이파이의 휴대폰으로 헬스장에서 씁니다. 이름은 펜싱 경기대 피스트(piste)에서 따 왔고, 완료 · 빠짐 색도 펜싱 득점등의 초록 · 빨강을 그대로 썼습니다.

PISTE 홈 (라이트 테마) — 오늘 운동, 이번 주, 다음 운동, 체중 추세, 전체 일정

1. 왜 만들었나

  • 헬스장에서 바로: 폰을 켜면 오늘 할 종목과 들어야 할 무게가 바로 보여야 했습니다. 세트 사이에는 휴식 시간도 재야 합니다.
  • 무게 계산은 앱이: 이중 점진법(정해진 횟수 범위를 모든 세트가 채우면 증량)과 처음 하는 종목의 시작 무게 찾기를 규칙대로 계산해 주길 바랐습니다.
  • 플랜은 자주 바뀐다: 일주일 사이에 루틴 구성을 여러 번 바꿨습니다. 그때마다 코드를 고치지 않고 화면에서 요일 · 루틴 · 종목 · 단계 기간을 바꿀 수 있어야 했습니다.
  • 집 안에서만: 혼자 쓰는 기록이라 로그인 · 클라우드 없이 PC 한 대에 두고, 같은 와이파이에서만 엽니다.

2. 구조 및 아키텍처

백엔드는 FastAPI + SQLAlchemy 2 + Alembic, DB는 Docker에서는 PostgreSQL, 개발 · 테스트에서는 SQLite입니다. 프론트는 React + TypeScript + Vite + TanStack Query이고, 백엔드의 OpenAPI 스키마에서 타입을 자동으로 만들어 API 모양을 손으로 맞추지 않습니다. nginx가 빌드된 화면을 내보내고 /api만 백엔드로 넘깁니다.

text
MINI_Piste/
┣━━ 📂 backend/
┃   ┣━━ 📂 app/
┃   ┃   ┣━━ 📂 api/             # 라우터 (오늘 · 플랜 · 기록 · 측정 · 통계)
┃   ┃   ┣━━ 📂 services/        # 무게 추천 · 날짜별 계획 · 달력 집계 · 통계
┃   ┃   ┣━━ models.py           # SQLAlchemy 모델
┃   ┃   ┣━━ schemas.py          # Pydantic 입출력 (OpenAPI 의 원본)
┃   ┃   ┗━━ seed.py             # 처음 설치용 기본 플랜 · 종목 · 루틴
┃   ┣━━ 📂 alembic/             # DB 마이그레이션
┃   ┗━━ 📂 tests/               # pytest
┣━━ 📂 frontend/
┃   ┣━━ 📂 e2e/                 # Playwright (데스크톱 · 모바일)
┃   ┗━━ 📂 src/
┃       ┣━━ 📂 api/             # fetch 클라이언트 · Query 훅 · OpenAPI 타입 (자동 생성)
┃       ┣━━ 📂 pages/           # 홈 · 운동 · 운동 모드 · 달력 · 체중 · 분석 · 플랜 · 설정
┃       ┣━━ 📂 components/      # 운동 카드 · 선택 상자 · 날짜 선택기 · 그래프 · 일정 로드맵
┃       ┗━━ 📂 styles/          # 디자인 토큰 · 공통 부품 (화면별은 *.module.css)
┗━━ 📄 docker-compose.yml       # db + api + web (nginx)

플랜과 기록은 이렇게 나뉩니다.

text
phases (단계: 이름 · 기간)
┗━━ phase_days (요일 7개: 근력 / 유산소 / 휴식, 루틴, 격주 루틴, 유산소 시간)
routines ── routine_items (종목 · 세트 · 횟수 범위 또는 시간 · 순서)
exercises ── exercise_muscles (근육 13부위, 주동 / 보조)
workouts (하루 1개) ── set_logs (무게 · 횟수 또는 시간 · 남은 개수 · 좌우)
daily_logs (체중 · 걸음 · 수면 · 못 함 표시)   body_measurements (월말 둘레 · 체지방)

오늘 화면 하나가 그려지는 흐름은 대략 다음과 같습니다.

text
GET /api/today?date= ─▶ planner: 날짜가 속한 단계 → 요일 계획 → (격주면) 루틴 고르기
                     ─▶ progression: 종목마다 가장 최근 세션의 세트로 다음 무게 계산
                     ─▶ 그날 세션 · 일일 기록
React ─ TanStack Query ─▶ 세트 기록(POST) ─▶ 관련 화면(오늘 · 달력 · 홈 · 통계) 캐시만 다시 불러오기

3. 핵심 구현 디테일

① 다음 무게 추천 (이중 점진법)

종목마다 가장 최근에 한 세션의 세트를 보고 다음 무게를 정합니다.

지난 기록 다음 무게
기록 없음 추천 없음 → 무게 찾기부터
마지막이 무게 찾기 세트 찾은 무게로 시작
계획 세트 수 이상 · 모두 목표 상한 이상 증량 단위만큼 올림 (예: 레그프레스 +10kg)
한 세트라도 하한 − 2 미만 한 단계 내림 (자세가 무너지는 신호)
그 외 같은 무게 유지

한쪽씩 하는 종목(싱글 레그프레스 등)은 왼쪽 · 오른쪽을 따로 계산해 약한 쪽 기준으로 정합니다. 증량 단위 · 휴식 시간은 종목마다 다르게 둘 수 있습니다.

② 시작 무게 찾기

처음 하는 종목은 '가볍겠다' 싶은 무게로 12회를 하고 "몇 개 더 할 수 있었나"만 고르면 다음 세트 무게가 나옵니다. 5개 이상이면 +2단계, 4개면 +1단계, 2~3개면 확정, 12회를 못 채우면 −1단계입니다. 선택지 문구는 백엔드가 내려 주어 화면과 서버의 규칙이 어긋나지 않습니다.

③ 날짜 → 그날 계획

날짜가 속한 단계를 찾고, 요일로 그날 계획을 꺼냅니다. 단계 시작일부터 센 주차가 홀수이면 격주 루틴을 써서 전신 A / B가 주마다 자연스럽게 번갈아 나옵니다. 단계 기간이 서로 겹치면 날짜별 계획이 모호해지므로, 단계를 만들거나 기간을 바꿀 때 겹치면 서버가 거절하고 어느 단계와 겹치는지 알려 줍니다.

④ 연속 기록 · 출석률

계획된 운동일(근력 · 유산소)만 셉니다. 휴식일과 플랜 밖 날짜는 건너뛰고, 아직 안 한 오늘은 연속을 끊지 않습니다. 아프거나 일정이 생겨 '못 함'으로 표시한 날은 달력에 빨간 '빠짐' 대신 회색으로 남지만, 안 한 날이므로 연속은 끊고 출석률에도 들어갑니다. 출석률 옆에는 "3/4일"처럼 근거를 같이 보여 줍니다.

⑤ 화면 짜임새

  • 타입 자동 생성: npm run gen:api 가 백엔드 OpenAPI → schema.ts 를 만들고, 화면 코드는 거기서 이름만 붙여 씁니다.
  • CSS Modules + 디자인 토큰: 색 · 글자 · 모서리는 토큰으로, 화면별 모양은 *.module.css 로 나눴습니다. 다크가 기본이고 라이트 · 기기 설정 따름을 고를 수 있습니다.
  • 브라우저 기본 UI 대신: select · 날짜 입력 · confirm · title 툴팁을 직접 만든 선택 상자 · 날짜 선택기 · 확인 창 · 말풍선으로 바꿨습니다. 휴대폰에서도 모양이 같고 키보드로도 조작됩니다.
  • 글꼴 · 아이콘은 파일로: Pretendard(본문)와 Barlow Condensed(숫자, 스코어보드 느낌), 아이콘 스프라이트를 저장소 안에 두고 씁니다.

4. 화면으로 보는 기능

4.1 홈

홈 — 쉬는 날에는 다음 운동 미리 보기, 이번 주, 다음 운동, 체중, 전체 일정
  • 오늘 운동: 종목별 추천 무게와 올린 폭(+10 등). 쉬는 날에는 다음 운동을 미리 보여 줍니다.
  • 이번 주: 완료 · 연속 운동 · 출석률과 7일 상태 막대(완료 초록 · 못 함 회색).
  • 체중: 7일 평균, 목표까지 진행 막대, 오늘 아침 체중을 바로 기록하는 칸, 아침 체중(점)과 7일 평균(선) 그래프.
  • 전체 일정: 지금 단계의 진행과 다음 두 단계.
  • 테마: 다크가 기본이고 라이트 · 기기 설정 따름을 설정에서 고릅니다 (서론 그림이 라이트 테마).

4.2 오늘 운동

오늘 운동 — 종목 카드마다 추천 무게, 추천 이유, 지난 기록, 무게 · 횟수 입력
  • 종목 카드: 추천 무게와 이유("3세트 모두 12회 달성 → 110kg로 올림"), 지난번 기록.
  • 세트 기록: 무게 · 횟수 · 남은 개수를 넣고 기록. 시간 종목(플랭크)은 초로 기록합니다.
  • 유산소 · 메모 · 몸 상태: 같은 화면 아래에서 함께 기록하고, 운동을 못 한 날은 '못 함'으로 표시합니다.

4.3 운동 모드 · 휴식 타이머 (휴대폰)

휴대폰 화면 — 홈과 운동 모드, 세트 기록 뒤 휴식 타이머
  • 한 종목씩 크게: 헬스장에서 한 손으로 누르기 좋게 글자 · 버튼을 키웠습니다. 위쪽 막대가 종목 진행입니다.
  • 휴식 타이머: 세트를 기록하면 종목별 휴식 시간(큰 종목 90초, 작은 종목 60초)으로 자동 시작, −15초 · +30초 · 바로 시작. 끝나면 진동 · 소리로 알립니다.
  • 자동 다음 종목 · 요약: 종목을 다 하면 다음 종목으로 넘어가고, 끝나면 세트 · 볼륨 · 신기록을 요약합니다.

4.4 달력

달력 — 날짜마다 계획(근력 · 유산소 · 휴식)과 완료 · 빠짐 · 못 함
  • 계획과 결과를 한 칸에: 근력은 파랑, 유산소는 청록, 완료한 날은 초록 바탕.
  • 날짜 상세: 날짜를 누르면 그날 계획 · 기록한 세트 · 할 종목이 아래에 나옵니다.

4.5 체중 · 월말 측정

체중 화면 — 오늘 체중 · 걸음 · 수면, 체중 추이, 월말 측정
  • 하루 값 대신 7일 평균: 체중은 하루하루 흔들리므로 달력 기준 7일 평균으로 추세를 봅니다.
  • 월말 측정: 허리 · 가슴 · 엉덩이 · 허벅지 · 팔 둘레와 체지방, 측정일 7일 평균 체중을 함께 남깁니다.

4.6 분석

분석 — 최근 7일 근육별 세트와 목표선, 개인 기록
  • 근육별 세트: 종목에 근육 13부위를 주동 / 보조로 달아 두고, 최근 7일 세트를 주동 1 · 보조 0.5로 셉니다. 세로선은 주간 목표입니다.
  • 개인 기록: 종목별 최고 무게와 추정 1RM(Epley).
  • 좌우 균형: 한쪽씩 하는 종목의 왼쪽 · 오른쪽 비교.

4.7 플랜 편집

플랜 — 전체 일정 로드맵과 단계별 요일 계획
  • 전체 일정: 단계를 점과 선으로 잇는 목록. 단계 길이가 1주에서 3개월까지 달라 비율 막대 대신 목록으로 보여 줍니다.
  • 단계: 새 단계(요일 계획 복사), 이름 · 기간 수정(직접 만든 날짜 선택기), 삭제.
  • 요일 · 루틴 · 종목: 요일마다 근력 · 유산소 · 휴식과 루틴(격주 루틴)을 정하고, 루틴의 종목 · 순서 · 세트 · 횟수를 바꿉니다.

5. 한계 · 설치 · 사용 흐름

한계 (중요)

  • 1인용 · 집 안 네트워크 전제입니다. 로그인이 없고 DB 비밀번호도 기본값이라 인터넷에 공개하는 용도가 아닙니다.
  • 식단 · 칼로리 기록은 없습니다.
  • 운동 모드의 화면 꺼짐 방지는 브라우저 정책상 localhost · HTTPS 에서만 동작합니다. 휴대폰에서 http://<PC IP> 로 열면 화면이 꺼질 수 있습니다.
  • 처음 들어가는 기본 플랜은 제 플랜 기준입니다. 플랜 화면에서 단계 · 요일 · 루틴을 바꿔 쓰면 됩니다.

사전 요구

항목 설명
Docker Docker Desktop (실행용)
Python 3.12 + uv, Node.js 개발용

설치 · 실행

bash
git clone https://github.com/Hyeonseok93/MINI_Piste.git
cd MINI_Piste
docker compose up -d --build

브라우저에서 http://localhost:8080 을 열고 이름을 입력하면 시작합니다. 휴대폰은 같은 와이파이에서 http://<PC의 IP>:8080. Windows에서는 start.cmd / stop.cmd 로 켜고 끌 수 있습니다.

저장 위치: Docker 볼륨 piste_pgdata (PostgreSQL). docker compose down -v 를 하면 기록이 모두 지워집니다.

사용 흐름

  1. 처음 열면 이름(과 키 · 시작 / 목표 체중)을 넣고 시작합니다.
  2. 플랜에서 단계 · 요일 계획 · 루틴을 내 일정에 맞게 고칩니다.
  3. 헬스장에서 휴대폰으로 운동 시작 → 추천 무게로 세트를 기록하고 휴식 타이머를 따라갑니다.
  4. 처음 하는 종목은 무게 찾기로 시작 무게를 정합니다.
  5. 아침마다 홈에서 체중을 기록하고, 월말에 둘레를 잽니다.
  6. 달력 · 분석에서 출석과 근육별 세트, 기록이 느는 것을 확인합니다.
웹 앱개인 프로젝트 · 4개 중 1번째프로젝트 전체 →
  1. PISTE운동 스케줄 · 세트 기록
  2. Patience Flashcard페이션스형 다층 암기 웹
  3. Web ScoreBoard보드게임용 반응형 웹 점수판 & 커스텀 승리 음악
  4. Code CanaryNVD·OSV 취약점 인텔리전스