作成日: 2026-04-07 対応設計書:
20260406_requirements_local.md/20260406_design_local.md(v2.1) スコープ: Phase 1 MVP(デスクトップ単体)
設計書3本(要件定義・設計・図解)に基づき、Phase 1 MVPを実装する。 Gemini APIキーは未取得のため、AI生成はモック実装とし、後からAPI差し替え可能な設計にする。
| 項目 | 状態 |
|---|---|
| Node.js | v20.19.6 OK |
| pnpm | 10.33 OK |
| webkit2gtk | OK |
| Rust | 未インストール(要インストール) |
Phase 1はデスクトップのみのため、モノレポではなくフラット構成を採用。
Phase 2でモバイル追加時に packages/ へ共通ロジックを分離する。
shift-app/
├── docs/ # 既存設計書
├── src-tauri/ # Rust backend (Tauri v2)
│ ├── Cargo.toml
│ ├── tauri.conf.json
│ ├── capabilities/
│ └── src/
│ └── lib.rs
├── src/ # React frontend
│ ├── main.tsx
│ ├── App.tsx # Router + Layout
│ ├── db/
│ │ ├── schema.sql # 11テーブルのCREATE文
│ │ ├── database.ts # DB初期化・接続
│ │ └── queries/ # テーブルごとのCRUD関数
│ │ ├── stores.ts
│ │ ├── positions.ts
│ │ ├── staff.ts
│ │ ├── staff-stores.ts
│ │ ├── staff-positions.ts
│ │ ├── staff-availability.ts
│ │ ├── staff-ng-dates.ts
│ │ ├── store-requirements.ts
│ │ ├── shift-entries.ts
│ │ └── conflict-logs.ts
│ ├── stores/ # Zustand stores
│ │ ├── useStoreStore.ts
│ │ ├── useStaffStore.ts
│ │ ├── useShiftStore.ts
│ │ ├── useCalendarStore.ts
│ │ └── useAuthStore.ts
│ ├── validation/
│ │ ├── rules.ts # 4つのバリデーションルール
│ │ └── types.ts
│ ├── services/
│ │ ├── mock-ai-generator.ts # モックAI生成
│ │ ├── pdf-export.ts # PDF出力
│ │ └── csv-export.ts # CSV出力
│ ├── components/
│ │ ├── ui/ # shadcn/ui (自動生成)
│ │ ├── layout/
│ │ │ ├── Sidebar.tsx
│ │ │ ├── Header.tsx
│ │ │ └── MainLayout.tsx
│ │ ├── auth/
│ │ │ └── PasswordScreen.tsx
│ │ ├── dashboard/
│ │ │ ├── DashboardPage.tsx
│ │ │ ├── WeeklySummary.tsx
│ │ │ ├── StaffingRate.tsx
│ │ │ └── WarningCount.tsx
│ │ ├── stores/
│ │ │ ├── StoreListPage.tsx
│ │ │ ├── StoreForm.tsx
│ │ │ └── PositionManager.tsx
│ │ ├── staff/
│ │ │ ├── StaffListPage.tsx
│ │ │ ├── StaffForm.tsx
│ │ │ ├── AvailabilityEditor.tsx
│ │ │ └── NgDateEditor.tsx
│ │ ├── requirements/
│ │ │ ├── RequirementsPage.tsx
│ │ │ └── RequirementForm.tsx
│ │ ├── calendar/
│ │ │ ├── CalendarPage.tsx
│ │ │ ├── MonthView.tsx
│ │ │ ├── WeekView.tsx
│ │ │ ├── ShiftCell.tsx
│ │ │ ├── ShiftEntryDialog.tsx
│ │ │ ├── StaffRow.tsx
│ │ │ ├── ValidationPanel.tsx
│ │ │ └── GenerateButton.tsx
│ │ └── export/
│ │ └── ExportDialog.tsx
│ ├── hooks/
│ │ ├── useDatabase.ts
│ │ └── useValidation.ts
│ ├── lib/
│ │ └── utils.ts # cn() helper, date utils
│ └── types/
│ └── index.ts # 共有TypeScript型定義
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── tailwind.config.ts
├── postcss.config.js
└── components.json # shadcn/ui config
| カテゴリ | パッケージ | 理由 |
|---|---|---|
| フレームワーク | Tauri v2 + React 19 + Vite | 設計書準拠 |
| 状態管理 | Zustand v5 | 設計書準拠 |
| ルーティング | react-router-dom v7 | SPA内画面遷移 |
| DB | @tauri-apps/plugin-sql | Tauri公式SQLiteプラグイン |
| ファイル操作 | @tauri-apps/plugin-dialog, plugin-fs | PDF/CSV保存ダイアログ |
| D&D | @dnd-kit/core + sortable | モダンDnDライブラリ(react-beautiful-dndは非推奨) |
| jspdf + jspdf-autotable | ローカル完結のPDF生成 | |
| 日付操作 | date-fns v4 | 軽量な日付ライブラリ |
| UI | shadcn/ui + Tailwind CSS | 設計書準拠 |
| アイコン | lucide-react | shadcn/uiと統合済み |
| 候補 | 不採用理由 |
|---|---|
| UUIDライブラリ | crypto.randomUUID() で十分 |
| カレンダーライブラリ | スタッフ×日付グリッドは標準カレンダーと異なる。カスタムグリッドの方がシンプル |
| フォームライブラリ | shadcn/ui + 制御コンポーネントで十分な規模 |
| i18nライブラリ | 日本語のみ |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -ypnpm create tauri-app で React + TypeScript + Vite テンプレート生成。
既存の docs/ ディレクトリは保持。
shadcn/uiコンポーネント: button, input, dialog, select, table, card, tabs, badge, form, label, textarea, checkbox, toast, separator
tauri-plugin-sql(sqlite feature)tauri-plugin-dialogtauri-plugin-fstauri-plugin-store(パスワードハッシュ保存用)
Cargo.toml、lib.rs、tauri.conf.json、capabilities/ を設定。
src/types/index.ts に11テーブル分のインターフェースを定義。
Store, Position, Staff, StaffAvailability, StaffNgDate, StoreRequirement, ShiftEntry 等。
ValidationWarning, ValidationError 型も定義。
src/db/schema.sql— 設計書のCREATE TABLE文をそのまま使用src/db/database.ts—Database.load("sqlite:shiftcraft.db")で初期化、schema.sqlを実行src/db/queries/*.ts— テーブルごとにCRUD関数を実装
UUID: crypto.randomUUID() でフロントエンド側で生成してINSERT。
日付/時刻: TEXT型(YYYY-MM-DD / HH:mm)で格納。
| ルール | 条件 | 重大度 |
|---|---|---|
| 休憩不足 | 6h超→45m / 8h超→1h未満 | error(赤) |
| 二重配置 | 同一スタッフ・同時刻に他店舗 | error(ブロック) |
| 長時間労働 | 週40h超 / 日12h超 | warning(黄) |
| 連続勤務 | max_consecutive_days超過(デフォルト5日) | warning(黄) |
// src/validation/rules.ts
export function validateShifts(shifts: ShiftEntry[], staff: Staff[]): ValidationResult[]useStoreStore— 店舗・ポジションの状態とCRUDuseStaffStore— スタッフ・可否・NG日の状態とCRUDuseShiftStore— シフトエントリ + バリデーション結果useCalendarStore— currentMonth, viewMode, selectedStoreId(UI状態のみ)useAuthStore— isUnlocked, パスワード検証
ルート構成:
| パス | コンポーネント | 説明 |
|---|---|---|
/ |
DashboardPage | ダッシュボード |
/stores |
StoreListPage | 店舗・ポジション管理 |
/staff |
StaffListPage | スタッフ管理 |
/requirements |
RequirementsPage | 必要人員設定 |
/calendar |
CalendarPage | シフトカレンダー |
PasswordScreen はルートではなく、未ロック時に表示するフルスクリーンオーバーレイ。
- テーブル一覧 + モーダルフォーム(追加/編集)
- ポジションは店舗詳細内でインライン管理
- テーブル一覧 + モーダルフォーム
- タブ構成: 基本情報 / 曜日別可否(7日グリッド) / NG日(日付ピッカー) / 店舗・ポジション(チェックボックス)
- 店舗・ポジション・日付を選択 → 時間帯・人数を入力
カスタムグリッド (カレンダーライブラリ不使用):
┌─────────┬──────┬──────┬──────┬...┬──────┐
│ スタッフ │ 4/1 │ 4/2 │ 4/3 │ │ 4/30 │
├─────────┼──────┼──────┼──────┼...┼──────┤
│ 田中 │ 9-17 │ │10-18 │ │ 9-14 │
│ 佐藤 │10-18 │ 9-17 │ │ │ │
└─────────┴──────┴──────┴──────┴...┴──────┘
@dnd-kitでセル間のD&D(スタッフ/日付の変更)- 月間/週間ビュー切替
ValidationPanelを画面下部に常時表示React.memoでセル再レンダリング最適化
アルゴリズム: 貪欲法(greedy slot-filling)
is_manual_modified = trueのシフトを除外(手動保護)- 各要件スロット(日付×時間帯×ポジション×人数)に対して:
- 適格スタッフを抽出(店舗・ポジション・曜日可否・NG日チェック)
- 現時点の割当時間が少ない順にソート(労働時間均等化)
- 上位N人を割当
- 休憩時間を自動設定(6h超→45m / 8h超→60m)
- 全シフトにバリデーション実行
{ shifts, warnings }を返却
インターフェース: 後でGemini API実装に差し替え可能な同一型定義。 UX: 1-2秒の擬似遅延 + プログレス表示。
- ライブラリ: jspdf + jspdf-autotable
- レイアウト: A3横、スタッフ名×日付のグリッド、ポジション色分け
- 日本語: NotoSansJPフォント埋め込み必須
- 保存: Tauriファイルダイアログで保存先選択
- 形式: UTF-8 BOM付き(Excel対応)
- カラム: 日付, スタッフ名, 店舗名, ポジション, 開始時間, 終了時間, 休憩(分)
- 対象: カレンダー表示中の期間
- 週間サマリー: 当週の日別シフト数・合計労働時間
- 充足率: 配置人数 ÷ 必要人数 × 100%
- 警告数: バリデーションエンジン結果の件数表示
- 更新: 表示時にSQLiteから都度計算(キャッシュなし)
| 項目 | 対応 |
|---|---|
| PDF日本語 | NotoSansJPフォントをbase64埋め込み |
| UUID | crypto.randomUUID() 使用。ライブラリ不要 |
| 日付/時刻 | TEXT型(YYYY-MM-DD / HH:mm)。date-fnsで操作 |
| business_hours JSON | Record<number, { open: string; close: string }> 型 |
| カレンダー性能 | 50人×30日=1500セル。React.memo必須。必要に応じてvirtualization追加 |
| パスワード | Phase 1はUIゲートのみ(SQLite暗号化なし)。E2EEはPhase 2 |
pnpm tauri devでアプリ起動- 店舗登録 → ポジション追加 → スタッフ登録(可否・NG日設定)→ 必要人員設定
- AI生成実行 → カレンダーにシフト表示
- D&Dでシフト移動 → バリデーション警告確認
- 手動編集後にAI再生成 → 手動保護シフトが維持されることを確認
- PDF出力 → A3横レイアウト・日本語表示確認
- CSV出力 → Excelで文字化けなく開けることを確認
- ダッシュボード → 充足率・警告数が正確に表示されることを確認