RELATE — 要件定義書 兼 Claude Code 実装指示書
版数 1.0(DBレス構成)/ 作成日 2026-08-13 / 発行 ソウゾウ合同会社 用途 「爆速アプリ開発」LP 実演デモ / 顧客管理システム編
このドキュメントの使い方(Claude Code へ)
これ1枚が、規約・仕様・タスクのすべて。リポジトリ直下に CLAUDE.md として置き、毎回読み込むこと。
- 実装は「11. 実装タスク」の順に進める。フェーズを飛ばさない。
- 各タスクの括弧内は要件ID。着手前に該当章を読むこと。
- 仕様がここに書かれていない場合は、推測で実装せず質問する。
- スコープ外(3章 Won't have)は、思いついても実装しない。
- 「デモだから」を理由に品質を落とす判断はしない。
姉妹プロジェクト HIREBASE との関係 同じ「爆速アプリ開発」LPに並べるデモ。両方を見た人に「同じテンプレで作っている」と思われた時点で失敗。 HIREBASE は求職者に読ませる消費者向けサイト(明朝体・余白・静けさ)。 RELATE は毎日8時間触る業務システム(ゴシック・高密度・キーボード操作)。 8章のデザイン要件は意図的に別方向に振っている。トークンを流用しないこと。
目次
- プロジェクト概要
- アーキテクチャ方針 — データベースを持たない
- スコープ定義
- ロールとデモ切替
- 画面一覧とユーザーフロー
- 機能要件
- データ設計
- デザイン要件
- 技術要件・ディレクトリ構成
- 非機能要件
- 実装タスク
- 受入基準
1. プロジェクト概要
背景と目的
「爆速アプリ開発」LPの見込み客の多くは、Excelとスプレッドシートで顧客・商談を管理している中小企業。SalesforceやHubSpotは高機能すぎて高い、しかしExcelでは限界がある——という層が中心。
この層に対して「あなたの業務に合わせた顧客管理システムを、この価格・この期間で作れます」を示すデモを用意する。求人サイト(HIREBASE)が消費者向けサービスの実装力を示すのに対し、RELATE は業務システムの実装力を示す。
| 目的 | 内容 |
|---|---|
| 業務システムの実装力の証明 | 一覧・検索・一括操作・権限・帳票・分析まで、実運用に耐える機能密度を示す |
| 操作効率の設計力の証明 | キーボード操作、一括更新、保存ビュー、コマンドパレット。「毎日使う人の速度」を設計できることを示す |
| カスタマイズ性の証明 | カスタム項目・パイプライン段階の編集。「うちの業務に合わせられるのか」という最大の懸念に答える |
| 横展開の資産化 | 業種特化版(不動産/建設/士業/介護)のベースとして再利用できる構造にする |
プロダクト定義
| 項目 | 内容 |
|---|---|
| プロダクト名 | RELATE(リレート)※仮称 |
| 一言定義 | Excelをやめられる、中小企業のための顧客・商談管理システム |
| 提供形態 | レスポンシブWebアプリ。フロントエンド完結(サーバー側の永続化なし) |
| 主戦場 | デスクトップ(業務時間中の利用が前提)。モバイルは外出先での確認・活動記録に最適化 |
| 想定利用者 | LP訪問者。誰でも即座に、登録なしで全ロールを体験できる |
プロダクトコンセプト
「入力の手間より、次に何をすべきかを返す。」
多くのCRMは入力項目が多く、現場が入力しなくなって死ぬ。RELATE は入力を最小にし、代わりに「今日やるべきこと」を毎朝返すことを設計の中心に置く。放置されている商談、期限切れのタスク、久しく接触のない顧客を、システム側から提示する。
デモとしての成功条件
- 触った瞬間に「業務で使えそう」と分かる — 空の画面が1つもなく、リアルな商談データが入っている
- 速い — 一覧の絞り込み、ステータス変更、活動記録が、待たされずに終わる
- ロールで見え方が変わる — 営業担当は自分の案件、マネージャーはチーム全体。権限設計ができていることを示す
- カスタマイズできる — 項目とパイプライン段階を画面上で足せる
- リセットできる — 誰が触った後でも、初期状態に戻せる
2. アーキテクチャ方針 — データベースを持たない
基本方針
バックエンドとデータベースを持たない。すべてブラウザ内で完結させる。
| 一般的な構成 | 本プロジェクト |
|---|---|
| PostgreSQL / Supabase | シードデータ(TypeScript)+ ブラウザ内ストア |
| 認証・SSO | デモ用ロール切替(ワンクリック) |
| REST / GraphQL API | ストア上の同期的な操作 |
| メール送信・カレンダー連携 | 送信内容のプレビュー表示で再現 |
| PDF生成(サーバー側) | ブラウザ内で生成し、実際にダウンロードできる |
| CSVインポート/エクスポート | 実際に動く。 ブラウザ内でパース/生成する |
なぜこの構成にするか
- LPデモとして最適 — 訪問者は登録もログインもせずに、全機能を即座に触れる
- 顧客データを一切預からない — 入力されたデータはブラウザから出ない。デモに自社の顧客リストをCSVで投入して試すこともできる(これは営業上かなり強い)
- 開発速度 — 認証・権限のサーバー実装・マイグレーションが不要になり、UIと操作効率の作り込みに全時間を投下できる
- 運用コストゼロ — Vercel の静的配信のみ
差し替え可能性の担保(重要)
データアクセスは必ず lib/repo/ のリポジトリ層を経由すること。 コンポーネントからストアを直接触らない。
[ コンポーネント ]
↓ 呼ぶのはこの層だけ
[ lib/repo/*.ts ] ← 全メソッドを async にしておく
↓
[ lib/store/*.ts ] ← Zustand + persist(localStorage)
↓
[ lib/seed/*.ts ] ← 初期データ
リポジトリの各メソッドは、中身が同期処理でも async で定義し、await で呼ぶ。実案件へ転用する際、リポジトリの実装だけをAPI呼び出しに差し替えれば、UI層は一行も変更せずに済む。
// lib/repo/deals.ts — 中身は同期でも async にする
export async function findDeals(q: DealQuery): Promise<Result<Deal[]>> {
await delay()
const deals = useDataStore.getState().deals
return { ok: true, data: applyScope(filterAndSort(deals, q)) } // 将来ここがAPI呼び出しになる
}
永続化の範囲
| 対象 | 挙動 |
|---|---|
| シードデータ(顧客・商談・活動履歴) | 初期投入。以降は編集可能 |
| ユーザーの操作結果(登録・編集・ステータス変更・活動記録・タスク) | localStorage に保存。リロードしても保持される |
| CSVインポートしたデータ | localStorage に保存される |
| 添付ファイル | セッション内のみ(Object URL)。リロードでファイル名のみ残る |
| リセット | ヘッダーの「デモをリセット」で localStorage をクリアし初期状態へ |
擬似的な非同期を必ず入れること。 リポジトリの各操作に 150〜400ms のディレイを挟み、ローディング状態を実際に見せる。
HIREBASE との差: 業務システムは「速さ」が価値なので、ディレイは HIREBASE(200〜600ms)より短く設定する。一覧の絞り込みなど体感速度が重要な操作は 100ms 以下にする。
3. スコープ定義
顧客・担当者管理
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-A01 | 顧客(企業)一覧 | テーブル表示、絞り込み、並び替え、列カスタマイズ、保存ビュー | Must |
| F-A02 | 顧客詳細 | 基本情報、担当者一覧、商談一覧、活動タイムライン、関連ファイル | Must |
| F-A03 | 顧客の登録・編集 | インライン編集とモーダル編集の両対応 | Must |
| F-A04 | 担当者(コンタクト)管理 | 顧客に紐づく人物情報。役職、連絡先、決裁権の有無 | Must |
| F-A05 | 重複検出 | 会社名・電話番号の類似から重複候補を検出し、統合(マージ)できる | Should |
| F-A06 | CSVインポート | 列マッピングUI、プレビュー、エラー行の表示、部分取り込み | Must |
| F-A07 | CSVエクスポート | 現在の絞り込み条件・表示列でエクスポート | Must |
| F-A08 | 一括操作 | チェックボックス選択 → 担当者変更/タグ付与/削除を一括実行 | Must |
| F-A09 | タグ管理 | 顧客・商談へのタグ付与、タグでの絞り込み | Should |
商談(案件)管理
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-D01 | パイプライン(カンバン) | 段階別の列表示、D&D移動、列ごとの合計金額 | Must |
| F-D02 | 商談一覧(テーブル) | カンバンと同じデータの表形式表示。切替可能 | Must |
| F-D03 | 商談詳細 | 金額、確度、予定クローズ日、段階、担当、関連顧客、活動履歴、タスク | Must |
| F-D04 | 商談の登録・編集 | 顧客からの新規作成、複製 | Must |
| F-D05 | 段階変更 | D&D またはキーボード。段階ごとの必須項目チェック | Must |
| F-D06 | 受注・失注処理 | 受注理由/失注理由の記録(定型選択 + 自由記述) | Must |
| F-D07 | 停滞検出 | 一定期間動きのない商談を自動でフラグ表示 | Must |
| F-D08 | パイプライン段階のカスタマイズ | 段階の追加・名称変更・並び替え・確度の初期値設定 | Should |
| F-D09 | 商談の予実 | 予定金額と受注金額、月次の着地見込み | Should |
活動・タスク
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-T01 | 活動記録 | 訪問/電話/メール/打合せ/その他。日時、相手、内容、次アクション | Must |
| F-T02 | 活動タイムライン | 顧客・商談ごとの時系列表示。種別フィルタ | Must |
| F-T03 | クイック活動記録 | 一覧画面から離れずに、モーダルで活動を記録できる | Must |
| F-T04 | タスク管理 | 期限、担当、関連レコード、完了チェック | Must |
| F-T05 | 今日のタスク | ダッシュボード上の当日・期限超過タスク一覧 | Must |
| F-T06 | 活動からのタスク自動生成 | 活動記録時に「次アクション」を入力すると、タスクが生成される | Should |
| F-T07 | メール送信 | テンプレート選択、差し込み変数、送信内容のプレビュー、活動履歴への自動記録 | Should |
見積・売上
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-Q01 | 商品マスタ | 商品名、単価、原価、単位、カテゴリ | Should |
| F-Q02 | 見積作成 | 明細行の追加・並び替え、数量・単価・値引き、小計/消費税/合計の自動計算 | Should |
| F-Q03 | 見積PDF出力 | 自社ロゴ入りの見積書PDFを生成し、実際にダウンロードできる | Should |
| F-Q04 | 見積の版管理 | 改訂版の作成、版ごとの履歴 | Could |
| F-Q05 | 受注時の売上計上 | 受注した商談の金額を、月次の売上実績に反映 | Should |
分析・管理
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-R01 | ダッシュボード | 今日のタスク、要対応商談、今月の着地見込み、活動サマリ | Must |
| F-R02 | パイプライン分析 | 段階別の件数・金額、転換率ファネル、平均滞留日数 | Should |
| F-R03 | 担当者別実績 | 個人別の商談数・受注額・活動件数。マネージャーのみ閲覧 | Should |
| F-R04 | 予実管理 | 月次目標と実績の対比、着地見込み | Should |
| F-R05 | 失注分析 | 失注理由の集計、段階別の失注率 | Could |
| F-R06 | カスタム項目管理 | 顧客・商談に独自項目(テキスト/数値/日付/選択/チェック)を追加できる | Should |
| F-R07 | ユーザー・チーム管理 | メンバー一覧、ロール変更、チーム編成 | Should |
| F-R08 | 操作ログ | 誰が・いつ・何を変更したかの履歴 | Could |
共通・基盤
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-S01 | ロール切替 | 営業担当/マネージャー/管理者をワンクリックで切替。切替バーは常時表示 | Must |
| F-S02 | グローバル検索 | 顧客・担当者・商談を横断検索。ヘッダーから常時アクセス | Must |
| F-S03 | コマンドパレット | ⌘K / Ctrl+K で検索・画面遷移・新規作成を呼び出す |
Must |
| F-S04 | 通知センター | 担当割当、期限接近、停滞アラート、メンション | Should |
| F-S05 | 保存ビュー | 絞り込み条件 + 表示列 + 並び順を名前を付けて保存。個人/共有を選択 | Must |
| F-S06 | デモリセット | localStorage をクリアして初期状態に戻す | Must |
| F-S07 | ガイドツアー | 初回訪問時に「何を試せるか」を3ステップで案内 | Should |
対象外(Won't have)
| 項目 | 理由 |
|---|---|
| データベース・バックエンドAPI | 2章の方針に基づく |
| 本物の認証・SSO | ロール切替で代替 |
| 実際のメール送受信・メールボックス同期 | プレビュー表示で再現 |
| カレンダー連携(Google / Outlook) | 日程は内部で保持。ics ダウンロードのみ対応 |
| 電話・SFA連携(CTI)、名刺スキャンOCR | 対象外 |
| ワークフロー自動化のビルダー | 停滞検出など固定ルールのみ実装 |
| 会計・請求書発行・入金消込 | 見積までを対象とする |
| モバイルネイティブアプリ | レスポンシブWebで対応 |
| 複数ブラウザ間でのデータ共有 | 各ブラウザで独立。デモとして問題ない |
スコープリスク: CRMは「あれもこれも」が最も起きやすい領域。特にワークフロー自動化ビルダーと帳票の自由レイアウト編集は、単体で数週間かかる。要望が出た場合は別フェーズとして見積り、この文書のスコープは変えない。
4. ロールとデモ切替
ロール定義
| ロールID | 名称 | 見えるデータ | 特徴的な権限 |
|---|---|---|---|
sales |
営業担当 | 自分が担当する顧客・商談のみ | 自分のレコードの作成・編集。他人の商談は閲覧のみ |
manager |
マネージャー | 自チーム全員のデータ | 担当者の割当変更、チーム実績の閲覧、共有ビューの作成 |
admin |
管理者 | 全社のデータ | カスタム項目・パイプライン段階の編集、ユーザー管理、操作ログ |
認証は行わない。ロールはストアの状態として保持し、切替バーから即座に変更できる。
データスコープ(デモの見せ場)
ロールによって同じ画面の表示件数が変わることを、明確に体験させる。
- 営業担当(田中)でパイプラインを開く → 自分の商談 18件
- マネージャーに切替 → 同じ画面がチーム全体の 62件になり、担当者フィルタが出現する
- 管理者に切替 → 全社 140件。設定メニューが出現する
スコープの適用は lib/repo/_scope.ts に集約し、全リポジトリメソッドが必ず通す。コンポーネント側でフィルタしない(実案件でRLSに置き換わる箇所であるため)。
デモ切替バー(F-S01)
画面上部に常時表示する固定バー。
- 現在のロールと担当者名の表示、3ロールの切替
- 営業担当ロール時:3名のメンバーから選択(見え方の違いを体験できる)
- 「デモをリセット」ボタン(確認ダイアログ付き)
- 「これはデモです」の明示
デザイン上の扱い: プロダクト本体のUIとは意図的に別扱い(ダークな帯 + 小さめのタイポ)にし、「アプリの外側にある操作パネル」であることを視覚的に区別する。
ロールを跨ぐ体験(必ず動くようにする)
- 営業担当で商談を「提案中」→「最終交渉」に移動 → マネージャーに切替 → ダッシュボードの着地見込みが増えている
- マネージャーが商談の担当者を田中から鈴木に変更 → 田中で見ると一覧から消え、鈴木で見ると現れる
- 営業担当が活動を記録し「次アクション」を入力 → タスクが自動生成され、今日のタスクに並ぶ
- 管理者がカスタム項目「導入時期」を追加 → 営業担当の商談詳細に、その項目が出現する
5. 画面一覧とユーザーフロー
全32画面。画面IDはディレクトリ構成と 1:1 で対応させる。
メイン画面
| 画面ID | 画面名 | パス | 主要要素 |
|---|---|---|---|
| SC-001 | ダッシュボード | / |
今日のタスク、期限超過、要対応商談(停滞)、今月の着地見込み、最近の活動 |
| SC-010 | 顧客一覧 | /accounts |
テーブル、絞り込み、列カスタマイズ、保存ビュー、一括操作、CSV入出力 |
| SC-011 | 顧客詳細 | /accounts/[id] |
基本情報(インライン編集)、担当者、商談、活動タイムライン、ファイル |
| SC-012 | 顧客の新規作成 | /accounts/new |
フォーム。重複候補を入力中に検出 |
| SC-013 | 重複マージ | /accounts/merge |
重複候補の並列比較、残す値を項目ごとに選択 |
| SC-014 | CSVインポート | /accounts/import |
ファイル選択 → 列マッピング → プレビュー → 取込結果 |
| SC-020 | 担当者一覧 | /contacts |
テーブル、絞り込み、所属企業へのリンク |
| SC-021 | 担当者詳細 | /contacts/[id] |
基本情報、関連商談、活動履歴 |
| SC-030 | パイプライン(カンバン) | /deals |
段階別列、D&D、列ごとの件数・合計金額、絞り込み |
| SC-031 | 商談一覧(テーブル) | /deals?view=table |
カンバンと同データの表形式。切替ボタンで往復 |
| SC-032 | 商談詳細 | /deals/[id] |
概要、段階バー、活動タイムライン、タスク、見積、関連情報 |
| SC-033 | 商談の新規作成 | /deals/new |
フォーム。顧客詳細からの遷移時は顧客を自動セット |
| SC-034 | 受注・失注登録 | モーダル | 理由の定型選択 + 自由記述、受注日、最終金額 |
| SC-040 | 活動一覧 | /activities |
全活動のタイムライン。種別・担当・期間で絞り込み |
| SC-041 | 活動記録 | モーダル | 種別、日時、相手、内容、次アクション(タスク自動生成) |
| SC-050 | タスク一覧 | /tasks |
期限別グルーピング(超過/今日/今週/今後)、担当者フィルタ |
| SC-060 | 見積一覧 | /quotes |
ステータス別、金額、有効期限 |
| SC-061 | 見積作成・編集 | /quotes/[id] |
明細行の追加・並び替え、自動計算、プレビュー、PDF出力 |
| SC-062 | 見積プレビュー | モーダル | 印刷レイアウトの表示 → PDFダウンロード |
分析画面
| 画面ID | 画面名 | パス | 主要要素 |
|---|---|---|---|
| SC-100 | パイプライン分析 | /reports/pipeline |
段階別件数・金額、転換率ファネル、平均滞留日数 |
| SC-101 | 担当者別実績 | /reports/members |
個人別の商談数・受注額・活動件数(マネージャー以上) |
| SC-102 | 予実管理 | /reports/forecast |
月次目標と実績、着地見込み、達成率 |
| SC-103 | 失注分析 | /reports/lost |
失注理由の集計、段階別失注率 |
| SC-104 | 活動分析 | /reports/activities |
種別別・担当者別の活動件数推移 |
設定画面(管理者のみ)
| 画面ID | 画面名 | パス | 主要要素 |
|---|---|---|---|
| SC-200 | 設定トップ | /settings |
設定項目の一覧 |
| SC-201 | カスタム項目 | /settings/fields |
対象(顧客/商談)、型、選択肢、必須、並び順 |
| SC-202 | パイプライン段階 | /settings/stages |
段階の追加・名称変更・並び替え・確度初期値 |
| SC-203 | 商品マスタ | /settings/products |
商品の CRUD、カテゴリ |
| SC-204 | ユーザー・チーム | /settings/members |
メンバー一覧、ロール変更、チーム編成 |
| SC-205 | タグ管理 | /settings/tags |
タグの CRUD、色設定、使用件数 |
| SC-206 | メールテンプレート | /settings/templates |
テンプレートの CRUD、差し込み変数 |
| SC-207 | 操作ログ | /settings/logs |
変更履歴の検索 |
| SC-208 | 目標設定 | /settings/targets |
月次目標の設定(全社/チーム/個人) |
主要フロー
フローA:新規顧客の獲得から受注まで
SC-001 ダッシュボード ── コマンドパレット ⌘K →「顧客を作成」
↓
SC-012 顧客の新規作成 ── 会社名入力中に重複候補を検出して警告
↓
顧客登録 → SC-011 顧客詳細
↓
「商談を作成」→ SC-033(顧客は自動セット済み)
商談名/金額/確度/予定クローズ日/段階「初回接触」
↓
SC-041 活動記録(クイック入力)
種別「訪問」/内容/次アクション「見積を送付する(3日後)」
↓ ★ 次アクションからタスクが自動生成される
SC-050 タスク一覧に「見積を送付する」が期限付きで並ぶ
↓
SC-061 見積作成 ── 商品マスタから明細追加 → 自動計算 → PDFダウンロード
↓
SC-030 パイプライン ── カードを「提案中」→「最終交渉」へD&D
↓
SC-034 受注登録 ── 受注理由・受注日・最終金額
↓
SC-102 予実管理に売上として計上される
フローB:マネージャーの週次パイプラインレビュー
【ロール切替:マネージャー】
SC-001 ダッシュボード ── 「要対応商談 7件」(14日以上動きなし)
↓
SC-030 パイプライン ── 停滞商談が視覚的にフラグ表示されている
↓
停滞している商談を開く → SC-032 商談詳細
最終活動日、滞留日数、これまでの経緯を確認
↓
担当者にタスクを割り当てる(担当:田中、期限:今週金曜)
↓
SC-101 担当者別実績 ── メンバーごとの進捗を確認
↓
SC-102 予実管理 ── 今月の着地見込みと目標のギャップを確認
↓
【ロール切替:営業担当(田中)】
SC-050 タスク一覧に、マネージャーから割り当てられたタスクが並んでいる
フローC:Excelからの移行(CSVインポート)
SC-010 顧客一覧 ──「インポート」
↓
SC-014 Step1 ファイル選択(CSVをドロップ)
↓
Step2 列マッピング
CSVの列 → RELATEの項目 を対応付け。自動推測 + 手動修正
未対応の列は「取り込まない」または「カスタム項目として追加」
↓
Step3 プレビュー
先頭10行の変換結果を表示
エラー行(必須項目の欠落・形式不正)を件数と理由付きで表示
↓
Step4 取込実行
「エラー行をスキップして取り込む」/「中止する」を選択
↓
結果サマリ:成功 142件 / スキップ 8件(理由別内訳)
↓
SC-010 顧客一覧に反映。取り込んだレコードに「今回インポート」タグが自動付与される
6. 機能要件
各要件は判定可能な粒度で記述している。テスト項目と 1:1 で対応させる。
6.1 ロール・デモ基盤
| ID | 要件 | 優先 |
|---|---|---|
| FR-001 | 画面上部の固定バーから、3ロール(営業担当/マネージャー/管理者)をワンクリックで切り替えられること | P1 |
| FR-002 | 営業担当ロールでは、3名のメンバーから操作主体を選択できること | P1 |
| FR-003 | 「デモをリセット」で localStorage をクリアし、確認ダイアログを経て初期状態に戻ること | P1 |
| FR-004 | 操作結果はブラウザをリロードしても保持されること | P1 |
| FR-005 | データスコープは lib/repo/_scope.ts に集約し、全リポジトリメソッドが必ず通すこと。コンポーネント側でロールによるフィルタを行わないこと |
P1 |
| FR-006 | 営業担当は自分が担当するレコードのみ、マネージャーは自チーム、管理者は全社のレコードが見えること | P1 |
| FR-007 | 権限外の操作(営業担当が他人の商談を編集する等)を試みた場合、ボタンを非活性にし、理由をツールチップで示すこと。実行してからエラーを出さないこと | P1 |
| FR-008 | ロール権限外の画面(営業担当で /settings)にアクセスした場合、案内画面からロール切替できること。素の404を出さないこと |
P1 |
| FR-009 | 初回訪問時に3ステップのガイドツアーを表示し、「今後表示しない」を選択できること | P2 |
| FR-010 | リポジトリ層の各操作は 150〜400ms の擬似ディレイを持つこと。ただし一覧の絞り込み・並び替えは 100ms 以下とすること | P1 |
6.2 一覧・検索・ビュー(業務システムの中核)
| ID | 要件 | 優先 |
|---|---|---|
| FR-101 | 一覧テーブルは、表示する列をユーザーが選択・並び替えできること。設定は保持されること | P1 |
| FR-102 | 各列でソートでき、複数列でのソート(第1キー・第2キー)に対応すること | P2 |
| FR-103 | 絞り込みは項目ごとの条件指定(等しい/含む/以上/以下/期間/空である)に対応すること | P1 |
| FR-104 | 絞り込み条件はURLクエリに反映し、リロード・URL共有で同一結果を再現できること | P1 |
| FR-105 | 適用中の絞り込み条件をチップで一覧表示し、個別に解除できること | P1 |
| FR-106 | 絞り込み条件 + 表示列 + 並び順を「ビュー」として名前を付けて保存できること。個人ビューと共有ビュー(マネージャー以上が作成)を区別すること | P1 |
| FR-107 | 保存ビューはサイドバーに一覧表示し、ワンクリックで切り替えられること。件数バッジを表示すること | P1 |
| FR-108 | 行のチェックボックス選択で一括操作(担当者変更/タグ付与/削除)ができること。全選択・絞り込み結果全件選択に対応すること | P1 |
| FR-109 | 一括操作の実行前に、対象件数と操作内容の確認ダイアログを表示すること | P1 |
| FR-110 | 削除操作は取り消し可能なトースト(5秒間「元に戻す」を表示)とすること | P1 |
| FR-111 | ヘッダーのグローバル検索から、顧客・担当者・商談を横断検索できること。種別ごとにグループ化して結果を表示すること | P1 |
| FR-112 | ⌘K / Ctrl+K でコマンドパレットを開き、検索・画面遷移・新規作成を実行できること |
P1 |
| FR-113 | 一覧の行数が多い場合も描画がもたつかないこと(仮想スクロールを実装すること) | P2 |
| FR-114 | 一覧の行の高さを「標準(40px)/コンパクト(32px)」で切り替えられること | P2 |
| FR-115 | テーブル上でセルを直接編集できること(インライン編集)。Enterで確定、Escで取消、Tabで次のセルへ移動すること | P2 |
6.3 顧客・担当者管理
| ID | 要件 | 優先 |
|---|---|---|
| FR-201 | 顧客に、会社名・フリガナ・業種・従業員数・住所・電話・URL・担当営業・ステータス・タグ・メモ を登録できること | P1 |
| FR-202 | 顧客詳細で、基本情報の各項目をクリックしてその場で編集できること(インライン編集) | P1 |
| FR-203 | 顧客の新規作成時、会社名の入力中に既存レコードとの類似を検出し、重複候補を警告表示すること | P2 |
| FR-204 | 重複候補を並列比較し、項目ごとに残す値を選択してマージできること。マージ後、両者の商談・活動・担当者がすべて統合先に引き継がれること | P2 |
| FR-205 | 担当者(コンタクト)を顧客に複数紐付けられること。氏名・役職・部署・メール・電話・決裁権の有無 を持つこと | P1 |
| FR-206 | 顧客詳細に、担当者・商談・活動履歴・タスク・見積 をタブで表示すること | P1 |
| FR-207 | CSVインポートで、列マッピング(自動推測 + 手動修正)、プレビュー、エラー行の理由表示、部分取込 ができること | P1 |
| FR-208 | インポート時、未対応の列を「取り込まない」または「カスタム項目として新規追加」から選べること | P2 |
| FR-209 | インポート結果を「成功N件/スキップN件(理由別内訳)」で表示すること | P1 |
| FR-210 | インポートしたレコードに、実行日時のタグを自動付与すること | P2 |
| FR-211 | 現在の絞り込み条件・表示列のままCSVエクスポートでき、実際にファイルがダウンロードされること | P1 |
6.4 商談・パイプライン
| ID | 要件 | 優先 |
|---|---|---|
| FR-301 | 商談に、商談名・顧客・担当者・金額・確度・段階・予定クローズ日・獲得経路・商品・メモ を登録できること | P1 |
| FR-302 | パイプラインはカンバン形式で表示し、各列のヘッダーに件数と合計金額を表示すること | P1 |
| FR-303 | カードのD&Dで段階を変更でき、変更が即座に反映されること | P1 |
| FR-304 | 段階変更をキーボードのみでも行えること(カード選択 → 移動先選択) | P1 |
| FR-305 | 段階を移動した際、その段階で必須とされた項目が未入力の場合、モーダルで入力を求めること | P2 |
| FR-306 | 確度は段階に応じた初期値が自動設定され、手動で上書きもできること | P2 |
| FR-307 | カンバンとテーブルを切り替えられること。絞り込み条件は切替後も維持されること | P1 |
| FR-308 | 最終活動日から一定日数(既定14日)動きのない商談を「停滞」として視覚的にフラグ表示すること | P1 |
| FR-309 | 予定クローズ日を過ぎた未クローズ商談を「期限超過」としてフラグ表示すること | P1 |
| FR-310 | 受注・失注の登録時、理由を定型選択(複数可)+ 自由記述で入力できること。失注理由は分析の集計対象とすること | P1 |
| FR-311 | 受注した商談の金額が、予実管理の実績に反映されること | P2 |
| FR-312 | 商談詳細に、段階の進捗バー(現在地と通過済みが分かる)を表示すること | P1 |
| FR-313 | 商談を複製して新規作成できること | P2 |
| FR-314 | 管理者はパイプライン段階を追加・名称変更・並び替え・削除でき、確度の初期値を設定できること。変更は既存の全商談に即座に反映されること | P2 |
6.5 活動・タスク
| ID | 要件 | 優先 |
|---|---|---|
| FR-401 | 活動に、種別(訪問/電話/メール/打合せ/その他)・日時・相手(担当者)・内容・次アクション を記録できること | P1 |
| FR-402 | 一覧画面や詳細画面から遷移せずに、モーダルで活動を記録できること | P1 |
| FR-403 | 活動記録時に「次アクション」と期限を入力すると、タスクが自動生成され、関連レコードに紐づくこと | P1 |
| FR-404 | 顧客詳細・商談詳細に、活動を時系列のタイムラインで表示すること。種別でフィルタできること | P1 |
| FR-405 | タスクに、内容・期限・担当者・関連レコード・完了状態 を持つこと | P1 |
| FR-406 | タスク一覧を「期限超過/今日/今週/今後/完了」でグルーピング表示すること | P1 |
| FR-407 | ダッシュボードに、当日期限と期限超過のタスクを表示すること | P1 |
| FR-408 | タスクの完了はチェックボックスのワンクリックで行え、取り消せること | P1 |
| FR-409 | マネージャーは他メンバーにタスクを割り当てられること。割当時、対象者の通知に配信されること | P2 |
| FR-410 | メールテンプレートを選択し、差し込み変数({{会社名}} {{担当者名}} {{自分の氏名}})を置換して本文を生成できること |
P2 |
| FR-411 | メール送信操作を行うと、実際に送信される内容のプレビューをモーダルで表示し、確定すると活動履歴に「メール」として自動記録されること | P2 |
6.6 見積・商品
| ID | 要件 | 優先 |
|---|---|---|
| FR-501 | 商品マスタに、商品名・単価・原価・単位・カテゴリ を登録できること | P2 |
| FR-502 | 見積の明細行を、商品マスタから選択または手入力で追加でき、ドラッグで並び替えられること | P2 |
| FR-503 | 数量・単価・値引きの入力に応じて、行小計・小計・消費税・合計が即座に再計算されること | P2 |
| FR-504 | 消費税の計算方式(内税/外税、端数処理)を設定でき、計算結果に反映されること | P2 |
| FR-505 | 見積書のプレビューを印刷レイアウトで表示し、実際にPDFをダウンロードできること。自社ロゴ・宛先・件名・有効期限・備考を含むこと | P2 |
| FR-506 | 見積は商談に紐づき、商談詳細から一覧・作成できること | P2 |
| FR-507 | 見積の改訂版を作成でき、版ごとの履歴を保持すること | P3 |
| FR-508 | 見積のステータス(作成中/送付済/承認/失注)を管理できること | P2 |
6.7 分析・設定
| ID | 要件 | 優先 |
|---|---|---|
| FR-601 | ダッシュボードに、今日のタスク・期限超過・停滞商談・今月の着地見込み・最近の活動 を表示すること | P1 |
| FR-602 | ダッシュボードの表示内容がロールによって変わること(営業担当は自分、マネージャーはチーム、管理者は全社) | P1 |
| FR-603 | パイプライン分析で、段階別の件数・金額、段階間の転換率ファネル、平均滞留日数 を表示すること | P2 |
| FR-604 | 担当者別実績で、個人別の商談数・受注額・活動件数を表示すること。営業担当ロールでは非表示とすること | P2 |
| FR-605 | 予実管理で、月次目標と実績、着地見込み(受注済 + 確度加重)、達成率 を表示すること | P2 |
| FR-606 | 失注分析で、失注理由の集計と段階別失注率を表示すること | P3 |
| FR-607 | 全ての分析画面で、期間(今月/先月/四半期/任意)を指定できること | P2 |
| FR-608 | 管理者はカスタム項目を追加できること。型は テキスト/数値/日付/単一選択/複数選択/チェックボックス に対応すること | P2 |
| FR-609 | 追加したカスタム項目が、対象レコードの詳細画面・編集フォーム・一覧の列選択・絞り込み条件に即座に反映されること | P2 |
| FR-610 | 管理者はユーザーのロール変更とチーム編成ができること | P2 |
| FR-611 | レコードの変更履歴(誰が・いつ・どの項目を・何から何に)を操作ログに記録し、検索できること | P3 |
| FR-612 | 通知センターに、担当割当・期限接近・停滞アラート・タスク割当 が配信されること | P2 |
7. データ設計
型定義(lib/types/)
すべての型を先に定義し、これを唯一の真実とする。
// ---- 組織・ユーザー ----
type User = {
id: string
name: string
nameKana: string
email: string
avatarUrl: string
role: 'sales' | 'manager' | 'admin'
teamId: string
monthlyTarget: number // 月次目標(円)
isActive: boolean
}
type Team = { id: string; name: string; managerId: string }
// ---- 顧客・担当者 ----
type Account = {
id: string
name: string
nameKana: string
industryId: string
employeeCount: number | null
postalCode: string
prefCode: string
address: string
phone: string
websiteUrl: string
ownerId: string // 担当営業。スコープ判定の起点
status: 'prospect' | 'active' | 'inactive' | 'lost' // 見込み / 取引中 / 休眠 / 失注
source: string // 獲得経路
tagIds: string[]
note: string
customFields: Record<string, CustomValue>
lastActivityAt: string | null // 活動記録時に自動更新
createdAt: string
updatedAt: string
}
type Contact = {
id: string
accountId: string
lastName: string; firstName: string
lastNameKana: string; firstNameKana: string
department: string
title: string // 役職
email: string
phone: string
mobile: string
isDecisionMaker: boolean // 決裁権の有無
note: string
createdAt: string
}
// ---- 商談 ----
type Stage = {
id: string
name: string // 初回接触 / ヒアリング / 提案中 / 最終交渉 / 受注 / 失注
sortOrder: number
defaultProbability: number // 確度の初期値(%)
requiredFieldIds: string[] // この段階へ移動する際に必須となる項目
type: 'open' | 'won' | 'lost'
}
type Deal = {
id: string
name: string
accountId: string
contactIds: string[]
ownerId: string
amount: number
probability: number // 0-100
stageId: string
expectedCloseDate: string
closedAt?: string
closeReason?: { codes: string[]; note: string } // 受注理由 / 失注理由
source: string
productIds: string[]
tagIds: string[]
note: string
customFields: Record<string, CustomValue>
lastActivityAt: string | null
createdAt: string
updatedAt: string
}
// 派生値。ストアには保存せず、リポジトリで算出する
type DealComputed = Deal & {
stagnantDays: number // 最終活動からの経過日数
isStagnant: boolean // 既定14日以上(FR-308)
isOverdue: boolean // 予定クローズ日超過(FR-309)
weightedAmount: number // amount * probability / 100
}
// ---- 活動・タスク ----
type Activity = {
id: string
type: 'visit' | 'call' | 'email' | 'meeting' | 'other'
accountId: string
dealId?: string
contactId?: string
ownerId: string
occurredAt: string
subject: string
body: string
nextAction?: string // 入力するとタスクが自動生成される(FR-403)
emailPreview?: { to: string; subject: string; html: string } // メール送信時(FR-411)
attachments: { name: string; size: number; objectUrl: string }[]
createdAt: string
}
type Task = {
id: string
title: string
dueDate: string
ownerId: string
assignedById?: string // 他者から割り当てられた場合
accountId?: string
dealId?: string
activityId?: string // 自動生成元
isDone: boolean
doneAt?: string
createdAt: string
}
// ---- 見積・商品 ----
type Product = { id: string; name: string; categoryId: string; unitPrice: number; cost: number; unit: string; isActive: boolean }
type Quote = {
id: string
quoteNumber: string // 見積番号(自動採番)
dealId: string
accountId: string
version: number
title: string // 件名
issuedDate: string
validUntil: string
items: QuoteItem[]
taxMode: 'inclusive' | 'exclusive'
taxRate: number // 0.10
roundingMode: 'floor' | 'ceil' | 'round'
note: string
status: 'draft' | 'sent' | 'accepted' | 'rejected'
createdAt: string
}
type QuoteItem = {
id: string
productId?: string
name: string
quantity: number
unit: string
unitPrice: number
discountRate: number // 0-1
sortOrder: number
}
// 小計・税・合計はストアに保存しない。lib/calc/quote.ts で常に算出する(FR-503)
// ---- カスタム項目・ビュー ----
type CustomField = {
id: string
target: 'account' | 'deal'
label: string
type: 'text' | 'number' | 'date' | 'select' | 'multiselect' | 'checkbox'
options?: string[]
isRequired: boolean
sortOrder: number
}
type CustomValue = string | number | boolean | string[] | null
type SavedView = {
id: string
name: string
target: 'account' | 'contact' | 'deal' | 'task'
ownerId: string
isShared: boolean // マネージャー以上のみ true にできる
filters: Filter[]
visibleColumns: string[]
sort: { field: string; dir: 'asc' | 'desc' }[]
createdAt: string
}
type Filter = {
field: string
operator: 'eq' | 'contains' | 'gte' | 'lte' | 'between' | 'in' | 'isEmpty' | 'isNotEmpty'
value: unknown
}
// ---- その他 ----
type Tag = { id: string; name: string; color: string }
type Notification = {
id: string
targetUserId: string
type: 'assigned' | 'task_assigned' | 'due_soon' | 'stagnant' | 'deal_won' | 'deal_lost'
title: string; body: string; link: string
readAt?: string
createdAt: string
}
type AuditLog = {
id: string
actorId: string
action: 'create' | 'update' | 'delete' | 'merge' | 'import'
targetType: 'account' | 'contact' | 'deal' | 'task' | 'quote' | 'setting'
targetId: string
changes: { field: string; before: unknown; after: unknown }[]
createdAt: string
}
type MonthlyTarget = { id: string; yearMonth: string; scope: 'company' | 'team' | 'user'; scopeId: string; amount: number }
シードデータ(lib/seed/)
| ファイル | 内容 | 件数 |
|---|---|---|
masters.ts |
業種30、都道府県47、獲得経路8、タグ12、失注理由10、受注理由6 | — |
users.ts |
ユーザー(営業6・マネージャー2・管理者1)、チーム2 | 9名 |
stages.ts |
パイプライン段階6(初回接触/ヒアリング/提案中/最終交渉/受注/失注) | 6件 |
accounts.ts |
架空企業。ステータス・業種・規模・担当を分散配置 | 140社 |
contacts.ts |
顧客に1〜4名ずつ紐付け | 320名 |
deals.ts |
商談(進行中90 / 受注35 / 失注15)。停滞・期限超過を意図的に含める | 140件 |
activities.ts |
活動履歴(過去6ヶ月分、自然な頻度で分散) | 900件 |
tasks.ts |
タスク(期限超過8/今日6/今週12/今後20/完了40) | 86件 |
products.ts |
商品マスタ | 24件 |
quotes.ts |
見積(各ステータスに分散) | 30件 |
targets.ts |
月次目標(全社/チーム/個人、過去6ヶ月+今月) | — |
シード作成のルール(品質を左右する)
- 実在企業名・実在人名を使わない。 架空のものを使う
- 商談名・活動内容をテンプレの繰り返しにしない。 業種・段階に応じて書き分ける。「〇〇の件」ばかりだと一瞬で作り物だと分かる
- 金額の分布に偏りを持たせる(小口が多く、大口が少ない)。均等分布は不自然
- 日付は現在日時からの相対で生成する。固定日付を埋め込まない
- 活動の頻度に濃淡をつける。よく訪問している顧客と、半年放置の顧客が混在すること
- 停滞商談・期限超過タスク・目標未達の担当者を意図的に配置する。 全部が順調だと、ダッシュボードのアラート機能が動いて見えない
- 受注/失注の理由を分散させ、失注分析のグラフが意味を持つようにする
ストアとリポジトリ
lib/
├── types/ # 上記の型定義
├── seed/ # シードデータ
├── store/
│ ├── session.ts # 現在のロール、操作中のユーザー
│ ├── data.ts # accounts / contacts / deals / activities / tasks / quotes ...
│ ├── settings.ts # customFields / stages / tags / products / targets
│ └── ui.ts # 表示密度、ガイドツアー表示済みフラグ
└── repo/ # ★ コンポーネントが触るのはここだけ
├── _delay.ts # 擬似ディレイ
├── _scope.ts # ★ ロールによるデータスコープ。全メソッドが通す
├── accounts.ts contacts.ts deals.ts
├── activities.ts tasks.ts quotes.ts
├── views.ts settings.ts notifications.ts reports.ts
リポジトリ層の規約(厳守)
- 全メソッドを
asyncで定義する。中身が同期でも例外なく - 戻り値は
{ ok: true; data: T } | { ok: false; error: string }に統一する - コンポーネントから Zustand ストアを直接参照しない。 読み取りも書き込みもリポジトリ経由
- スコープ適用(
_scope.ts)を全メソッドが必ず通す。 ここが実案件でRLSに置き換わる箇所(FR-005) - 派生値(
stagnantDays、weightedAmount、見積の合計)はストアに保存せず、リポジトリ/計算モジュールで都度算出する
// lib/repo/_scope.ts — 実案件では RLS に置き換わる箇所
export function scopeAccounts(accounts: Account[]): Account[] {
const { role, userId, teamId } = useSessionStore.getState()
if (role === 'admin') return accounts
if (role === 'manager') {
const memberIds = getTeamMemberIds(teamId)
return accounts.filter(a => memberIds.includes(a.ownerId))
}
return accounts.filter(a => a.ownerId === userId) // sales
}
8. デザイン要件
アートディレクション
「一望できる作業台」
RELATE は毎日8時間開かれる業務システム。読ませる画面ではなく、比べて・選んで・動かす画面。したがって設計の主眼は「一画面に必要な情報がどれだけ収まるか」と「手がどれだけ止まらないか」に置く。
- 数字が主役。 金額・件数・確度・日数を等幅数字で揃え、桁を目で比較できるようにする
- 密度を上げる。 余白でゆったり見せるのではなく、罫線と整列で情報量を担保する
- 色は警告のためにある。 通常状態はほぼ無彩色。停滞・期限超過・目標未達だけが色を持つ
- 手を止めない。 キーボード操作、インライン編集、コマンドパレット、一括操作
HIREBASE との差別化(重要) HIREBASE:明朝体・広い余白・静けさ・読ませる。 RELATE:ゴシック・高密度・等幅数字・操作させる。 トークンを流用しない。並べて見せたときに別のプロダクトに見えること自体が、LPでの説得材料になる。
カラートークン
app/globals.css に CSS 変数で定義し、tailwind.config.ts の theme から参照する。直書きの色指定を禁止する。
:root {
--ink-900: #16181D; /* 見出し・本文・数値 */
--ink-600: #545A66; /* 補助テキスト */
--ink-400: #8A909C; /* ラベル・非活性・罫線上のテキスト */
--brand-700: #23407A; /* 主要CTA・選択状態 */
--brand-500: #35589B; /* ホバー */
--brand-50: #EBF0F9; /* 選択行・アクティブなビュー */
--surface: #F6F6F4; /* ページ背景 */
--card: #FFFFFF; /* テーブル・カード背景 */
--border: #E3E2DE; /* 罫線 */
--border-strong: #C9C7C1; /* テーブルヘッダー下・区切り */
/* シグナル:これ以外の色を画面に持ち込まない */
--won: #2E7D64; /* 受注・達成 */
--lost: #8A909C; /* 失注(無彩色。赤にしない) */
--alert: #C2453B; /* 期限超過・目標未達 */
--warn: #B4741C; /* 停滞・期限接近 */
}
配色ルール
- 通常状態のテーブル行に色を持たせない。色がついている行は「対応が必要な行」だけ
- 失注は赤ではなく無彩色にする。赤は「今действするべき」の意味に限定する
- 段階(ステージ)に固有色を割り当てない。段階は位置と幅で表現する。6色のバッジが並ぶ画面にしない
タイポグラフィ
| 役割 | 書体 | 用途 |
|---|---|---|
| UI全般 | Noto Sans JP 400 / 500 / 700 | 見出しも本文もこれ1つ。ウェイトと文字サイズだけで階層を作る |
| 数値 | Roboto Mono 500(font-variant-numeric: tabular-nums) |
金額・件数・確度・日数・日付。桁を揃えて縦に比較できることが最優先 |
| トークン | サイズ | 行間 | 用途 |
|---|---|---|---|
| page-title | 22px / 700 | 1.4 | 画面タイトル |
| section | 15px / 700 | 1.5 | セクション見出し |
| body | 13px / 400 | 1.7 | テーブルセル、本文 |
| label | 11px / 500 | 1.5 | 項目ラベル(letter-spacing: .04em) |
| metric-lg | 28px / 500 Mono | 1.2 | ダッシュボードの主要数値 |
| metric-sm | 15px / 500 Mono | 1.4 | テーブル内の金額 |
本文は13px。 HIREBASE の15pxより小さい。これは業務システムとして情報密度を優先するための意図的な選択であり、代わりに行間とコントラストで可読性を担保する。
レイアウトとスペーシング
| 項目 | 定義 |
|---|---|
| スペーシング | 4pxベース:4 / 8 / 12 / 16 / 24 / 32 / 48 |
| アプリシェル | 左サイドバー(240px、折りたたみで56px)+ メインエリア。ヘッダー56px固定 |
| コンテンツ幅 | 最大幅を設けない。画面幅いっぱいに使う(テーブルの情報量を優先) |
| テーブル行高 | 標準40px / コンパクト32px(切替可能、FR-114) |
| 角丸 | 4px(カード・入力・ボタン)/2px(バッジ)/999px(アバター) |
| 影 | 1段階のみ。0 4px 12px rgba(22,24,29,.10)(ポップオーバー・モーダルのみ)。テーブルやカードに影を使わない |
| 区切り | 罫線1px #E3E2DE。テーブルヘッダー下のみ --border-strong |
主要コンポーネント仕様
| コンポーネント | 仕様 |
|---|---|
| データテーブル | 固定ヘッダー、行ホバーで背景 --brand-50 の50%、選択行は --brand-50。金額列は右寄せ + 等幅数字。列幅リサイズ可 |
| ツールバー | テーブル上部に固定。左=ビュー切替・絞り込み・列設定、右=密度切替・エクスポート・新規作成 |
| 絞り込みチップ | 適用中の条件を横並び表示。× で個別解除、「すべて解除」を末尾に |
| 一括操作バー | 行を選択するとテーブル下部からせり上がる。「N件を選択中」+ 操作ボタン + 解除 |
| パイプラインカード | 商談名/顧客名/金額(Mono・大)/確度/予定クローズ日/担当アバター/停滞バッジ。高さを揃える |
| パイプライン列ヘッダー | 段階名/件数/合計金額。列の背景に薄いプログレスを敷き、全体に占める金額比率を示す |
| 段階進捗バー | 商談詳細の上部。通過済み・現在地・未通過を1本のバーで表現。クリックで段階変更 |
| 活動タイムライン | 左に種別アイコン + 縦罫線、右に内容。日付でグルーピング。種別フィルタをタブで |
| タスク行 | チェックボックス/内容/関連レコード/期限(超過は --alert)/担当アバター。チェックで即完了、取り消し可 |
| メトリクスカード | ラベル(label)/主数値(metric-lg)/前期比(増減の記号 + %)。装飾グラフを載せない |
| 空状態 | 全一覧に専用の空状態。「何が表示されるか」+ 次の行動ボタン。絞り込み0件と、データ0件を別の文言にする |
| スケルトン | テーブルは行形状のスケルトン。スピナーを使わない |
| コマンドパレット | ⌘K。検索・遷移・新規作成をグループ化。矢印キー + Enter で実行 |
| デモ切替バー | プロダクトのトークンとは別扱い(ダークな帯 + 小さめタイポ) |
インタラクション要件(業務システムの肝)
| ID | 要件 |
|---|---|
| IX-01 | 一覧の絞り込み・並び替えは体感で即座に反映されること(100ms以下) |
| IX-02 | テーブル上で ↑↓ 行移動、Enter 詳細を開く、Space 行選択、⌘A 全選択 ができること |
| IX-03 | インライン編集は Enter 確定、Esc 取消、Tab で次のセルへ移動すること |
| IX-04 | ⌘K コマンドパレット、/ グローバル検索フォーカス、N 新規作成 のショートカットを提供すること |
| IX-05 | ショートカット一覧を ? キーで表示すること |
| IX-06 | 破壊的操作(削除・一括更新)は、実行後に5秒間「元に戻す」を表示すること |
| IX-07 | 保存はすべて楽観的更新とし、失敗時のみロールバックしてエラーを表示すること |
| IX-08 | モーダルを開いても背後の一覧の状態(スクロール位置・選択)が保持されること |
モーション
| 対象 | duration | 内容 |
|---|---|---|
| ホバー・フォーカス | 80ms | 色のみ。業務システムでは反応が速いことが価値 |
| モーダル・ポップオーバー | 160ms | フェード + 4px |
| 一括操作バーのせり上がり | 200ms | 下から。cubic-bezier(.16,1,.3,1) |
| カンバンのドラッグ | 150ms | ドロップ時に着地 |
| トースト | 160ms | 右下から |
| 数値の変化 | 400ms | ダッシュボードのメトリクスのみカウントアップ。テーブル内では行わない |
全体的に **HIREBASE より短く設定する。**業務システムで待たされる感覚は品質の低さとして受け取られる。
prefers-reduced-motion: reduce 時は全アニメーションを無効化する。
アクセシビリティ(WCAG 2.1 AA)
| ID | 要件 |
|---|---|
| A11Y-01 | コントラスト比は通常4.5:1以上、18px以上は3:1以上。13px本文でも必ず満たすこと |
| A11Y-02 | 全ての操作をキーボードのみで完遂できる。カンバンのD&Dにはキーボード代替を用意(FR-304) |
| A11Y-03 | フォーカスリングは2px・オフセット2pxで常時可視。outline:none の単独使用禁止 |
| A11Y-04 | 情報を色のみで伝えない。停滞・期限超過は色 + アイコン + テキストで示す |
| A11Y-05 | テーブルは <table> を用い、scope 属性と caption を適切に設定する |
| A11Y-06 | フォームの各入力に label を関連付け、エラーは aria-describedby と role="alert" で読み上げる |
| A11Y-07 | モーダルはフォーカストラップ + Esc で閉じる + 起動元へフォーカス復帰 |
| A11Y-08 | 一括操作の選択件数、絞り込み結果の件数を aria-live で通知 |
| A11Y-09 | ページタイトルは画面ごとに固有。見出しレベルを飛ばさない |
| A11Y-10 | フォントサイズ200%指定でも内容が読め、テーブルは横スクロールで対応する |
ライティング規約
| 原則 | 例 |
|---|---|
| ボタンは「何が起きるか」 | ○「受注として登録」/×「保存」「OK」 |
| 件数を明示する | ○「12件を削除しますか?」/×「削除しますか?」 |
| エラーは原因と対処 | ○「3行目:金額が数値ではありません。半角数字で入力してください」/×「取り込みに失敗しました」 |
| 空状態を区別する | データ0件:「まだ商談がありません。最初の商談を作成しましょう」/絞り込み0件:「条件に一致する商談はありません」+ 条件解除ボタン |
| 謝罪しない | ×「申し訳ございませんが〜」/○ 事実と次の行動 |
| システム語を使わない | ○「この商談を失注として記録する」/×「ステージをlostに更新」 |
9. 技術要件・ディレクトリ構成
技術スタック(固定・勝手に変更しない)
| レイヤ | 技術 | 備考 |
|---|---|---|
| フレームワーク | Next.js 15(App Router)/ TypeScript strict | |
| スタイリング | Tailwind CSS + CSS Variables | トークンは CSS 変数で定義 |
| UIコンポーネント | shadcn/ui(Radix UI基盤) | a11y要件を自前実装せずに満たす |
| 状態管理 | Zustand + persist ミドルウェア | localStorage に永続化 |
| テーブル | TanStack Table v8 | 列制御・ソート・選択。UIは自前。ヘッドレスのまま使う |
| 仮想スクロール | TanStack Virtual | FR-113 |
| フォーム | React Hook Form + Zod | |
| D&D | dnd-kit | キーボード操作対応が必須のため |
| グラフ | Recharts | 分析画面 |
| CSV | Papa Parse | インポート/エクスポート |
| @react-pdf/renderer | 見積書。ブラウザ内で生成する | |
| 日付 | date-fns(ja locale) | |
| アイコン | lucide-react | |
| コマンドパレット | cmdk | FR-112 |
| ホスティング | Vercel | |
| テスト | Vitest / Playwright / axe-core |
上記以外のライブラリを入れる前に必ず提案し、承認を得ること。
ディレクトリ構成
/
├── CLAUDE.md # このファイル
├── app/
│ ├── layout.tsx # アプリシェル(サイドバー・ヘッダー・デモ切替バー)
│ ├── page.tsx # SC-001 ダッシュボード
│ ├── accounts/ # SC-010〜014
│ ├── contacts/ # SC-020, 021
│ ├── deals/ # SC-030〜034
│ ├── activities/ # SC-040
│ ├── tasks/ # SC-050
│ ├── quotes/ # SC-060〜062
│ ├── reports/ # SC-100〜104
│ ├── settings/ # SC-200〜208
│ └── dev/components/ # コンポーネントカタログ(本番では非公開)
├── components/
│ ├── ui/ # shadcn/ui プリミティブ
│ ├── table/ # DataTable, ColumnPicker, FilterBar, BulkActionBar, ViewSidebar
│ ├── domain/ # DealCard, StageBar, ActivityTimeline, TaskRow, MetricCard, QuoteEditor
│ ├── demo/ # RoleSwitcher, ResetButton, GuideTour
│ └── layout/
├── lib/
│ ├── types/ seed/ store/ repo/ # 7章参照
│ ├── query/ # 絞り込み・ソート・ビューの適用ロジック
│ ├── calc/ # 見積計算、着地見込み、転換率、滞留日数
│ ├── csv/ # インポート(列マッピング・検証)/エクスポート
│ ├── pdf/ # 見積書テンプレート
│ ├── validation/ # Zod スキーマ
│ └── utils/
├── e2e/ # Playwright
└── public/
コーディング規約
- TypeScript strict。
any禁止。やむを得ない場合はunknown+ 型ガード - ファイル名は kebab-case、コンポーネントは PascalCase
- 1ファイル300行を超えたら分割を検討する
- コンポーネントから Zustand ストアを直接参照しない。必ず
lib/repo/経由 - 絞り込み・計算ロジックは
lib/query/とlib/calc/に置き、コンポーネントに埋め込まない(テスト対象にするため) - 派生値をストアに保存しない。常に算出する
- コミットは1タスクごと。メッセージに要件IDを含める
例:
feat(deals): パイプラインの停滞検出を実装 (FR-308)
10. 非機能要件
| ID | 要件 | 目標値 |
|---|---|---|
| NFR-01 | LCP(デスクトップ) | 1.8秒以下 |
| NFR-02 | INP(操作への応答) | 200ms以下 |
| NFR-03 | CLS | 0.1以下 |
| NFR-04 | 一覧1,000行での絞り込み・並び替えの反映 | 100ms以下 |
| NFR-05 | 一覧1,000行のスクロールが60fpsを維持すること(仮想スクロール) | — |
| NFR-06 | 初期JSバンドルサイズ(gzip後) | 250KB以下(PDF・グラフは動的import) |
| NFR-07 | Lighthouse | Performance 90 / Accessibility 95 以上 |
| NFR-08 | 対応ブラウザ | Chrome / Safari / Edge 最新2バージョン、iOS Safari 16以降 |
| NFR-09 | ユーザー入力はサニタイズし、CSVインポート時の数式インジェクション(=, +, -, @ 始まり)を無害化すること |
— |
| NFR-10 | localStorage の容量上限(5MB程度)に達した場合、警告を表示し、データを破損させないこと | — |
実案件への転用時の注意(デモ本体の要件ではない)
将来この構成を実サービスへ転用する場合、以下が追加で必要になる。商談で聞かれた際に説明できるよう記録として残す。
- データスコープはサーバー側(RLS等)で担保する必要がある。 本デモの
lib/repo/_scope.tsがその置き換え地点であり、構造上そのまま差し替えられる - 顧客情報は個人情報保護法の対象。利用目的の明示、安全管理措置、委託先管理、開示・削除請求への対応が必要になる
- CSVエクスポートは情報持ち出しの経路になるため、実運用では権限制御と操作ログが必須
- 本デモは顧客データをサーバーに送信しないため、これらの義務は発生しない。 ただし「デモである」旨は画面上に常時明示する
11. 実装タスク
この順に進める。フェーズを飛ばさない。 完了時は [x] に更新する。
Phase 0 — 基盤とデザインシステム(3日)
0-1. 初期化
- Next.js 15 / TypeScript strict / App Router / Tailwind で初期化
- ESLint / Prettier / husky(pre-commit で lint + typecheck)
- 9章のディレクトリ構成を作成
0-2. 型とシード
-
lib/types/に7章の型定義をすべて実装 -
lib/seed/masters.ts/users.ts/stages.ts -
lib/seed/accounts.ts(140社)/contacts.ts(320名) -
lib/seed/deals.ts(140件。停滞・期限超過を意図的に含める) -
lib/seed/activities.ts(900件。頻度に濃淡をつける)/tasks.ts(86件) -
lib/seed/products.ts/quotes.ts/targets.ts - 商談名・活動内容を業種と段階に応じて書き分ける。テンプレの使い回し禁止
- 日付は現在日時からの相対で生成する
0-3. ストアとリポジトリ
-
lib/store/{session,data,settings,ui}.tsを Zustand + persist で実装 -
lib/repo/_scope.tsを最初に実装する(FR-005, FR-006) -
lib/repo/の全モジュールを実装。全メソッド async、擬似ディレイ、Result型、スコープ適用 -
lib/calc/に 滞留日数・加重金額・着地見込み・見積計算 を実装 - スコープとcalcの単体テストをこの時点で書く。後回しにしない
0-4. デザインシステム
-
app/globals.cssにカラートークンを CSS 変数で定義 -
tailwind.config.tsから CSS 変数を参照するよう theme を拡張 - フォント(Noto Sans JP / Roboto Mono)を
next/fontで最適化。数値にtabular-numsを適用 - タイプスケールをユーティリティクラスとして定義
- shadcn/ui を導入し、トークンに合わせて Button / Input / Select / Checkbox / Dialog / Sheet / Popover / Toast / Badge / Tabs / Tooltip / DropdownMenu を上書き
- アプリシェル(サイドバー折りたたみ、ヘッダー、パンくず)
- 共通コンポーネント:
EmptyState(データ0件と絞り込み0件を出し分け)/Skeleton(テーブル行形状)/MetricCard/StatusBadge/TagChip/UserAvatar -
prefers-reduced-motionの全体対応 -
/dev/componentsにコンポーネントカタログを作成
0-5. デモ基盤
-
RoleSwitcher(3ロール + 営業担当のメンバー選択)(FR-001, FR-002) - デモ切替バーを固定配置。プロダクトのトークンとは別扱いのデザイン
- 「デモをリセット」(FR-003)
- ロール権限外アクセス時の案内画面 (FR-008)
Phase 0 完了チェック
-
/dev/componentsで全コンポーネントがトークン通りに表示される - ロールを切り替えると、リポジトリが返す件数が変わる(スコープのテストが通る)
- リロードしても状態が保持される
Phase 1 — 一覧基盤と顧客管理(4日)
1-1. データテーブル基盤(最重要。ここの品質が全画面に波及する)
-
lib/query/に 絞り込み・ソート・ビュー適用ロジックを実装(テスト付き) -
DataTable(TanStack Table ヘッドレス + 自前UI)。固定ヘッダー、列幅リサイズ - 列カスタマイズ(表示・非表示・並び替え)(FR-101)
- ソート(複数列対応)(FR-102)
-
FilterBar:項目ごとの条件指定 (FR-103) - URLクエリとの同期 (FR-104)
-
ConditionChips:適用条件の表示と個別解除 (FR-105) - 行選択と
BulkActionBar(下からせり上がる)(FR-108, FR-109) - 取り消し可能な削除トースト (FR-110, IX-06)
- 仮想スクロール (FR-113)
- 密度切替(40px / 32px)(FR-114)
- キーボード操作(
↑↓EnterSpace⌘A)(IX-02) - インライン編集(
EnterEscTab)(FR-115, IX-03)
1-2. 保存ビュー
-
ViewSidebar:ビュー一覧、件数バッジ、ワンクリック切替 (FR-107) - ビューの保存・編集・削除。個人/共有の区別(共有はマネージャー以上)(FR-106)
1-3. 顧客・担当者
- SC-010 顧客一覧(上記テーブル基盤を適用)
- SC-011 顧客詳細(インライン編集、タブ構成)(FR-202, FR-206)
- SC-012 顧客の新規作成 + 重複検出警告 (FR-203)
- SC-013 重複マージ(並列比較、項目ごとの選択、関連レコードの引き継ぎ)(FR-204)
- SC-020 担当者一覧 / SC-021 担当者詳細 (FR-205)
1-4. CSV入出力
-
lib/csv/にインポート(列マッピング・検証)/エクスポートを実装 - SC-014 インポート4ステップ(ファイル選択 → 列マッピング → プレビュー → 結果)(FR-207, FR-209)
- 未対応列の「カスタム項目として追加」(FR-208)
- インポートタグの自動付与 (FR-210)
- 数式インジェクションの無害化 (NFR-09)
- エクスポート(現在の絞り込み・表示列のまま、実際にダウンロード)(FR-211)
1-5. グローバル検索・コマンドパレット
- ヘッダーのグローバル検索(種別グループ化)(FR-111)
-
⌘Kコマンドパレット(cmdk)(FR-112) - ショートカット:
/検索フォーカス、N新規作成、?一覧表示 (IX-04, IX-05)
Phase 1 完了チェック
- フローCが完走する(CSVインポート → 一覧に反映)
- 1,000行でも絞り込みが100ms以下、スクロールが60fps
- テーブルがキーボードのみで操作完遂できる
- ロールを切り替えると一覧の件数が変わる
Phase 2 — 商談・パイプライン(4日)
- SC-030 パイプライン(カンバン)。列ヘッダーに件数・合計金額・金額比率のプログレス (FR-302)
- dnd-kit による D&D 段階変更 (FR-303)
- キーボードでの段階変更代替 (FR-304, A11Y-02)
- 段階移動時の必須項目チェックモーダル (FR-305)
- 確度の自動設定と手動上書き (FR-306)
- SC-031 商談一覧(テーブル)。カンバンとの切替で絞り込みを維持 (FR-307)
- 停滞検出(既定14日)の算出とフラグ表示 (FR-308)
- 期限超過フラグ (FR-309)
- SC-032 商談詳細。段階進捗バー、活動タイムライン、タスク、見積 (FR-312)
- SC-033 商談の新規作成(顧客詳細からの遷移で顧客を自動セット)/複製 (FR-313)
- SC-034 受注・失注登録(理由の定型選択 + 自由記述)(FR-310)
- SC-001 ダッシュボード(ロールで表示内容が変わる)(FR-601, FR-602)
Phase 2 完了チェック
- 4章「ロールを跨ぐ体験」の1と2が動作する
- 停滞・期限超過の商談が、ダッシュボードとパイプラインの両方で正しくフラグされる
- カンバンがキーボードのみで操作できる
Phase 3 — 活動・タスク・見積(4日)
3-1. 活動・タスク
- SC-041 活動記録モーダル(一覧・詳細から離れずに記録)(FR-401, FR-402)
- 次アクション入力からのタスク自動生成 (FR-403)
-
ActivityTimeline(種別アイコン、日付グルーピング、種別フィルタ)(FR-404) - SC-040 活動一覧
- SC-050 タスク一覧(期限別グルーピング)(FR-405, FR-406)
- ダッシュボードの今日のタスク・期限超過 (FR-407)
- チェックによる完了と取り消し (FR-408)
- マネージャーによるタスク割当と通知 (FR-409)
- 活動記録時に
Account.lastActivityAt/Deal.lastActivityAtを更新する
3-2. メール
- メールテンプレートと差し込み変数の置換 (FR-410)
- 送信内容のプレビューモーダル → 確定で活動履歴に自動記録 (FR-411)
3-3. 見積・商品
- SC-203 商品マスタ (FR-501)
- SC-061 見積作成(明細行の追加・並び替え、商品マスタからの選択)(FR-502)
-
lib/calc/quote.ts:行小計・小計・消費税・合計の算出(内税/外税、端数処理)(FR-503, FR-504) - SC-062 見積プレビュー → @react-pdf/renderer でPDFを生成し、実際にダウンロードできること (FR-505)
- SC-060 見積一覧、商談詳細からの作成 (FR-506, FR-508)
Phase 3 完了チェック
- フローAが完走する(顧客登録 → 商談作成 → 活動記録 → タスク自動生成 → 見積PDF → 受注)
- 4章「ロールを跨ぐ体験」の3が動作する
- 見積PDFが実際にダウンロードでき、レイアウトが崩れていない
Phase 4 — 分析と設定(3日)
4-1. 分析
- SC-100 パイプライン分析(段階別件数・金額、転換率ファネル、平均滞留日数)(FR-603)
- SC-101 担当者別実績(営業担当ロールでは非表示)(FR-604)
- SC-102 予実管理(目標・実績・着地見込み・達成率)(FR-605, FR-311)
- SC-103 失注分析 (FR-606)
- SC-104 活動分析
- 全分析画面の期間指定 (FR-607)
4-2. 設定
- SC-201 カスタム項目(6種の型)(FR-608)
- カスタム項目が詳細画面・編集フォーム・列選択・絞り込みに即座に反映されること (FR-609)
- SC-202 パイプライン段階の編集。変更が既存商談に即座に反映されること (FR-314)
- SC-204 ユーザー・チーム管理 (FR-610)
- SC-205 タグ管理 / SC-206 メールテンプレート / SC-208 目標設定
- SC-207 操作ログ (FR-611)
- 通知センター (FR-612)
Phase 4 完了チェック
- フローBが完走する(マネージャーの週次レビュー)
- 4章「ロールを跨ぐ体験」の4が動作する(カスタム項目の追加が営業担当の画面に反映)
Phase 5 — 仕上げ(3日)
5-1. 体験の総点検
- 4章「ロールを跨ぐ体験」の4パターンをすべて手動で確認する
- 全32画面を開き、空の画面が1つもないことを確認する
- 全画面の空状態(データ0件/絞り込み0件の出し分け)・ローディング・エラー状態を確認する
- ガイドツアーを実装 (FR-009)
- localStorage 容量上限時の警告 (NFR-10)
5-2. アクセシビリティ監査
- axe-core を全画面に実行し、Critical / Serious を0件に
- キーボードのみで全画面を操作完遂する手動確認
- 13px本文のコントラスト比を実測して 4.5:1 以上を確認 (A11Y-01)
- フォントサイズ200%での確認 (A11Y-10)
5-3. パフォーマンス
- PDF・グラフ・CSVを動的importに切り出し、初期バンドルを250KB以下に (NFR-06)
- 1,000行での絞り込み100ms以下、スクロール60fps を実測 (NFR-04, NFR-05)
- Lighthouse で Performance 90 / Accessibility 95 を達成 (NFR-07)
5-4. テストと公開
- Playwright で フローA・B・C の E2E を実装・実行
- Vitest:
lib/querylib/calclib/csvlib/repo/_scopeのカバレッジ80%以上 -
/dev/componentsを本番で非公開に - Vercel へデプロイ
- LP への導線設置(HIREBASE と並べる)
- 各フェーズのキャプチャを整理し、LP掲載用の時系列コンテンツにまとめる
合計 18営業日(約3.5週間)/1名専任 + レビュー体制。 各フェーズ完了時点でプレビューURLと画面キャプチャを残し、「Day 3 でここまで」「Day 7 でここまで」という時系列コンテンツとして LP に掲載する。
工数配分の意図: Phase 1 に4日を割いているのは、データテーブル基盤が全画面に波及するため。ここを雑に作ると、以降の全一覧画面が作り直しになる。逆にここが良ければ、商談・タスク・見積の一覧はほぼ設定だけで完成する。
12. 受入基準
- 6章の優先度 P1 の要件がすべて実装されていること
- 5章の主要フローA・B・Cが、エンドツーエンドで完走すること
- 4章「ロールを跨ぐ体験」の4パターンがすべて動作すること
- ロールを切り替えると、一覧・ダッシュボード・分析の表示範囲が変わること。かつスコープ判定が
lib/repo/_scope.tsに集約されていること - 一覧テーブルが、絞り込み・列選択・保存ビュー・一括操作・キーボード操作のすべてに対応していること
- 1,000行での絞り込みが100ms以下、スクロールが60fpsを維持すること
- CSVインポートが列マッピングとエラー行表示に対応し、CSVエクスポートと見積PDFが実際にダウンロードできること
- 全32画面のいずれにも空の状態がなく、リアルなデータが表示されていること
- リロード後も操作結果が保持され、「デモをリセット」で初期状態に戻ること
- axe-core で Critical / Serious の指摘が0件であること
- Lighthouse の基準値を満たすこと
- データアクセスがすべて
lib/repo/を経由しており、コンポーネントが Zustand ストアを直接参照していないこと - HIREBASE と並べたときに、明確に別のプロダクトとして見えること(8章のアートディレクションが実現されていること)
判断に迷ったときのルール
- 仕様がこの文書にない場合は、実装せずに質問する。 推測で作らない
- キーボード操作と「空の画面を作らない」は削減対象外。 速度を出すために削らない
- スコープ外(3章 Won't have)は、思いついても実装しない。特にワークフロー自動化ビルダーと帳票の自由レイアウト編集
- 「デモだから」を理由に品質を落とす判断はしない
- コンポーネントからストアを直接触りたくなったら、それはリポジトリ層に足りないメソッドがあるということ
- HIREBASE のトークン・コンポーネントをコピーしない。 別プロダクトとして設計する