/workout-sessions/new, /workout-sessions/{session_id}/edit는 같은 React 페이지를 사용함.
생성/수정 모드는 서버 props가 아니라 React Router path와 path parameter 기준으로 결정함.
생성 화면의 기본 운동일은 GET /api/health/meta/default-workout-date로 조회함.
폼 초기 메타와 상세 조회 로딩 중에는 SkeletonBlock form variant를 표시함.
상단에는 운동 날짜와 메모를 배치함.
운동 그룹 카드 영역은 운동 시작 시간 입력칸, 운동 그룹 카드 목록, 운동 종료 시간 입력칸 순서로 구성함. 두 시간은 선택값이며 HH:MM 형식으로 저장함. 운동 그룹을 추가·삭제하거나 순서를 바꿔도 시작 시간은 카드 목록의 앞, 종료 시간은 카드 목록의 뒤에 고정함.
세트 입력은 jb-workout-set-grid 계열 공통 클래스를 사용하여 헤더와 행을 같은 컬럼 템플릿으로 맞춤.
세트 입력 그리드의 중량 헤더는 화면 컴포넌트의 한국어 문구 중량(kg)로 표기함.
세트 입력 그리드 헤더는 공통 스타일에서 대문자 변환을 강제하지 않으며, 단위 kg는 소문자를 유지함.
세트 상태는 계획, 성공, 실패를 순환하며, 성공 시 RPE, 실패 시 FP 계열 입력만 활성화함.
운동 그룹 카드의 보조 액션은 우측 정렬로 배치하며, 직전 기록, 추가, 삭제 순서를 사용함.
운동 그룹 카드의 추가 버튼은 해당 기구에 새 세트 행을 추가하며, 새 행의 상태는 계획으로 생성함.
새 세트 행의 중량과 반복수는 같은 화면 안에서 같은 기구의 가장 최근 성공 세트가 있으면 그 값을 우선 사용하고, 없으면 직전 기록 조회 API가 제공하는 같은 기구의 가장 최근 성공 세트 값을 사용함.
같은 화면과 직전 기록 조회 결과에 사용할 수 있는 성공 세트가 없으면 새 세트 행은 기구별 최소 중량과 기본 반복수 1회를 사용함.
직전 기록 버튼은 모달을 먼저 열고, 사용자가 가져오기를 누른 뒤 확인창에서 승인할 때만 현재 그룹 세트를 PLAN 기준으로 덮어씀.
직전 기록 가져오기 확인창 문구는 화면 컴포넌트에 한국어 문구로 직접 작성함.
직전 기록 가져오기 확인창에서 취소하면 직전 기록 모달은 유지되고 현재 세트 입력값은 변경되지 않음.
직전 기록 조회는 현재 화면의 운동일과 저장된 세션 식별자를 API query로 함께 전달함.
저장된 세션 화면에서 직전 기록을 조회하면 현재 세션은 제외되고, 현재 세션보다 이전인 운동 세션 중 같은 운동 종목이 포함된 가장 최근 세션의 기록 전체가 표시됨.
신규 세션 화면에서 직전 기록을 조회하면 현재 화면의 운동일 이하에서 같은 운동 종목이 포함된 가장 최근 세션의 기록 전체가 표시됨.
직전 후보 세션 안에 같은 운동 종목 기록이 여러 묶음 또는 여러 세트로 존재하면 모달은 해당 기록을 모두 보여줌.
직전 후보가 없으면 같은 모달 안에서 직전 기록 없음 상태를 표시하고 가져오기 버튼을 비활성화함.
직전 기록 모달은 직전 세트의 상태, 중량, 반복, 그리고 상태에 따라 RPE 또는 FP만 한 줄 카드로 보여줌.
직전 기록 모달의 상태 칩은 세션 입력 화면과 같은 색 규칙을 사용함.
운동 선택 바텀시트와 직전 기록 시트의 닫기 버튼은 고스트 버튼을 사용하며, 버튼 텍스트가 줄바꿈되지 않도록 최소 가로폭을 확보함.
운동 선택 바텀시트의 운동 부위 필터는 shared-ui의 SegmentedTabs를 사용하며, 부위 항목 수가 늘어날 수 있으므로 가로 스크롤 가능한 탭 구조를 사용함.
중량 숫자는 정수면 소수점 없이 표시하고, 소수점이 있을 때만 소수부를 유지함.
숫자 선택 시트는 shared-ui의 BottomNumberPicker를 사용하며, 실제 하단 선택 시트 외형과 스크롤 동작은 공통 BottomOptionPicker 계약을 재사용하고 숫자 옵션은 중앙 정렬함.
숫자 선택 시트와 직전 기록 모달은 호출 시점의 문서 스크롤 위치를 보존하고 닫기, 가져오기, 오류 처리 후 같은 위치로 복원함.
하단 고정 액션바는 목록, 연결, 삭제, 추가, 저장 흐름을 사용하며, 추가와 저장이 남은 폭을 균등 분할함.
하단 고정 액션바는 모바일 viewport 하단에 고정되며, SPA route transition stage 안에서도 페이지 스크롤 끝으로 밀리지 않는 구조를 사용함.
/weight-records는 HEALTH 권한 사용자에게 제공하는 모바일 체중 관리 카드 목록 화면임.
앱 셸 상단 메뉴 순서는 운동 -> 러닝 -> 체중 -> 음식임. 음식 기록과 음식 관리 경로에서는 모두 음식 메뉴가 활성화됨.
목록 진입 시 GET /api/health/weight/summary와 GET /api/health/weight/records를 병렬 조회함.
요약은 최근 체중, 최근 90일, 최근 투약 3열을 같은 위계로 표시함.
최근 90일 변화량의 계산 기준은 API 응답을 그대로 사용하며 클라이언트가 재계산하지 않음.
최근 투약은 API의 최신 투약 일시를 기준으로 현재 날짜와의 일수 차이를 클라이언트에서 계산해 N일 전 형식으로 표시함. 약명·용량을 보여 주는 별도 회색 보조 문구는 표시하지 않음.
목록 카드는 YYYY-MM-DD (화) 날짜 텍스트, 체중 주요 텍스트, 선택 투약 통합 뱃지로 구성함.
통합 투약 뱃지는 약명 · 용량 · 부위 형식이며 투약 부위만 단독 뱃지로 중복 표시하지 않음.
카드 우측 보조 영역은 현재 비워 두며 후속 상태·지표 확장을 위한 레이아웃만 유지함.
목록 하단 액션바는 다운로드, 새 기록 순서임. 다운로드 바텀시트는 GET /api/health/weight/records/date-range로 현재 사용자 활성 기록의 최초·최종 날짜를 조회하여 기본 기간을 설정하고, 시작일·종료일과 CSV·XLSX 형식을 선택함. 시작일이 종료일보다 늦으면 요청하지 않음.
파일 요청은 GET /api/health/weight/records/download에 start_date, end_date, format 쿼리를 전달하는 문서 이동 방식임. 다운로드 바텀시트의 설명은 남은 너비에서 줄바꿈하고 닫기 버튼은 자신의 너비가 줄지 않아 한 줄로 표시됨.
/running-plans/new, /running-plans/{planId}/edit는 같은 React 폼 화면을 사용함.
러닝 계획 편집 화면은 근력운동 세션 편집 화면을 확장하지 않고 별도 RunningPages 계열 컴포넌트에서 관리함.
상단 입력 영역은 러닝 이름, 러닝 날짜, 실행 상태, 메모, 전체 예상 운동시간과 예상 거리를 순서대로 표시함.
러닝 날짜는 type=date 입력으로 관리하며 시간은 입력하지 않음.
실행 상태는 현재 running_status를 표시하며 계획 입력 payload에는 포함하지 않음.
수정 화면에서 상태가 COMPLETED가 아니면 상태 카드에 상태 변경 버튼을 제공함. 버튼은 BottomOptionPicker 하단 선택 시트를 열어 상태 전환 항목을 선택하게 함.
IN_PROGRESS 상태의 선택 항목은 수행 전으로 되돌리기와 수행 완료 처리임. 수행 전으로 되돌리기는 확인창 승인 뒤 PUT /api/health/running/plans/{planId}/progress에 running_status=READY, current_timeline_index=null을 전송하고 성공 응답의 running_status로 화면 상태를 갱신함.
READY 상태의 선택 항목은 수행 완료 처리만 제공함. 수행 완료 처리는 확인창 승인 뒤 POST /api/health/running/plans/{planId}/performed를 호출하고 성공 응답의 running_status로 화면 상태를 갱신함.
COMPLETED 상태에서는 상태 변경 버튼을 표시하지 않음.
러닝 블록은 카드 하나가 연속 구간 또는 반복 패턴 하나를 의미함.
러닝 블록 카드는 카드 이름, 반복 횟수, 총 시간, 예상 거리, 단계 목록을 포함함.
블록 카드는 개별 접힘/펼침 상태를 가지며, 한 블록을 펼친다고 다른 블록이 자동으로 접히지 않음.
블록 제목 옆 요약 뱃지는 반복 횟수 · 총 시간 · 예상 거리를 한 뱃지에 표시함.
예상 거리는 단계별 목표 속도와 실행 시간을 기준으로 화면에서 계산하며 별도 저장값으로 관리하지 않음.
거리 표시는 소수점 둘째 자리까지 반올림하고 불필요한 trailing zero는 제거함.
블록 순서 이동은 카드 우측 상단의 위/아래 아이콘 버튼으로 수행함.
블록 삭제와 복사는 펼친 카드 하단의 아이콘 버튼으로 제공함.
단계 추가는 펼친 카드 하단의 텍스트 버튼으로 제공함.
러닝 단계는 별도 단계 이름 입력을 받지 않음.
러닝 단계의 표시명과 음성 안내명은 단계 유형 공통코드 라벨을 기준으로 함.
단계 입력 행은 유형, 분, 속도, 경사를 한 줄로 배치함.
단계 유형 선택은 네이티브 select가 아니라 shared-ui의 BottomOptionPicker 기반 모바일 하단 선택 시트를 사용함.
단계 유형 option은 GET /api/health/running/meta/steps 응답을 사용함.
블록 반복 횟수는 1~20의 BottomOptionPicker 선택값으로 입력하며 기본값은 1임.
단계 시간 입력 헤더는 분으로 표기함. 입력 중인 분 문자열은 저장 단위인 초와 분리해 관리하므로 사용자가 기존 값을 완전히 지운 뒤 새 값을 입력할 수 있음.
단계 시간 입력은 숫자, -, .만 편집 중간값으로 허용함. 비어 있거나 숫자로 해석할 수 없는 중간값은 단계 상태에 NaN으로 반영하지 않음.
비어 있는 단계 시간 입력값으로는 저장하거나 러닝을 시작할 수 없으며, 저장 시 유한한 숫자와 0.1분 이상을 검증함. 유효한 분 입력값은 가장 가까운 초 단위 정수로 변환해 저장 payload에 반영함.
경사는 고밀도 단계 입력 행의 직접 키 입력 필드로 제공함. 숫자만 입력할 수 있고 기존 값을 완전히 지울 수 있으며, 편집 중 빈 값은 단계 상태에 NaN으로 반영하지 않음.
저장 또는 러닝 시작 전 경사는 0~20 범위의 정수인지 검증하며, 기본값은 0임.
목표 속도는 선택 입력값이며 비어 있으면 저장 payload에서 null로 정규화됨.
하단 고정 액션바는 수정 화면에서 목록 아이콘 -> 삭제 아이콘 -> 복사 아이콘 -> 러닝 시작 -> 저장 구조를 사용함.
삭제 아이콘은 즉시 CUD를 일으키는 삭제 액션이므로 jb-button 강조 버튼 외형을 사용함.
복사 아이콘은 확인창을 거친 뒤 POST /api/health/running/plans/{planId}/copy를 호출함.
복사 성공 시 러닝 계획을 복사했습니다. 성공 토스트를 표시하고 복사된 계획의 /running-plans/{newPlanId}/edit 화면으로 이동함.
생성 화면에서는 삭제 아이콘과 복사 아이콘을 표시하지 않음.
저장은 생성 시 POST /api/health/running/plans, 수정 시 PUT /api/health/running/plans/{planId}를 호출함.
일반 저장은 IN_PROGRESS 상태의 계획만 progress API로 READY와 빈 진행 단계로 정리한 뒤 계획 저장을 수행함. COMPLETED 상태의 계획은 수정 저장 후에도 수행 완료 상태를 유지함.
러닝 시작은 계획을 먼저 저장하되 실행 상태와 진행 단계 번호를 변경하지 않고 저장된 계획 식별자를 기준으로 /running-run/{planId}로 이동함.