“입력은 더 빠르게, 피드백은 더 직관적으로.”
/api/stats/monthly의 byCategory로 원형 차트 추가/api/accounts, /api/categories?flow=, /api/transactions 사용10.0.2.2:8080 (물리 기기는 PC LAN IP:8080)src/api/client.tsAccount, CategoryAccountsAPI.list(), CategoriesAPI.list(flow?)src/screens/TransactionFormScreen.tsxsrc/screens/HomeScreen.tsx# 차트 & SVG (Expo가 자동 링크)
npx expo install react-native-svg
npm i victory-native@^36.6.11
# 드롭다운 & 날짜 선택기 (Expo SDK에 맞춰 설치 권장)
npx expo install @react-native-picker/picker @react-native-community/datetimepicker
npm i로 설치하다 충돌나면expo install을 사용하거나,node_modules/package-lock.json삭제 후 다시 설치.
# 안드로이드 에뮬레이터
EXPO_PUBLIC_API_URL=http://10.0.2.2:8080 npx expo start -c
# 물리 기기(같은 Wi-Fi): PC IP 사용
EXPO_PUBLIC_API_URL=http://<PC_LAN_IP>:8080 npx expo start -c
app.config.js에 extra.apiUrl을 넣어두면, 환경변수가 없을 때 해당 값 사용.
실제 사용 중인 baseURL은 개발 콘솔에서 한 번 확인해두면 디버깅이 편함:
console.log('[API baseURL]', api.defaults.baseURL);
export type Account = { id: number; type: 'CASH'|'CARD'|'BANK'; name: string };
export type Category = { id: number; flow: 'INCOME'|'EXPENSE'; name: string; color?: string };
export const AccountsAPI = {
async list() { const { data } = await api.get<Account[]>('/api/accounts'); return data; }
};
export const CategoriesAPI = {
async list(flow?: 'INCOME'|'EXPENSE') {
const url = flow ? `/api/categories?flow=${flow}` : '/api/categories';
const { data } = await api.get<Category[]>(url); return data;
}
};
const [flow, setFlow] = useState<'INCOME'|'EXPENSE'>('EXPENSE');
const [date, setDate] = useState(ymd(new Date()));
// ...
<View style={{ flexDirection:'row', gap:8 }}>
{(['INCOME','EXPENSE'] as const).map(v => (
<Pressable key={v} onPress={()=>setFlow(v)} /* 선택 스타일 */>
<Text>{v==='INCOME'?'INCOME(수입)':'EXPENSE(지출)'}</Text>
</Pressable>
))}
</View>
<View> {/* 계정 드롭다운 */} <Picker ...>{accounts.map(a=>... )}</Picker></View>
<View> {/* 카테고리 드롭다운 */} <Picker ...>{categories.map(c=>... )}</Picker></View>
<Pressable onPress={()=>setShowDate(true)}><Text>{date}</Text></Pressable>
{showDate && <DateTimePicker value={new Date(date)} mode="date" onChange={(_,d)=>{ setShowDate(false); d && setDate(ymd(d)); }} />}
import { VictoryPie } from 'victory-native';
const INNER = 60;
const pieData = (stats?.byCategory||[])
.map((c:any)=>({ x:c.name, y:Number(c.total) }))
.filter((d:any)=>d.y>0);
<VictoryPie
data={pieData}
height={220}
innerRadius={INNER}
padAngle={2}
labelRadius={INNER + 30}
style={{ labels: { fontSize: 10 } }}
/>


10.0.2.2:8080http://<PC_LAN_IP>:8080http://10.0.2.2:8080/ping 열리면 네트워크 OK.npx expo install <패키지>로 SDK에 맞춰 설치node_modules/package-lock.json 제거 후 재설치labelRadius={({ innerRadius }) => (innerRadius ?? 60) + 30}에서
“NumberOrCallback + number 연산 불가” 경고 → 상수로 고정:
const INNER = 60;
<VictoryPie innerRadius={INNER} labelRadius={INNER + 30} ... />
(또는 typeof innerRadius==='function' 분기 후 숫자로 변환)
ConfigError: main entry ... → package.json의 "main": "index.js"로 설정하고 루트에 index.js 생성:
import { registerRootComponent } from 'expo';
import App from './App';
registerRootComponent(App);