Appearance
実装指示書
転職エージェント業務管理システム - Claude Code向け
プロジェクト概要
転職エージェント業務における複数ツール(スプレッドシート、社内システム、Matchingood)を統合し、業務効率化・登録漏れ防止を実現するWebアプリケーション。
対象ユーザー
- エージェント: 2名(面談・企業紹介担当)
- アシスタント: 1名(エントリー・企業調整担当)
- 計3名、権限分けなし
スケジュール
- MVP: 2025年2月末
- フェーズ2: 2025年3月以降
技術スタック
| レイヤー | 技術 |
|---|---|
| フロントエンド | React 19 + TypeScript + Vite 6 |
| スタイリング | Tailwind CSS 4 + shadcn/ui |
| データ取得 | TanStack React Query 5 |
| 状態管理 | Zustand |
| バックエンド | Hono 4 + Node.js 22 |
| データベース | Supabase (PostgreSQL) |
| 認証 | Cloudflare Access (Google IdP) |
| フロントホスティング | Cloudflare Pages |
| APIホスティング | Cloud Run |
実装時の注意事項
認証について
重要: 認証はCloudflare Accessがインフラレベルで処理する。
- フロントエンドにログイン画面は不要
- フロントエンドに認証ライブラリ(Auth0等)は不要
- APIリクエスト時、
CF-Access-JWT-Assertionヘッダーが自動付与される - バックエンドではこのJWTを検証してユーザー情報を取得
typescript
// フロントエンド: API呼び出し
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_ROOT_URL,
withCredentials: true, // Cookie送信でJWTが自動付与される
})
// バックエンド: JWT検証
const cfToken = c.req.header('CF-Access-JWT-Assertion')
const { payload } = await jose.jwtVerify(cfToken, jwks, { audience: CF_ACCESS_AUD })
c.set('user', { email: payload.email })データベース接続
Supabaseへの接続は2つの方法がある:
- Supabase Client(推奨): REST API経由、セットアップが簡単
- Direct Connection: PostgreSQL直接接続、パフォーマンス重視
今回はSupabase Clientを使用。
typescript
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!)クリーンアーキテクチャ
バックエンドは4層構造:
- interfaces層: ルーティング、ミドルウェア、コントローラー
- application層: ユースケース(サービス)
- domain層: エンティティ、リポジトリインターフェース、ドメインサービス
- infrastructure層: リポジトリ実装(Supabase)
依存性注入を使い、domain層がinfrastructure層に依存しないようにする。
主要機能の実装ポイント
1. ダッシュボード
エンドポイント: /api/v1/dashboard/*
表示項目:
- 未完了タスク一覧
- 今日の面接予定
- 今週の面接予定
- 選考結果待ち一覧
- 実績サマリ(前月比較付き)
実装ポイント:
- 面接予定は
applicationsテーブルとselection_statusesから取得 - 実績サマリは集計クエリで計算
- 前月比較は2つの期間の集計を比較
2. 求職者管理
エンドポイント: /api/v1/candidates/*
実装ポイント:
- 一覧取得はページネーション必須
- フィルタは複数条件のAND検索
- 詳細画面には選考一覧と面談メモを含める
- 年齢は生年月日から自動計算
3. 選考管理
エンドポイント: /api/v1/applications/*
実装ポイント:
- ステータス更新時は必ず履歴テーブルにも追加
- 同じ求職者×企業の重複登録はエラー
- 横断一覧は複数フィルタ対応
4. ASP出金日計算
ドメインサービス: AspPaymentCalculator
typescript
function calculatePaymentDate(
applicationDate: Date, // 申込日
cutoffDay: number, // ASPの締め日
paymentMonthOffset: number // 出金月オフセット
): Date {
const day = applicationDate.getDate()
if (day > cutoffDay) {
// 締め日を過ぎている → オフセット+1ヶ月後の月末
return endOfMonth(addMonths(applicationDate, paymentMonthOffset + 1))
} else {
// 締め日以前 → オフセット月後の月末
return endOfMonth(addMonths(applicationDate, paymentMonthOffset))
}
}5. ASP承認判定
ドメインサービス: AspApprovalJudge
typescript
function getApprovalStatus(candidate: Candidate): 'approved' | 'rejected' | 'pending' {
// Zoom面談日が入っている = 承認
if (candidate.zoomInterviewDate) return 'approved'
// 終了理由がある = 否認
if (candidate.terminationReason || candidate.phoneTerminationReason) return 'rejected'
// それ以外 = 対応待ち
return 'pending'
}6. 売上計算
ドメインサービス: RevenueCalculator
typescript
function calculateRevenue(application: Application): Revenue {
const finalSalary = application.finalSalary * 10000 // 万円→円
const feeRate = application.candidate.channel.feeRate
const aspCost = application.candidate.asp?.costPerAcquisition || 0
if (feeRate > 0) {
// スカウト媒体経由
return {
grossRevenue: finalSalary,
cost: finalSalary * feeRate,
netRevenue: finalSalary * (1 - feeRate),
}
} else {
// 自社サイト経由
return {
grossRevenue: finalSalary,
cost: aspCost,
netRevenue: finalSalary - aspCost,
}
}
}画面実装のポイント
OOUIの原則
このシステムはオブジェクト指向UI設計に基づいている:
- オブジェクト(名詞)が主役: 求職者、企業、選考
- 一覧 → 詳細 → アクションの流れ
- 詳細画面から関連オブジェクトへ遷移可能
画面遷移パターン
求職者一覧 → 求職者詳細 → 選考詳細 / 面談メモ
企業一覧 → 企業詳細 → 選考詳細
選考一覧 → 選考詳細 → 求職者詳細 / 企業詳細
ダッシュボード → 各詳細画面(直接遷移)コンポーネント設計
- 一覧画面: フィルタ + テーブル + ページネーション
- 詳細画面: 基本情報 + サブコレクション(タブまたはセクション)
- 登録・編集: モーダルダイアログ
- ステータス更新: 専用モーダル(履歴追加機能付き)
ファイル一覧(このプロジェクトで提供するドキュメント)
| ファイル名 | 内容 |
|---|---|
要件定義書.docx | 機能要件、非機能要件、業務フロー |
テーブル定義書.docx | 全12テーブルの詳細定義 |
ER図.mermaid | テーブル間のリレーション |
画面定義書.docx | 全14画面の詳細定義 |
API_DESIGN.md | APIエンドポイント設計 |
INFRASTRUCTURE.md | インフラ構成ガイド |
ENVIRONMENT_VARIABLES.md | 環境変数一覧 |
DIRECTORY_STRUCTURE.md | ディレクトリ構成 |
database_schema.sql | Supabase用テーブル作成SQL |
Dockerfile | Cloud Run用Dockerfile |
deploy-api.yml | バックエンドCI/CDワークフロー |
deploy-cloudflare.yml | フロントエンドCI/CDワークフロー |
実装の進め方(推奨)
Phase 1: 基盤構築
プロジェクト初期化
- フロントエンド:
npm create vite@latest frontend -- --template react-ts - バックエンド:
mkdir api && npm init -y
- フロントエンド:
データベース構築
- Supabaseプロジェクト作成
database_schema.sqlを実行
バックエンド基盤
- Honoアプリケーション設定
- Cloudflare Access JWT検証ミドルウェア
- Supabase接続設定
- ヘルスチェックエンドポイント
フロントエンド基盤
- Vite + React設定
- Tailwind CSS + shadcn/ui設定
- React Query設定
- APIクライアント設定
- ルーティング設定
Phase 2: マスタ機能
- バックエンド: マスタCRUD API
- フロントエンド: マスタ管理画面
Phase 3: 求職者機能
- バックエンド: 求職者CRUD API、面談メモAPI
- フロントエンド: 求職者一覧、詳細、登録・編集画面
Phase 4: 企業機能
- バックエンド: 企業CRUD API
- フロントエンド: 企業一覧、詳細、登録・編集画面
Phase 5: 選考機能
- バックエンド: 選考CRUD API、ステータス更新API、履歴API
- フロントエンド: 選考一覧、詳細、ステータス更新画面
Phase 6: ダッシュボード
- バックエンド: ダッシュボード集計API
- フロントエンド: ダッシュボード画面
Phase 7: 集計・レポート
- バックエンド: 各種集計API(日次、月次、媒体別、ASP別、担当者別)
- フロントエンド: レポート画面
Phase 8: 仕上げ
- テスト: ユニットテスト、E2Eテスト
- CI/CD: GitHub Actions設定
- デプロイ: Cloud Run、Cloudflare Pages
開発フロー(Issue駆動開発)
基本ルール
- 実装前にIssueを作成する
- 1つのIssueは1つの小さなタスク
- Issueに紐づくブランチで作業
- コミットメッセージにIssue番号を含める
- 完了したらIssueをクローズ
Issueの粒度
1つのIssueは1〜4時間で完了できるサイズに分割する。
良い例:
[API] 求職者一覧取得エンドポイント実装[Frontend] 求職者一覧テーブルコンポーネント作成[API] Cloudflare Access JWT検証ミドルウェア実装
悪い例:
求職者機能を実装する← 大きすぎるバグ修正← 具体性がない
Issue テンプレート
markdown
## 概要
<!-- 何を実装するか -->
## 完了条件
- [ ] 条件1
- [ ] 条件2
- [ ] 条件3
## 関連ドキュメント
<!-- 参照すべきドキュメントがあれば -->
## 備考
<!-- 補足事項があれば -->ブランチ命名規則
feature/#{Issue番号}-{簡潔な説明}例:
feature/#1-setup-projectfeature/#5-candidate-list-apifeature/#12-dashboard-ui
コミットメッセージ規則
{type}: {説明} #{Issue番号}type一覧:
feat: 新機能fix: バグ修正docs: ドキュメントstyle: フォーマット(コードの動作に影響なし)refactor: リファクタリングtest: テストchore: ビルド、ツール設定等
例:
feat: 求職者一覧APIを実装 #5
fix: ページネーションのオフセット計算を修正 #8
docs: API設計書を更新 #10ワークフロー
1. Issueを作成
↓
2. ブランチを作成(feature/#XX-xxx)
↓
3. 実装・コミット(コミットメッセージに#XX)
↓
4. Push
↓
5. PRを作成(またはmainに直接マージ)
↓
6. Issueをクローズ(PRマージ時に自動クローズ or 手動)PRでIssueを自動クローズ
PRの説明に以下を含めると、マージ時にIssueが自動クローズされる:
Closes #5推奨Issueラベル
| ラベル | 色 | 説明 |
|---|---|---|
api | 青 | バックエンド関連 |
frontend | 緑 | フロントエンド関連 |
infra | 紫 | インフラ関連 |
bug | 赤 | バグ |
enhancement | 水色 | 機能改善 |
documentation | 黄 | ドキュメント |
初期Issue一覧(参考)
以下の順序でIssueを作成し、実装を進めることを推奨する。
Phase 1: 基盤構築
[Infra] プロジェクト初期化(モノレポ構成)[Infra] Husky + lint-staged セットアップ[Infra] Supabaseプロジェクト作成・テーブル構築[API] Honoアプリケーション基盤構築[API] Cloudflare Access JWT検証ミドルウェア実装[API] Supabase接続設定[API] ヘルスチェックエンドポイント実装[Frontend] Vite + React プロジェクト初期化[Frontend] Tailwind CSS + shadcn/ui セットアップ[Frontend] React Query + APIクライアント設定[Frontend] ルーティング設定[Frontend] レイアウトコンポーネント作成
Phase 2: マスタ機能
[API] マスタ取得API実装(channels, asps, statuses等)[API] マスタ更新API実装[Frontend] マスタ管理画面実装
Phase 3: 求職者機能
[API] 求職者一覧API実装[API] 求職者詳細API実装[API] 求職者登録・更新API実装[API] 面談メモCRUD API実装[Frontend] 求職者一覧画面実装[Frontend] 求職者詳細画面実装[Frontend] 求職者登録・編集モーダル実装[Frontend] 面談メモ機能実装
Phase 4: 企業機能
[API] 企業CRUD API実装[Frontend] 企業一覧画面実装[Frontend] 企業詳細画面実装[Frontend] 企業登録・編集モーダル実装
Phase 5: 選考機能
[API] 選考一覧API実装[API] 選考詳細・履歴API実装[API] 選考登録・更新API実装[API] ステータス更新API実装(履歴追加)[Frontend] 選考一覧画面実装[Frontend] 選考詳細画面実装[Frontend] 選考登録・編集モーダル実装[Frontend] ステータス更新モーダル実装
Phase 6: ダッシュボード
[API] ダッシュボード集計API実装[Frontend] ダッシュボード画面実装
Phase 7: 集計・レポート
[API] 日次・月次実績API実装[API] 媒体別・ASP別集計API実装[API] 担当者別集計API実装[Frontend] レポート画面実装
Phase 8: 仕上げ
[Test] ドメインサービスユニットテスト[Test] API統合テスト[Infra] CI/CD設定(GitHub Actions)[Infra] 本番デプロイ
コード品質の指針
Husky + lint-staged(必須)
コミット前にESLint/Prettierを自動実行する。プロジェクトルートで以下をセットアップすること。
セットアップ手順:
bash
# ルートディレクトリで実行
npm install -D husky lint-staged eslint prettier
# Husky初期化
npx husky initpackage.json(ルート)に追加:
json
{
"devDependencies": {
"husky": "^9.0.0",
"lint-staged": "^15.0.0",
"eslint": "^9.0.0",
"prettier": "^3.0.0"
},
"scripts": {
"prepare": "husky"
},
"lint-staged": {
"frontend/**/*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"api/**/*.ts": ["eslint --fix", "prettier --write"],
"*.{json,md,yml,yaml}": ["prettier --write"]
}
}.husky/pre-commit:
bash
npx lint-staged.prettierrc:
json
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100
}eslint.config.js(ESLint v9 flat config):
javascript
import js from '@eslint/js'
import typescript from '@typescript-eslint/eslint-plugin'
import typescriptParser from '@typescript-eslint/parser'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
export default [
js.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
},
plugins: {
'@typescript-eslint': typescript,
react,
'react-hooks': reactHooks,
},
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
},
]TypeScript
strict: trueを有効化any型は極力使わない- 型定義は
types/ディレクトリに集約
React
- 関数コンポーネントのみ使用
- カスタムフックで状態ロジックを分離
- コンポーネントは単一責任原則
API
- RESTful設計
- 適切なHTTPステータスコード
- エラーレスポンスは統一フォーマット
テスト
- ドメインサービスはユニットテスト必須
- APIはインテグレーションテスト推奨
よくある質問(FAQ)
Q: ログイン画面は必要?
A: 不要。Cloudflare Accessがインフラレベルで認証を行う。
Q: ユーザー登録機能は必要?
A: マスタ管理画面でユーザー(エージェント/アシスタント)を登録できるようにする。ただし、実際のログイン認証はCloudflare Access + Google Workspaceで行うため、メールアドレスが一致している必要がある。
Q: データ移行はどうする?
A: 既存データ(約1,000人分)はCSV/Excelでエクスポートし、Supabaseにインポートする。移行スクリプトは別途作成。
Q: メール機能は?
A: フェーズ2で実装予定。MVPには含まない。
Q: モバイル対応は?
A: レスポンシブデザインで対応。専用モバイルアプリは作らない。
連絡事項
- 不明点があれば、このドキュメントと一緒に提供される他のドキュメント(要件定義書、画面定義書など)を参照
- 仕様の判断に迷う場合は、シンプルな方を選択
- パフォーマンスより可読性・保守性を優先