家計簿アプリを作る #23:収支一覧のキーワード検索
家計簿アプリ作成シリーズの第23回です。収支一覧にキーワード検索機能を追加します。月フィルタと組み合わせて、メモ・カテゴリを対象に絞り込みができるようにします。
実装方針
?search=キーワードクエリパラメータで検索キーワードを管理- DBクエリは
allTransactionsの全件取得1本のみ - 月フィルタ・検索・ページネーションはすべてJavaScriptで処理
- UIは flowbite-svelte の
Searchコンポーネントを使用
+page.server.ts の実装
// 検索キーワード
const search = url.searchParams.get('search') ?? '';
// 年月+検索で絞り込み
const monthTransactions = allTransactions.filter(t => {
const matchMonth = t.date.startsWith(selectedMonth ?? '');
const matchSearch = search
? t.memo.includes(search) || t.category.includes(search)
: true;
return matchMonth && matchSearch;
});
// ページ数計算・ページネーション
const totalPages = Math.ceil(monthTransactions.length / PAGE_SIZE);
const selectedTransactions = monthTransactions.slice(offset, offset + PAGE_SIZE);
// 集計はmonthTransactions(絞り込み後の全件)から計算
const income = monthTransactions
.filter(t => t.type === 'income')
.reduce((sum, t) => sum + t.amount, 0);
const expense = monthTransactions
.filter(t => t.type === 'expense')
.reduce((sum, t) => sum + t.amount, 0);
当初はDBクエリ(like・or)とJavaScriptフィルタの両方で検索を実装していましたが、allTransactions で全件取得済みなのでJavaScriptだけで統一しました。DBクエリを1本に減らすことで、集計・ページ数・表示データの整合性が取れます。
+page.svelte の実装
<script lang="ts">
import { goto } from "$app/navigation";
import { Search, Button } from "flowbite-svelte";
let { data } = $props();
let searchQuery = $state("");
function handleSearch() {
goto(`/?month=${data.selectedMonth}&page=1&search=${searchQuery}`);
}
function handlePageChange(page: number) {
currentPage = page;
goto(`/?month=${data.selectedMonth}&page=${currentPage}&search=${searchQuery}`);
}
</script>
<Search bind:value={searchQuery}>
<Button class="me-1" onclick={handleSearch}>検索</Button>
</Search>
Search コンポーネントに bind:value={searchQuery} で入力値を変数に同期します。Button の onclick からその変数を参照して goto で遷移します。
ページ切り替え時に検索キーワードを引き継ぐ
ページ切り替え時も search パラメータを URL に含めることで、検索状態を維持します。
function handlePageChange(page: number) {
currentPage = page;
goto(`/?month=${data.selectedMonth}&page=${currentPage}&search=${searchQuery}`);
}
月切り替え時は検索をリセット
月を切り替えた場合は検索キーワードをリセットして page=1 から表示します。
<Select
onchange={(e) => goto(`/?month=${e.currentTarget.value}&page=1`)}
>
まとめ
| ポイント | 内容 |
|---|---|
| 検索の実装場所 | DBクエリではなくJavaScriptの filter で統一 |
| 集計の計算対象 | 絞り込み後の全件(ページネーション前)から計算 |
| ページ切り替え時 | search パラメータを URL に引き継ぐ |
| 月切り替え時 | 検索キーワードをリセットして page=1 から表示 |