🦔

ネイティブアプリ開発日記(8/100)支出・収入の切替

に公開

前回はこちら
https://zenn.dev/nabeken/articles/9f254211fa9d47

  • 前回作成した家計簿アプリの機能
    • 画面遷移
      • 画面下部のタブバーで遷移できる
    • 支出登録機能
      • 年月日を押下すると、カレンダーが表示されて選択できる
      • 金額を押下すると、テンキーが表示されて入力できる
      • カテゴリを押下すると、リストが表示されて選択できる
    • 支出参照機能
      • 年月毎の集計を参照できる
      • 一覧を参照できる
    • 支出編集機能
      • 支出(1件毎)を編集できる
      • 支出(1件毎)を削除できる
  • 今回作成する機能
    • 支出の他に収入を管理できる

入力画面はこちら。セグメントコントロールで支出・収入を切替可能。

セグメントで「収入」を選択したのがこちら。

カテゴリは支出・収入で分ける。

レポート画面も支出・収入で切替可能。

実装面の学び1. 入力画面での支出・収入の切替

  • 支出・収入の選択を状態として定義
    • const [type, setType] = useState('expense');
  • セグメントコントロールでの選択
    • <SegmentedControl values={['支出', '収入']} onChange={(event) => { setType(・・・); }
  • 入力欄のカテゴリも支出・収入で個別に管理
    • const categories = type === 'expense' ? expenseCategories : incomeCategories;
InputScreen.js
import SegmentedControl from '@react-native-segmented-control/segmented-control';
import { expenseCategories, incomeCategories } from '../parameters/categories';

// 入力変数の初期値
const [type, setType] = useState('expense');
const [category, setCategory] = useState('-');

// 支出・収入の切替
const categories = type === 'expense' ? expenseCategories : incomeCategories;
const typeText = type === 'expense' ? '支出' : '収入';

return (
    <View style={styles.container}>
        <SegmentedControl
            values={['支出', '収入']}
            selectedIndex={type === 'expense' ? 0 : 1 }
            onChange={(event) => {
                const index = event.nativeEvent.selectedSegmentIndex;
                setType(index === 0 ? 'expense' : 'income');
                setCategory('-');
            }}
        />
        <Picker
            selectedValue={category}
            onValueChange={(itemValue) => setCategory(itemValue)}
        >
            {categories.map((cat) => (
                <Picker.Item key={cat.value} label={cat.label} value={cat.value} />
            ))}
        </Picker>
    </View>
);

実装面の学び2. レポート画面での支出・収入の切替

  • 支出・収入の選択を状態として定義
    • const [type, setType] = useState('expense');
  • 画面に表示するデータを支出・収入で切替
    • const filtered = data.filter((item) => item.type === type);
SummaryScreen.js
import SegmentedControl from '@react-native-segmented-control/segmented-control';
import { loadExpense } from '../utils/storage';
import { calculateMonthlyTotals } from '../utils/aggregate';

const isFocused = useIsFocused();
const [transactions, setTransactions] = useState([]);
const [monthlyTotals, setMonthlyTotals] = useState([]);
const [type, setType] = useState('expense');
const typeText = type === 'expense' ? '支出' : '収入';

useEffect(() => {
    const fetchData = async () => {
        const data = await loadExpense();
        const filtered = data.filter((item) => item.type === type);
        setTransactions(filtered);
        setMonthlyTotals(calculateMonthlyTotals(filtered));
    };
    if (isFocused) {
        fetchData();
    }
}, [isFocused, type]);

return (
    <View>
        <SegmentedControl
            values={['支出', '収入']}
            selectedIndex={type === 'expense' ? 0 : 1 }
            onChange={(event) => {
                const index = event.nativeEvent.selectedSegmentIndex;
                setType(index === 0 ? 'expense' : 'income');
            }}
        />
        <Text>月ごとの合計{typeText}</Text>
        {Object.entries(monthlyTotals).map(([month, {expense, income}]) => (
            <Text key={month}>
                {month}: {type === 'expense' ? `¥${expense}` : `¥${income}`}
            </Text>
        ))}
        <Text>{typeText}一覧</Text>
        <FlatList
            data={transactions}
            keyExtractor={(item) => item.id}
            renderItem={({ item }) => (
                <View>
                    <Text>{item.date} - {item.category}: ¥{item.amount}</Text>
                </View>
            )}
        />
    </View>
)

Discussion