앱인토스 미니앱 11 – 체중 다이어리
앱인토스 미니앱 제작기 열한 번째는 체중 다이어리입니다. 오늘 체중을 적고 최근 흐름을 보는, 기능이 많지 않은 앱이에요.
이 앱에서 잡은 기준은 두 가지입니다. 빨리 적을 수 있을 것, 그리고 흐름을 판정 없이 보여줄 것. 이 글은 그 두 가지를 화면과 코드로 옮긴 기록입니다.
체중 다이어리는 어떤 앱인토스 미니앱인가



화면은 오늘 기록 홈 하나에, 주간 요약과 그래프, 설정이 시트로 올라오는 구조입니다. 그래프 탭이나 설정 탭을 따로 두지 않았어요.
위 화면의 체중 값은 시연용으로 넣은 예시입니다.
숫자를 치지 않고 적는다
숫자를 처음부터 입력하지 않고, 마지막에 적은 값에서 시작합니다.
- 앱을 열면 마지막 기록이 미리 채워져 있다
- 빼기와 더하기 버튼으로 0.1kg씩 조정한다
- “오늘 기록”을 누른다
어제 기록이 있으면 “어제와 같은 62.3kg” 버튼이 따로 나옵니다. 숫자를 직접 입력할 수도 있어요. 저장한 직후에는 “방금 저장한 기록 되돌리기”가 보여서, 잘못 눌렀을 때 바로 되돌릴 수 있습니다.
앱인토스 미니앱 1 – 물마시기하루에 기록은 하나
같은 날 두 번 저장하면 기록이 두 개 생기는 게 아니라 그날 값이 바뀝니다.
/** Pure state update. The caller persists the returned value and handles storage errors. */
export function recordWeight(state: WeightState, weight: number, date = getToday()): WeightState {
if (!validateWeight(weight) || !isCalendarDate(date)) {
throw new RangeError('체중과 날짜를 확인해 주세요.');
}
const rounded = Math.round(weight * 10) / 10;
const records = [...recordMap(state.records).values()];
const existingIndex = records.findIndex((record) => record.date === date);
const record = { date, weight: rounded };
if (existingIndex >= 0) records[existingIndex] = record;
else records.push(record);
records.sort((a, b) => a.date.localeCompare(b.date));
return { ...state, records, startWeight: state.startWeight || rounded, isOnboarded: true };
}값을 0.1kg 단위로 반올림하고, 그 날짜의 기록이 이미 있으면 바꾸고 없으면 더합니다. 화면의 버튼도 여기에 맞춰 이름이 바뀌어요. 오늘 기록이 없으면 “오늘 기록”, 있으면 “오늘 기록 수정”입니다.
계산과 저장을 나눈다
이 함수는 새 상태를 만들어 돌려주기만 하고 기기에 쓰지는 않습니다. 저장은 부른 쪽에서 따로 하고, 실패하면 “기기에 저장하지 못했어요”라고 알려요. 저장이 안 됐는데 화면에 저장됐다고 나오는 일을 막으려는 구조입니다. 경조사 봉투장부에서 쓴 것과 같은 생각이에요.
빠진 날짜를 지어내지 않는다
이 앱은 연속 기록을 요구하지 않고, 빠진 날을 실패로 표시하지도 않습니다. 그러면 기록이 듬성듬성할 때 평균을 어떻게 낼지를 정해야 하는데, 기준은 “입력한 값만 쓴다”로 잡았습니다.
function average(records: WeightRecord[]): number | null {
if (records.length < 3) return null;
return records.reduce((total, record) => total + record.weight, 0) / records.length;
}기록이 3개보다 적으면 평균을 내지 않습니다. 화면은 이때 평균 대신 “기록이 3회 모이면 7일 평균을 보여드려요”라고 안내합니다. 평균을 보여줄 때도 “최근 7일 중 기록한 6일 평균”처럼 며칠 치인지 같이 적어요.
그래프도 마찬가지입니다. 위 세 번째 화면을 보면 선이 중간중간 끊겨 있는데, 기록이 없는 날을 건너뛰고 점을 이으면 그 사이에도 값이 있었던 것처럼 보이기 때문에 일부러 잇지 않았습니다.
앱인토스 미니앱 5 – 경조사 봉투장부주간 비교는 끝난 주끼리만
주간 요약은 지난주와 그 전 주의 평균을 비교합니다. 여기서 “이번 주”는 넣지 않아요.
/** Compares the two most recent complete Monday–Sunday weeks; never includes the current partial week. */
export function getCompletedWeeklyComparison(state: WeightState, today = getToday()): WeeklyComparison {
const currentWeekStart = addDays(mondayOfWeek(today), -7);
const currentWeekEnd = addDays(currentWeekStart, 6);
const previousWeekStart = addDays(currentWeekStart, -7);
const previousWeekEnd = addDays(previousWeekStart, 6);
const current = summarizePeriod(state, currentWeekStart, currentWeekEnd);
const previous = summarizePeriod(state, previousWeekStart, previousWeekEnd);
const difference = current.average === null || previous.average === null
? null
: current.average - previous.average;
return { current, previous, difference };
}오늘이 수요일이면 이번 주는 사흘 치밖에 없습니다. 그래서 월요일부터 일요일까지 다 지난 주 두 개만 놓고 봅니다. 두 주 모두 기록이 3개 이상이어야 차이를 계산하고, 한쪽이라도 모자라면 차이를 보여주지 않아요.
차이를 보여주는 말투도 정해 뒀습니다. 두 주의 평균과 함께 “두 주 평균 차이 -0.5kg”이라고 적을 뿐, 잘했다거나 분발하라는 말은 붙이지 않습니다. 색으로 좋고 나쁨을 가르지도 않아요. 하루하루의 변동을 평가하거나 건강 판정처럼 읽힐 표현을 피하려는 겁니다. 화면에 보이는 두 평균을 직접 빼면 0.1kg쯤 다를 수 있는데, 앱은 반올림하기 전 값으로 차이를 계산합니다.
남에게 보이지 않는 기록
체중은 남에게 보이고 싶지 않은 숫자입니다. 그래서 공유 기능을 만들지 않았고, 순위나 다른 사람과의 비교도 없습니다. 기록은 이 기기에만 저장되고 자동으로 동기화되지 않아요.
목표 체중도 기본으로는 보이지 않습니다. 설정에서 직접 정한 사람에게만 표시돼요.
기록과 기본 요약, 그래프는 전면 광고 없이 쓸 수 있습니다. 30일을 구간별로 나눈 평균 요약을 더 보고 싶을 때만 광고 안내가 나옵니다.
다음에 할 일
몇 주 운영한 뒤에 기록이 실제로 어떻게 쌓이는지 다시 확인해 볼 생각입니다.
프로젝트를 어떤 식으로 진행하는지는 프로젝트 진행 방식에, 매일 기록하는 또 다른 앱 이야기는 물마시기 글에 있습니다.
체중 다이어리 자주 묻는 질문
체중 다이어리는 어떤 앱인가요?
오늘 체중을 간편하게 기록하고 최근 7일·30일 흐름과 주간 요약을 확인하는 앱입니다. 목표 체중은 설정에서 직접 정했을 때만 표시됩니다.
기록을 빠뜨린 날은 어떻게 처리하나요?
빠진 날짜의 값을 추정해서 채우지 않습니다. 평균은 실제로 기록한 날의 값으로만 내고, 그래프에서는 날짜가 비어 있으면 점을 선으로 잇지 않습니다.
체중 기록은 어디에 저장되나요?
이 기기에만 저장되고 자동으로 동기화되지 않습니다. 공유 기능도 없습니다.
이 글은 앱인토스 미니앱 「체중 다이어리」의 기록입니다. 앱인토스는 토스 앱 안에서 미니앱을 서비스하는 플랫폼이고, 공식 안내는 앱인토스 개발자센터에 있습니다.
이 글은 직접 만들고 운영하며 남긴 기록입니다. 적힌 수치는 작성 시점의 제 계정 기준이며, 같은 결과나 수익을 보장하지 않습니다.

One Comment