# HyperDashi フロントエンド実装仕様書
## 概要
HyperDashi物品管理システムのフロントエンドをPreact + TypeScript + MUI + Viteで実装する。
Excel風のUIを提供し、物品管理・貸出管理・画像アップロード機能を持つWebアプリケーションを構築する。
## 技術スタック
- **フレームワーク**: Preact 10.x
- **言語**: TypeScript 5.x
- **UIライブラリ**: MUI (Material-UI) 5.x
- **ビルドツール**: Vite 5.x
- **状態管理**: Preact/signals または Zustand
- **HTTP クライアント**: Fetch API または Axios
- **ルーティング**: Preact Router
- **フォームバリデーション**: React Hook Form + Yup/Zod
- **データテーブル**: MUI DataGrid または react-table
- **ファイルアップロード**: react-dropzone
## バックエンドAPI仕様
### ベースURL
```
http://127.0.0.1:8081
```
### 認証
現在は認証なし(将来的にJWTトークン認証を追加予定)
### エンドポイント一覧
#### 1. 基本エンドポイント
- `GET /` - サーバー情報
- `GET /health` - ヘルスチェック
#### 2. 物品管理API
**物品一覧取得**
```http
GET /api/v1/items?page=1&per_page=20&search=keywords&is_on_loan=false&is_disposed=false
```
Response:
```typescript
interface ItemsListResponse {
items: Item[];
total: number;
page: number;
per_page: number;
}
interface Item {
id: number;
name: string;
label_id: string;
model_number?: string;
remarks?: string;
purchase_year?: number;
purchase_amount?: number;
durability_years?: number;
is_depreciation_target?: boolean;
connection_names?: string[];
cable_color_pattern?: string[];
storage_locations?: string[];
is_on_loan?: boolean;
qr_code_type?: 'qr' | 'barcode' | 'none';
is_disposed?: boolean;
image_url?: string;
created_at: string;
updated_at: string;
}
```
**物品作成**
```http
POST /api/v1/items
Content-Type: application/json
{
"name": "テストマイク",
"label_id": "MIC001",
"model_number": "Sony WM-1000XM4",
"remarks": "高品質ワイヤレスマイク",
"purchase_year": 2023,
"purchase_amount": 35000.0,
"durability_years": 5,
"is_depreciation_target": true,
"connection_names": ["XLR", "USB-C"],
"cable_color_pattern": ["red", "black"],
"storage_locations": ["部屋A", "ラックB", "コンテナ1"],
"qr_code_type": "qr",
"image_url": "http://example.com/image.jpg"
}
```
**物品詳細取得**
```http
GET /api/v1/items/:id
```
**物品更新**
```http
PUT /api/v1/items/:id
Content-Type: application/json
```
**物品削除**
```http
DELETE /api/v1/items/:id
```
**物品廃棄**
```http
POST /api/v1/items/:id/dispose
```
**ラベルID検索**
```http
GET /api/v1/items/by-label/:label_id
```
#### 3. 貸出管理API
**貸出一覧取得**
```http
GET /api/v1/loans?page=1&per_page=20&item_id=1&student_number=12345&active_only=true
```
Response:
```typescript
interface LoansListResponse {
loans: LoanWithItem[];
total: number;
page: number;
per_page: number;
}
interface LoanWithItem {
id: number;
item_id: number;
item_name: string;
item_label_id: string;
student_number: string;
student_name: string;
organization?: string;
loan_date: string;
return_date?: string;
remarks?: string;
created_at: string;
updated_at: string;
}
interface Loan {
id: number;
item_id: number;
student_number: string;
student_name: string;
organization?: string;
loan_date: string;
return_date?: string;
remarks?: string;
created_at: string;
updated_at: string;
}
```
**貸出作成**
```http
POST /api/v1/loans
Content-Type: application/json
{
"item_id": 1,
"student_number": "12345678",
"student_name": "山田太郎",
"organization": "情報メディアシステム学類",
"remarks": "授業用"
}
```
**貸出詳細取得**
```http
GET /api/v1/loans/:id
```
**返却処理**
```http
POST /api/v1/loans/:id/return
Content-Type: application/json
{
"return_date": "2024-01-15T10:30:00Z", // optional, defaults to now
"remarks": "正常に返却"
}
```
#### 4. 画像管理API
**画像アップロード**
```http
POST /api/v1/images/upload
Content-Type: multipart/form-data
Form fields:
- image: File (JPEG, PNG, GIF, WebP, max 10MB)
```
Response:
```typescript
interface ImageUploadResponse {
url: string;
filename: string;
size: number;
}
```
### エラーレスポンス
```typescript
interface ErrorResponse {
error: string;
message?: string;
}
```
## UI/UX要件
### 1. 全体レイアウト
**ヘッダー**
- アプリ名: "HyperDashi"
- ナビゲーションメニュー: 物品管理、貸出管理
- 検索バー(グローバル検索)
**サイドナビ**
- 物品管理
- 物品一覧
- 物品登録
- 廃棄物品
- 貸出管理
- 貸出一覧
- 新規貸出
- 返却処理
**メインコンテンツエリア**
- ページタイトル
- アクションボタン
- データテーブルまたはフォーム
### 2. 物品管理画面
#### 物品一覧画面 (`/items`)
**機能要件**
- Excel風のデータグリッド表示
- ページネーション(20件/ページ)
- 検索機能(物品名、ラベルID、型番、備考)
- フィルタリング(貸出状態、廃棄状態)
- ソート機能(各カラム)
- 行選択(単一・複数)
- アクション(編集、削除、廃棄、QRコード表示)
**表示カラム**
- ラベルID
- 物品名
- 型番
- 購入年度
- 購入金額
- 貸出状態(チップ表示)
- 廃棄状態(チップ表示)
- 最終更新日
- アクション(編集、削除ボタン)
**UI要素**
```tsx
// フィルター・検索バー
}}
/>
貸出状態
廃棄状態
} onClick={handleAddNew}>
新規登録
// データグリッド
```
#### 物品登録・編集画面 (`/items/new`, `/items/:id/edit`)
**機能要件**
- 必須フィールドバリデーション
- リアルタイムバリデーション
- 画像アップロード(ドラッグ&ドロップ対応)
- 配列フィールド(接続名、ケーブル色、収納場所)の動的追加・削除
- フォーム状態の保存(一時保存機能)
**フォームフィールド**
```tsx
interface ItemFormData {
name: string; // 必須
label_id: string; // 必須、英数字のみ
model_number?: string;
remarks?: string;
purchase_year?: number; // 1900-2100
purchase_amount?: number; // 0以上
durability_years?: number; // 1-100
is_depreciation_target?: boolean;
connection_names?: string[]; // 動的配列
cable_color_pattern?: string[]; // 動的配列
storage_locations?: string[]; // 動的配列
qr_code_type?: 'qr' | 'barcode' | 'none';
image_url?: string;
}
```
**バリデーションルール**
```typescript
const itemValidationSchema = yup.object({
name: yup.string().required('物品名は必須です').max(255, '255文字以内で入力してください'),
label_id: yup
.string()
.required('ラベルIDは必須です')
.matches(/^[A-Za-z0-9]+$/, '英数字のみ使用可能です')
.max(50, '50文字以内で入力してください'),
model_number: yup.string().max(255, '255文字以内で入力してください'),
purchase_year: yup
.number()
.min(1900, '1900年以降を入力してください')
.max(2100, '2100年以前を入力してください'),
purchase_amount: yup.number().min(0, '0以上の値を入力してください'),
durability_years: yup
.number()
.min(1, '1年以上を入力してください')
.max(100, '100年以下を入力してください'),
});
```
**画像アップロード UI**
```tsx
物品画像
{({getRootProps, getInputProps, isDragActive}) => (
{isDragActive
? 'ここにファイルをドロップ'
: '画像をドラッグ&ドロップまたはクリックして選択'}
JPEG, PNG, GIF, WebP (最大10MB)
)}
{previewUrl && (
}>
画像を削除
)}
```
### 3. 貸出管理画面
#### 貸出一覧画面 (`/loans`)
**機能要件**
- 貸出履歴の一覧表示
- フィルタリング(物品ID、学籍番号、アクティブ貸出のみ)
- ソート機能
- 返却処理(一括・個別)
- 貸出状況の可視化
**表示カラム**
- 貸出ID
- 物品名(ラベルID)
- 学籍番号
- 氏名
- 所属
- 貸出日
- 返却予定日
- 返却日
- 状態(貸出中/返却済み)
- アクション
**フィルター UI**
```tsx
`${option.name} (${option.label_id})`}
renderInput={(params) => (
)}
onChange={handleItemFilterChange}
/>
状態
} onClick={handleNewLoan}>
新規貸出
```
#### 新規貸出画面 (`/loans/new`)
**機能要件**
- 物品選択(利用可能な物品のみ)
- 学生情報入力
- バリデーション
- 貸出可能性チェック
**フォームフィールド**
```typescript
interface LoanFormData {
item_id: number; // 必須
student_number: string; // 必須
student_name: string; // 必須
organization?: string;
remarks?: string;
}
```
#### 返却処理画面 (`/loans/:id/return`)
**機能要件**
- 返却日時設定(デフォルト:現在時刻)
- 返却時の備考入力
- 物品状態確認
### 4. 共通コンポーネント
#### レスポンシブデザイン
- モバイル対応(768px以下)
- タブレット対応(768px-1024px)
- デスクトップ対応(1024px以上)
#### 通知システム
```tsx
// Snackbar通知
const showNotification = (message: string, severity: 'success' | 'error' | 'warning' | 'info') => {
// MUI Snackbar + Alert実装
};
// 使用例
showNotification('物品が正常に登録されました', 'success');
showNotification('エラーが発生しました', 'error');
```
#### ローディング状態
```tsx
// スケルトンローディング
// プログレスインジケーター
// データテーブルローディング
```
#### エラーハンドリング
```tsx
// エラーバウンダリ
}>
// APIエラー処理
const handleApiError = (error: any) => {
if (error.response?.status === 404) {
showNotification('データが見つかりません', 'error');
} else if (error.response?.status >= 500) {
showNotification('サーバーエラーが発生しました', 'error');
} else {
showNotification('予期しないエラーが発生しました', 'error');
}
};
```
## プロジェクト構成
```
src/
├── components/ # 共通コンポーネント
│ ├── ui/ # 基本UIコンポーネント
│ ├── forms/ # フォームコンポーネント
│ ├── tables/ # テーブルコンポーネント
│ └── layout/ # レイアウトコンポーネント
├── pages/ # ページコンポーネント
│ ├── items/ # 物品管理ページ
│ ├── loans/ # 貸出管理ページ
│ └── dashboard/ # ダッシュボード
├── hooks/ # カスタムフック
├── services/ # API呼び出し
├── types/ # TypeScript型定義
├── utils/ # ユーティリティ関数
├── stores/ # 状態管理
└── constants/ # 定数定義
```
## 実装優先度
### Phase 1: 基本機能
1. プロジェクトセットアップ(Vite + Preact + TypeScript + MUI)
2. ルーティング設定
3. レイアウトコンポーネント
4. 物品一覧画面
5. 物品登録・編集画面
### Phase 2: 拡張機能
1. 貸出管理機能
2. 画像アップロード機能
3. 検索・フィルタリング機能強化
4. レスポンシブデザイン
### Phase 3: 改善・最適化
1. パフォーマンス最適化
2. エラーハンドリング強化
3. ユーザビリティ改善
4. テスト実装
## 開発環境設定
### 必要なパッケージ
```json
{
"dependencies": {
"preact": "^10.19.0",
"@preact/signals": "^1.2.0",
"preact-router": "^4.1.0",
"@mui/material": "^5.15.0",
"@mui/icons-material": "^5.15.0",
"@mui/x-data-grid": "^6.18.0",
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"react-hook-form": "^7.48.0",
"yup": "^1.4.0",
"@hookform/resolvers": "^3.3.0",
"react-dropzone": "^14.2.0",
"axios": "^1.6.0"
},
"devDependencies": {
"@preact/preset-vite": "^2.8.0",
"vite": "^5.0.0",
"typescript": "^5.2.0",
"@types/node": "^20.10.0"
}
}
```
### Vite設定 (vite.config.ts)
```typescript
import { defineConfig } from 'vite';
import preact from '@preact/preset-vite';
export default defineConfig({
plugins: [preact()],
server: {
host: '0.0.0.0',
port: 3000,
proxy: {
'/api': {
target: 'http://127.0.0.1:8081',
changeOrigin: true,
},
},
},
build: {
target: 'esnext',
},
});
```
### TypeScript設定 (tsconfig.json)
```json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "preact",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
```
## API クライアント実装例
```typescript
// src/services/api.ts
import axios from 'axios';
const api = axios.create({
baseURL: '/api/v1',
headers: {
'Content-Type': 'application/json',
},
});
// リクエストインターセプター
api.interceptors.request.use((config) => {
// 将来的にJWTトークンを追加
return config;
});
// レスポンスインターセプター
api.interceptors.response.use(
(response) => response,
(error) => {
// エラーハンドリング
console.error('API Error:', error);
return Promise.reject(error);
}
);
export default api;
// src/services/items.ts
export const itemsApi = {
getItems: (params: GetItemsParams) =>
api.get('/items', { params }),
getItem: (id: number) =>
api.get- (`/items/${id}`),
createItem: (data: CreateItemRequest) =>
api.post
- ('/items', data),
updateItem: (id: number, data: UpdateItemRequest) =>
api.put
- (`/items/${id}`, data),
deleteItem: (id: number) =>
api.delete(`/items/${id}`),
disposeItem: (id: number) =>
api.post
- (`/items/${id}/dispose`),
};
```
この仕様書に基づいて、モダンで使いやすいExcel風UIを持つHyperDashiフロントエンドを実装してください。