• #Vue.js
  • #UI改善
  • #tax-assistant
  • #テーブルレイアウト
  • #状態管理
開発tax-assistantメモ

Vue.jsで重複チェック画面をテーブル形式に刷新

tax-assistantの重複レシートチェック画面(DuplicateView.vue)を大幅に改善した。

改善の背景

従来のカード形式UIでは以下の問題があった。

  • 同じhash_idを持つレシート群の関連性が視覚的にわかりにくい
  • 一覧性が低く、多数の重複候補がある場合にスクロールが多発
  • 画像プレビューが別画面遷移で、確認作業が煩雑

実装した機能

1. テーブル形式への変更

カード形式からテーブル形式に変更し、一覧性を向上させた。

Before(カード形式)

  • 各レシートが個別カードで表示
  • グループの境界が不明確
  • 縦に長いスクロールが必要

After(テーブル形式)

  • 行ごとにレシート情報を表示
  • グループヘッダーで関連レシートを明示
  • コンパクトな表示で一覧性向上

2. グループ単位の表示

同じhash_idを持つレシートをグループ化して表示する機能を実装した。

// hash_idでレシートをグループ化
const groupedReceipts = computed(() => {
  const groups = new Map<string, Receipt[]>();
  receipts.value.forEach(receipt => {
    const hashId = receipt.hash_id;
    if (!groups.has(hashId)) {
      groups.set(hashId, []);
    }
    groups.get(hashId)!.push(receipt);
  });
  return groups;
});

グループ選択時にはそのグループ内の全レシートが強調表示され、関連性が一目でわかる。

3. ImageNavigationPanelを使った画像プレビュー

別コンポーネントとして実装済みのImageNavigationPanelを活用し、画面遷移なしで画像プレビューを実現した。

  • グループ選択時に関連画像を右パネルに表示
  • 矢印キーで画像を切り替え可能
  • ズームイン・ズームアウト対応

4. 帳票リンクから読取一覧タブへの遷移

帳票リンクをクリックすると、読取一覧タブに遷移して該当レシートを選択状態にする機能を追加した。

const navigateToReceipt = (receiptId: string) => {
  router.push({
    path: '/receipts',
    query: {
      tab: 'scan-list',
      receiptId
    }
  });
};

5. URLクエリパラメータでの状態管理

選択中のグループIDをURLクエリパラメータ(dupGroup)で管理し、ブラウザの戻る・進むボタンやブックマークに対応した。

// URLからグループIDを復元
const route = useRoute();
const selectedGroupId = computed(() => route.query.dupGroup as string || null);

// グループ選択時にURLを更新
const selectGroup = (groupId: string) => {
  router.push({
    query: { ...route.query, dupGroup: groupId }
  });
};

6. タブ切り替え時の自動選択

重複チェックタブに切り替えた際、自動で最初のグループを選択する処理を追加した。

watch(() => activeTab.value, (newTab) => {
  if (newTab === 'duplicate-check' && !selectedGroupId.value) {
    const firstGroup = Array.from(groupedReceipts.value.keys())[0];
    if (firstGroup) {
      selectGroup(firstGroup);
    }
  }
});

7. 4カラムレイアウト

重複レシートの状態を4つのカラムで管理する。

カラム状態説明
pending未処理確認待ちの重複候補
in_progress処理中確認作業中
completed完了重複確認済み
archivedアーカイブ対応不要として保管

ドラッグ&ドロップでカラム間を移動できる。

技術的なポイント

computed内でのMap使用

Vue 3のリアクティビティでMapを使用する場合、computedで新しいMapインスタンスを返すことで変更検知が正しく動作する。

URLクエリパラメータの同期

useRoute()useRouter()を組み合わせ、URLとコンポーネント状態を双方向に同期させた。router.pushではなくrouter.replaceを使うことで、履歴を汚さない選択肢もある。

今後の改善予定

  • グループ内レシートの一括削除機能
  • 重複判定ロジックの精度向上
  • キーボードショートカットの拡充