# Add-on Request System - Frontend Documentation

## Table of Contents
1. [Overview](#overview)
2. [API Endpoints](#api-endpoints)
3. [Data Structures](#data-structures)
4. [UI Components](#ui-components)
5. [State Management](#state-management)
6. [Implementation Examples](#implementation-examples)
7. [Error Handling](#error-handling)
8. [Best Practices](#best-practices)

## Overview

The Add-on Request System allows business users to create additional service requests for bookings/travelers, which are then processed by different departments (Ticketing, Line Control, Operations) through a structured workflow.

### Key Features
- **Three-department workflow**: Ticketing, Line Control, Operations
- **Multi-level approval system** with role-based permissions
- **Real-time status tracking** with complete audit trail
- **File upload and management**
- **Cost calculation** with itemized breakdown
- **Priority management** for urgent requests
- **Dashboard and reporting**

## API Endpoints

### Base URL
```
/api/addon-requests
```

### Authentication
All endpoints require authentication via Sanctum token:
```javascript
headers: {
  'Authorization': 'Bearer ' + token,
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}
```

### Core Endpoints

#### 1. Categories & Types

```javascript
// Get all categories
GET /api/addon-requests/categories
Response: {
  success: true,
  data: [
    {
      id: 1,
      name: "票務",
      name_en: "Ticketing",
      department_code: "TICKETING",
      is_active: true,
      sort_order: 1
    }
  ]
}

// Get types for a category
GET /api/addon-requests/categories/{categoryId}/types
Response: {
  success: true,
  data: [
    {
      id: 1,
      category_id: 1,
      name: "機票",
      name_en: "Flight Ticket",
      base_cost: 1500.00,
      requires_approval: true,
      processing_time_hours: 48
    }
  ]
}

// Get all types (with optional category filter)
GET /api/addon-requests/types?category_id=1
```

#### 2. Main CRUD Operations

```javascript
// List requests with filtering and pagination
GET /api/addon-requests?status=pending&priority=high&page=1&per_page=15
Response: {
  success: true,
  data: {
    data: [...], // Request objects
    current_page: 1,
    total: 50,
    per_page: 15
  },
  meta: {
    status_options: [...],
    priority_options: [...]
  }
}

// Create new request
POST /api/addon-requests
Body: {
  booking_id: 123,
  traveler_id: 456, // optional
  type_id: 1,
  priority: "normal",
  title: "機票加購需求",
  description: "需要升等商務艙",
  estimated_cost: 5000.00,
  requested_completion_date: "2025-11-10T10:00:00Z",
  items: [
    {
      item_name: "商務艙升等",
      quantity: 1,
      unit_cost: 5000.00,
      total_cost: 5000.00
    }
  ]
}

// Get single request
GET /api/addon-requests/{requestId}
Response: {
  success: true,
  data: {
    id: 1,
    request_number: "AR-20251104-0001",
    status: "pending",
    priority: "normal",
    title: "機票加購需求",
    booking: {...},
    traveler: {...},
    category: {...},
    type: {...},
    items: [...],
    status_history: [...],
    approvals: [...],
    files: [...]
  }
}

// Update request
PUT /api/addon-requests/{requestId}
Body: {
  title: "Updated title",
  description: "Updated description",
  estimated_cost: 6000.00
}

// Delete request (only pending status)
DELETE /api/addon-requests/{requestId}
```

#### 3. Status Management

```javascript
// Update status
PATCH /api/addon-requests/{requestId}/status
Body: {
  status: "in_progress",
  notes: "開始處理此需求"
}

// Assign to user
PATCH /api/addon-requests/{requestId}/assign
Body: {
  assigned_to_user_id: 123
}
```

#### 4. Items Management

```javascript
// Get request items
GET /api/addon-requests/{requestId}/items

// Add item
POST /api/addon-requests/{requestId}/items
Body: {
  item_name: "額外服務",
  quantity: 2,
  unit_cost: 500.00,
  total_cost: 1000.00
}

// Update item
PUT /api/addon-requests/{requestId}/items/{itemId}
Body: {
  quantity: 3,
  total_cost: 1500.00
}

// Delete item
DELETE /api/addon-requests/{requestId}/items/{itemId}
```

#### 5. Approvals

```javascript
// Get approvals for request
GET /api/addon-requests/{requestId}/approvals
Response: {
  success: true,
  data: [...],
  meta: {
    next_approval_level: 1,
    requires_approval: true,
    can_user_approve: false
  }
}

// Approve request
PATCH /api/addon-requests/approvals/{approvalId}/approve
Body: {
  notes: "核准此需求"
}

// Reject request
PATCH /api/addon-requests/approvals/{approvalId}/reject
Body: {
  notes: "需求不符合規定，請重新提交"
}
```

#### 6. File Management

```javascript
// Get request files
GET /api/addon-requests/{requestId}/files

// Upload file
POST /api/addon-requests/{requestId}/files
Body: FormData {
  file: File,
  file_type: "quote", // quote, invoice, receipt, contract, other
  description: "報價單"
}

// Delete file
DELETE /api/addon-requests/files/{fileId}
```

#### 7. Dashboard & Reports

```javascript
// Dashboard statistics
GET /api/addon-requests/dashboard/stats
Response: {
  success: true,
  data: {
    total_requests: 150,
    pending_requests: 25,
    in_progress_requests: 30,
    completed_requests: 90,
    overdue_requests: 5,
    total_cost: 250000.00,
    average_processing_time: 36.5
  }
}

// Reports by category
GET /api/addon-requests/reports/by-category?date_from=2025-10-01&date_to=2025-10-31

// Reports by status
GET /api/addon-requests/reports/by-status?date_from=2025-10-01&date_to=2025-10-31
```

#### 8. Booking/Traveler Specific

```javascript
// Get requests for specific booking
GET /api/bookings/{bookingId}/addon-requests

// Get requests for specific traveler
GET /api/travelers/{travelerId}/addon-requests
```

## Data Structures

### Request Object
```typescript
interface AddonRequest {
  id: number;
  request_number: string;
  booking_id: number;
  traveler_id?: number;
  category_id: number;
  type_id: number;
  requested_by_user_id: number;
  assigned_to_user_id?: number;
  status: 'pending' | 'in_progress' | 'confirmed' | 'completed' | 'cancelled' | 'rejected';
  priority: 'low' | 'normal' | 'high' | 'urgent';
  title: string;
  description?: string;
  special_requirements?: string;
  estimated_cost?: number;
  final_cost?: number;
  cost_currency: string;
  requested_completion_date?: string;
  actual_completion_date?: string;
  business_notes?: string;
  department_notes?: string;
  rejection_reason?: string;
  metadata?: any;
  created_at: string;
  updated_at: string;
  
  // Relationships
  booking?: Booking;
  traveler?: Traveler;
  category?: Category;
  type?: Type;
  requested_by?: User;
  assigned_to?: User;
  items?: RequestItem[];
  status_history?: StatusHistory[];
  approvals?: Approval[];
  files?: RequestFile[];
  
  // Computed properties
  status_label?: string;
  priority_label?: string;
  formatted_final_cost?: string;
  is_overdue?: boolean;
}
```

### Category Object
```typescript
interface Category {
  id: number;
  name: string;
  name_en?: string;
  description?: string;
  department_code: 'TICKETING' | 'LINE_CONTROL' | 'OPERATIONS';
  responsible_role_id?: number;
  is_active: boolean;
  sort_order: number;
  
  // Relationships
  types?: Type[];
  responsible_role?: Role;
}
```

### Type Object
```typescript
interface Type {
  id: number;
  category_id: number;
  name: string;
  name_en?: string;
  description?: string;
  base_cost?: number;
  cost_currency: string;
  requires_approval: boolean;
  processing_time_hours: number;
  is_active: boolean;
  sort_order: number;
  metadata?: any;
  
  // Relationships
  category?: Category;
  
  // Computed properties
  formatted_base_cost?: string;
  estimated_completion_time?: string;
}
```

### Request Item Object
```typescript
interface RequestItem {
  id: number;
  addon_request_id: number;
  item_name: string;
  item_description?: string;
  quantity: number;
  unit_cost?: number;
  total_cost?: number;
  status: 'pending' | 'confirmed' | 'completed' | 'cancelled';
  notes?: string;
  metadata?: any;
  created_at: string;
  updated_at: string;
  
  // Computed properties
  status_label?: string;
  formatted_total_cost?: string;
}
```

### Status History Object
```typescript
interface StatusHistory {
  id: number;
  addon_request_id: number;
  previous_status?: string;
  new_status: string;
  changed_by_user_id: number;
  notes?: string;
  created_at: string;
  
  // Relationships
  changed_by?: User;
  
  // Computed properties
  previous_status_label?: string;
  new_status_label?: string;
}
```

### Approval Object
```typescript
interface Approval {
  id: number;
  addon_request_id: number;
  approver_user_id: number;
  approval_level: number;
  status: 'pending' | 'approved' | 'rejected';
  approved_at?: string;
  notes?: string;
  created_at: string;
  updated_at: string;
  
  // Relationships
  approver?: User;
  
  // Computed properties
  status_label?: string;
  is_approved?: boolean;
  is_rejected?: boolean;
  is_pending?: boolean;
}
```

### Request File Object
```typescript
interface RequestFile {
  id: number;
  addon_request_id: number;
  file_storage_id: number;
  file_type: 'quote' | 'invoice' | 'receipt' | 'contract' | 'other';
  uploaded_by_user_id: number;
  description?: string;
  created_at: string;
  updated_at: string;
  
  // Relationships
  file_storage?: FileStorage;
  uploaded_by?: User;
  
  // Computed properties
  file_type_label?: string;
  file_url?: string;
  file_name?: string;
  file_size?: number;
}
```

## UI Components

### 1. Request List Component

```vue
<template>
  <div class="addon-request-list">
    <!-- Filters -->
    <div class="filters">
      <select v-model="filters.status">
        <option value="">所有狀態</option>
        <option v-for="status in statusOptions" :key="status.value" :value="status.value">
          {{ status.label }}
        </option>
      </select>
      
      <select v-model="filters.priority">
        <option value="">所有優先級</option>
        <option v-for="priority in priorityOptions" :key="priority.value" :value="priority.value">
          {{ priority.label }}
        </option>
      </select>
      
      <select v-model="filters.category_id">
        <option value="">所有分類</option>
        <option v-for="category in categories" :key="category.id" :value="category.id">
          {{ category.name }}
        </option>
      </select>
    </div>
    
    <!-- Request Cards -->
    <div class="request-cards">
      <div 
        v-for="request in requests" 
        :key="request.id" 
        class="request-card"
        :class="[`status-${request.status}`, `priority-${request.priority}`]"
      >
        <div class="card-header">
          <h3>{{ request.title }}</h3>
          <div class="badges">
            <span class="status-badge" :class="`status-${request.status}`">
              {{ request.status_label }}
            </span>
            <span class="priority-badge" :class="`priority-${request.priority}`">
              {{ request.priority_label }}
            </span>
          </div>
        </div>
        
        <div class="card-body">
          <p><strong>需求編號:</strong> {{ request.request_number }}</p>
          <p><strong>分類:</strong> {{ request.category?.name }}</p>
          <p><strong>類型:</strong> {{ request.type?.name }}</p>
          <p><strong>預估費用:</strong> {{ request.formatted_final_cost || 'N/A' }}</p>
          <p><strong>建立時間:</strong> {{ formatDate(request.created_at) }}</p>
          
          <div v-if="request.is_overdue" class="overdue-warning">
            ⚠️ 已逾期
          </div>
        </div>
        
        <div class="card-actions">
          <button @click="viewRequest(request.id)" class="btn btn-primary">
            查看詳情
          </button>
          <button 
            v-if="canEdit(request)" 
            @click="editRequest(request.id)" 
            class="btn btn-secondary"
          >
            編輯
          </button>
        </div>
      </div>
    </div>
    
    <!-- Pagination -->
    <pagination 
      :current-page="currentPage"
      :total-pages="totalPages"
      @page-change="loadRequests"
    />
  </div>
</template>

<script>
export default {
  name: 'AddonRequestList',
  data() {
    return {
      requests: [],
      categories: [],
      statusOptions: [],
      priorityOptions: [],
      filters: {
        status: '',
        priority: '',
        category_id: ''
      },
      currentPage: 1,
      totalPages: 1,
      loading: false
    }
  },
  async mounted() {
    await this.loadCategories();
    await this.loadRequests();
  },
  methods: {
    async loadRequests(page = 1) {
      this.loading = true;
      try {
        const params = new URLSearchParams({
          page: page.toString(),
          per_page: '15',
          ...this.filters
        });
        
        const response = await this.$http.get(`/api/addon-requests?${params}`);
        this.requests = response.data.data.data;
        this.currentPage = response.data.data.current_page;
        this.totalPages = response.data.data.last_page;
        this.statusOptions = response.data.meta.status_options;
        this.priorityOptions = response.data.meta.priority_options;
      } catch (error) {
        this.$toast.error('載入加購需求失敗');
      } finally {
        this.loading = false;
      }
    },
    
    async loadCategories() {
      try {
        const response = await this.$http.get('/api/addon-requests/categories');
        this.categories = response.data.data;
      } catch (error) {
        console.error('載入分類失敗:', error);
      }
    },
    
    viewRequest(id) {
      this.$router.push(`/addon-requests/${id}`);
    },
    
    editRequest(id) {
      this.$router.push(`/addon-requests/${id}/edit`);
    },
    
    canEdit(request) {
      return ['pending', 'in_progress'].includes(request.status) && 
             (request.requested_by_user_id === this.$auth.user.id || this.$auth.user.hasRole('admin'));
    },
    
    formatDate(date) {
      return new Date(date).toLocaleDateString('zh-TW');
    }
  },
  watch: {
    filters: {
      handler() {
        this.loadRequests(1);
      },
      deep: true
    }
  }
}
</script>
```

### 2. Request Form Component

```vue
<template>
  <div class="addon-request-form">
    <form @submit.prevent="submitForm">
      <!-- Basic Information -->
      <div class="form-section">
        <h3>基本資訊</h3>
        
        <div class="form-group">
          <label>訂單 *</label>
          <select v-model="form.booking_id" required>
            <option value="">請選擇訂單</option>
            <option v-for="booking in bookings" :key="booking.id" :value="booking.id">
              {{ booking.booking_number }} - {{ booking.planned_trip?.itinerary_code }}
            </option>
          </select>
        </div>
        
        <div class="form-group" v-if="travelers.length">
          <label>旅客</label>
          <select v-model="form.traveler_id">
            <option value="">請選擇旅客（可選）</option>
            <option v-for="traveler in travelers" :key="traveler.id" :value="traveler.id">
              {{ traveler.chinese_first_name }} {{ traveler.chinese_last_name }}
            </option>
          </select>
        </div>
        
        <div class="form-group">
          <label>分類 *</label>
          <select v-model="form.category_id" @change="loadTypes" required>
            <option value="">請選擇分類</option>
            <option v-for="category in categories" :key="category.id" :value="category.id">
              {{ category.name }}
            </option>
          </select>
        </div>
        
        <div class="form-group" v-if="types.length">
          <label>類型 *</label>
          <select v-model="form.type_id" @change="onTypeChange" required>
            <option value="">請選擇類型</option>
            <option v-for="type in types" :key="type.id" :value="type.id">
              {{ type.name }} 
              <span v-if="type.base_cost">({{ type.formatted_base_cost }})</span>
            </option>
          </select>
        </div>
        
        <div class="form-group">
          <label>優先級</label>
          <select v-model="form.priority">
            <option v-for="priority in priorityOptions" :key="priority.value" :value="priority.value">
              {{ priority.label }}
            </option>
          </select>
        </div>
      </div>
      
      <!-- Request Details -->
      <div class="form-section">
        <h3>需求詳情</h3>
        
        <div class="form-group">
          <label>標題 *</label>
          <input v-model="form.title" type="text" required maxlength="255">
        </div>
        
        <div class="form-group">
          <label>描述</label>
          <textarea v-model="form.description" rows="4" maxlength="1000"></textarea>
        </div>
        
        <div class="form-group">
          <label>特殊需求</label>
          <textarea v-model="form.special_requirements" rows="3" maxlength="1000"></textarea>
        </div>
        
        <div class="form-row">
          <div class="form-group">
            <label>預估費用</label>
            <input v-model.number="form.estimated_cost" type="number" step="0.01" min="0">
          </div>
          
          <div class="form-group">
            <label>貨幣</label>
            <select v-model="form.cost_currency">
              <option value="TWD">TWD</option>
              <option value="USD">USD</option>
              <option value="EUR">EUR</option>
              <option value="JPY">JPY</option>
            </select>
          </div>
        </div>
        
        <div class="form-group">
          <label>希望完成日期</label>
          <input v-model="form.requested_completion_date" type="datetime-local">
        </div>
        
        <div class="form-group">
          <label>業務備註</label>
          <textarea v-model="form.business_notes" rows="3" maxlength="1000"></textarea>
        </div>
      </div>
      
      <!-- Items -->
      <div class="form-section">
        <h3>項目明細</h3>
        
        <div v-for="(item, index) in form.items" :key="index" class="item-row">
          <div class="form-row">
            <div class="form-group">
              <label>項目名稱 *</label>
              <input v-model="item.item_name" type="text" required>
            </div>
            
            <div class="form-group">
              <label>數量 *</label>
              <input v-model.number="item.quantity" type="number" min="1" required @input="calculateItemTotal(index)">
            </div>
            
            <div class="form-group">
              <label>單價</label>
              <input v-model.number="item.unit_cost" type="number" step="0.01" min="0" @input="calculateItemTotal(index)">
            </div>
            
            <div class="form-group">
              <label>總價</label>
              <input v-model.number="item.total_cost" type="number" step="0.01" min="0" readonly>
            </div>
            
            <div class="form-group">
              <button type="button" @click="removeItem(index)" class="btn btn-danger btn-sm">
                刪除
              </button>
            </div>
          </div>
          
          <div class="form-group">
            <label>項目描述</label>
            <textarea v-model="item.item_description" rows="2"></textarea>
          </div>
        </div>
        
        <button type="button" @click="addItem" class="btn btn-secondary">
          新增項目
        </button>
      </div>
      
      <!-- Actions -->
      <div class="form-actions">
        <button type="button" @click="$router.go(-1)" class="btn btn-secondary">
          取消
        </button>
        <button type="submit" :disabled="loading" class="btn btn-primary">
          {{ loading ? '處理中...' : (isEdit ? '更新' : '建立') }}
        </button>
      </div>
    </form>
  </div>
</template>

<script>
export default {
  name: 'AddonRequestForm',
  props: {
    requestId: {
      type: [String, Number],
      default: null
    }
  },
  data() {
    return {
      form: {
        booking_id: '',
        traveler_id: '',
        category_id: '',
        type_id: '',
        priority: 'normal',
        title: '',
        description: '',
        special_requirements: '',
        estimated_cost: null,
        cost_currency: 'TWD',
        requested_completion_date: '',
        business_notes: '',
        items: []
      },
      bookings: [],
      travelers: [],
      categories: [],
      types: [],
      priorityOptions: [
        { value: 'low', label: '低' },
        { value: 'normal', label: '一般' },
        { value: 'high', label: '高' },
        { value: 'urgent', label: '緊急' }
      ],
      loading: false
    }
  },
  computed: {
    isEdit() {
      return !!this.requestId;
    }
  },
  async mounted() {
    await this.loadCategories();
    await this.loadBookings();
    
    if (this.isEdit) {
      await this.loadRequest();
    }
  },
  methods: {
    async loadCategories() {
      try {
        const response = await this.$http.get('/api/addon-requests/categories');
        this.categories = response.data.data;
      } catch (error) {
        this.$toast.error('載入分類失敗');
      }
    },
    
    async loadTypes() {
      if (!this.form.category_id) {
        this.types = [];
        return;
      }
      
      try {
        const response = await this.$http.get(`/api/addon-requests/categories/${this.form.category_id}/types`);
        this.types = response.data.data;
      } catch (error) {
        this.$toast.error('載入類型失敗');
      }
    },
    
    async loadBookings() {
      try {
        const response = await this.$http.get('/api/bookings');
        this.bookings = response.data.data;
      } catch (error) {
        this.$toast.error('載入訂單失敗');
      }
    },
    
    async loadTravelers() {
      if (!this.form.booking_id) {
        this.travelers = [];
        return;
      }
      
      try {
        const response = await this.$http.get(`/api/bookings/${this.form.booking_id}/travelers`);
        this.travelers = response.data.data;
      } catch (error) {
        console.error('載入旅客失敗:', error);
      }
    },
    
    async loadRequest() {
      try {
        const response = await this.$http.get(`/api/addon-requests/${this.requestId}`);
        const request = response.data.data;
        
        this.form = {
          booking_id: request.booking_id,
          traveler_id: request.traveler_id || '',
          category_id: request.category_id,
          type_id: request.type_id,
          priority: request.priority,
          title: request.title,
          description: request.description || '',
          special_requirements: request.special_requirements || '',
          estimated_cost: request.estimated_cost,
          cost_currency: request.cost_currency,
          requested_completion_date: request.requested_completion_date ? 
            new Date(request.requested_completion_date).toISOString().slice(0, 16) : '',
          business_notes: request.business_notes || '',
          items: request.items || []
        };
        
        await this.loadTypes();
        await this.loadTravelers();
      } catch (error) {
        this.$toast.error('載入需求失敗');
        this.$router.go(-1);
      }
    },
    
    onTypeChange() {
      const selectedType = this.types.find(t => t.id == this.form.type_id);
      if (selectedType && selectedType.base_cost) {
        this.form.estimated_cost = selectedType.base_cost;
      }
    },
    
    addItem() {
      this.form.items.push({
        item_name: '',
        item_description: '',
        quantity: 1,
        unit_cost: null,
        total_cost: null
      });
    },
    
    removeItem(index) {
      this.form.items.splice(index, 1);
    },
    
    calculateItemTotal(index) {
      const item = this.form.items[index];
      if (item.quantity && item.unit_cost) {
        item.total_cost = item.quantity * item.unit_cost;
      }
    },
    
    async submitForm() {
      this.loading = true;
      
      try {
        const url = this.isEdit ? 
          `/api/addon-requests/${this.requestId}` : 
          '/api/addon-requests';
        const method = this.isEdit ? 'put' : 'post';
        
        const response = await this.$http[method](url, this.form);
        
        this.$toast.success(response.data.message);
        this.$router.push(`/addon-requests/${response.data.data.id}`);
      } catch (error) {
        if (error.response?.data?.errors) {
          // Handle validation errors
          const errors = error.response.data.errors;
          Object.keys(errors).forEach(key => {
            errors[key].forEach(message => {
              this.$toast.error(message);
            });
          });
        } else {
          this.$toast.error(error.response?.data?.message || '操作失敗');
        }
      } finally {
        this.loading = false;
      }
    }
  },
  watch: {
    'form.booking_id'() {
      this.loadTravelers();
    },
    
    'form.category_id'() {
      this.form.type_id = '';
      this.loadTypes();
    }
  }
}
</script>
```

### 3. Request Detail Component

```vue
<template>
  <div class="addon-request-detail" v-if="request">
    <!-- Header -->
    <div class="detail-header">
      <div class="header-left">
        <h1>{{ request.title }}</h1>
        <p class="request-number">需求編號: {{ request.request_number }}</p>
      </div>
      
      <div class="header-right">
        <span class="status-badge" :class="`status-${request.status}`">
          {{ request.status_label }}
        </span>
        <span class="priority-badge" :class="`priority-${request.priority}`">
          {{ request.priority_label }}
        </span>
      </div>
    </div>
    
    <!-- Actions -->
    <div class="detail-actions" v-if="canPerformActions">
      <button 
        v-if="canEdit" 
        @click="editRequest" 
        class="btn btn-primary"
      >
        編輯
      </button>
      
      <button 
        v-if="canUpdateStatus" 
        @click="showStatusModal = true" 
        class="btn btn-secondary"
      >
        更新狀態
      </button>
      
      <button 
        v-if="canAssign" 
        @click="showAssignModal = true" 
        class="btn btn-secondary"
      >
        指派
      </button>
      
      <button 
        v-if="canApprove" 
        @click="approveRequest" 
        class="btn btn-success"
      >
        核准
      </button>
      
      <button 
        v-if="canReject" 
        @click="showRejectModal = true" 
        class="btn btn-danger"
      >
        拒絕
      </button>
    </div>
    
    <!-- Main Content -->
    <div class="detail-content">
      <!-- Basic Information -->
      <div class="info-section">
        <h3>基本資訊</h3>
        <div class="info-grid">
          <div class="info-item">
            <label>訂單:</label>
            <span>{{ request.booking?.booking_number }}</span>
          </div>
          
          <div class="info-item" v-if="request.traveler">
            <label>旅客:</label>
            <span>{{ request.traveler.chinese_first_name }} {{ request.traveler.chinese_last_name }}</span>
          </div>
          
          <div class="info-item">
            <label>分類:</label>
            <span>{{ request.category?.name }}</span>
          </div>
          
          <div class="info-item">
            <label>類型:</label>
            <span>{{ request.type?.name }}</span>
          </div>
          
          <div class="info-item">
            <label>建立者:</label>
            <span>{{ request.requested_by?.display_name }}</span>
          </div>
          
          <div class="info-item" v-if="request.assigned_to">
            <label>負責人:</label>
            <span>{{ request.assigned_to.display_name }}</span>
          </div>
          
          <div class="info-item">
            <label>建立時間:</label>
            <span>{{ formatDateTime(request.created_at) }}</span>
          </div>
          
          <div class="info-item" v-if="request.requested_completion_date">
            <label>希望完成時間:</label>
            <span>{{ formatDateTime(request.requested_completion_date) }}</span>
          </div>
        </div>
      </div>
      
      <!-- Description -->
      <div class="info-section" v-if="request.description">
        <h3>描述</h3>
        <p>{{ request.description }}</p>
      </div>
      
      <!-- Special Requirements -->
      <div class="info-section" v-if="request.special_requirements">
        <h3>特殊需求</h3>
        <p>{{ request.special_requirements }}</p>
      </div>
      
      <!-- Cost Information -->
      <div class="info-section">
        <h3>費用資訊</h3>
        <div class="cost-info">
          <div class="cost-item" v-if="request.estimated_cost">
            <label>預估費用:</label>
            <span>{{ formatCurrency(request.estimated_cost, request.cost_currency) }}</span>
          </div>
          
          <div class="cost-item" v-if="request.final_cost">
            <label>最終費用:</label>
            <span class="final-cost">{{ formatCurrency(request.final_cost, request.cost_currency) }}</span>
          </div>
        </div>
      </div>
      
      <!-- Items -->
      <div class="info-section" v-if="request.items?.length">
        <h3>項目明細</h3>
        <div class="items-table">
          <table>
            <thead>
              <tr>
                <th>項目名稱</th>
                <th>數量</th>
                <th>單價</th>
                <th>總價</th>
                <th>狀態</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="item in request.items" :key="item.id">
                <td>
                  <div>{{ item.item_name }}</div>
                  <small v-if="item.item_description">{{ item.item_description }}</small>
                </td>
                <td>{{ item.quantity }}</td>
                <td>{{ item.unit_cost ? formatCurrency(item.unit_cost) : 'N/A' }}</td>
                <td>{{ item.total_cost ? formatCurrency(item.total_cost) : 'N/A' }}</td>
                <td>
                  <span class="status-badge" :class="`status-${item.status}`">
                    {{ item.status_label }}
                  </span>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
      
      <!-- Notes -->
      <div class="info-section" v-if="request.business_notes || request.department_notes">
        <h3>備註</h3>
        
        <div v-if="request.business_notes" class="note-item">
          <h4>業務備註:</h4>
          <p>{{ request.business_notes }}</p>
        </div>
        
        <div v-if="request.department_notes" class="note-item">
          <h4>部門備註:</h4>
          <p>{{ request.department_notes }}</p>
        </div>
      </div>
      
      <!-- Status History -->
      <div class="info-section" v-if="request.status_history?.length">
        <h3>狀態歷史</h3>
        <div class="status-timeline">
          <div 
            v-for="history in request.status_history" 
            :key="history.id" 
            class="timeline-item"
          >
            <div class="timeline-marker"></div>
            <div class="timeline-content">
              <div class="timeline-header">
                <span class="status-change">
                  {{ history.previous_status_label || '初始' }} → {{ history.new_status_label }}
                </span>
                <span class="timeline-date">{{ formatDateTime(history.created_at) }}</span>
              </div>
              <div class="timeline-user">{{ history.changed_by?.display_name }}</div>
              <div v-if="history.notes" class="timeline-notes">{{ history.notes }}</div>
            </div>
          </div>
        </div>
      </div>
      
      <!-- Approvals -->
      <div class="info-section" v-if="request.approvals?.length">
        <h3>審核記錄</h3>
        <div class="approvals-list">
          <div 
            v-for="approval in request.approvals" 
            :key="approval.id" 
            class="approval-item"
            :class="`approval-${approval.status}`"
          >
            <div class="approval-header">
              <span class="approval-level">Level {{ approval.approval_level }}</span>
              <span class="approval-status" :class="`status-${approval.status}`">
                {{ approval.status_label }}
              </span>
            </div>
            <div class="approval-approver">{{ approval.approver?.display_name }}</div>
            <div v-if="approval.approved_at" class="approval-date">
              {{ formatDateTime(approval.approved_at) }}
            </div>
            <div v-if="approval.notes" class="approval-notes">{{ approval.notes }}</div>
          </div>
        </div>
      </div>
      
      <!-- Files -->
      <div class="info-section">
        <h3>附件</h3>
        
        <div v-if="request.files?.length" class="files-list">
          <div v-for="file in request.files" :key="file.id" class="file-item">
            <div class="file-info">
              <span class="file-name">{{ file.file_name }}</span>
              <span class="file-type">{{ file.file_type_label }}</span>
              <span class="file-size">{{ formatFileSize(file.file_size) }}</span>
            </div>
            <div class="file-actions">
              <a :href="file.file_url" target="_blank" class="btn btn-sm btn-primary">
                下載
              </a>
              <button 
                v-if="canDeleteFile(file)" 
                @click="deleteFile(file.id)" 
                class="btn btn-sm btn-danger"
              >
                刪除
              </button>
            </div>
          </div>
        </div>
        
        <div v-if="canUploadFile" class="file-upload">
          <input 
            ref="fileInput" 
            type="file" 
            @change="handleFileUpload" 
            style="display: none"
          >
          <button @click="$refs.fileInput.click()" class="btn btn-secondary">
            上傳附件
          </button>
        </div>
      </div>
    </div>
    
    <!-- Status Update Modal -->
    <modal v-if="showStatusModal" @close="showStatusModal = false">
      <template #header>更新狀態</template>
      <template #body>
        <div class="form-group">
          <label>新狀態:</label>
          <select v-model="statusForm.status">
            <option v-for="status in availableStatuses" :key="status.value" :value="status.value">
              {{ status.label }}
            </option>
          </select>
        </div>
        
        <div class="form-group">
          <label>備註:</label>
          <textarea v-model="statusForm.notes" rows="3"></textarea>
        </div>
      </template>
      <template #footer>
        <button @click="showStatusModal = false" class="btn btn-secondary">取消</button>
        <button @click="updateStatus" class="btn btn-primary">確認</button>
      </template>
    </modal>
    
    <!-- Assignment Modal -->
    <modal v-if="showAssignModal" @close="showAssignModal = false">
      <template #header>指派負責人</template>
      <template #body>
        <div class="form-group">
          <label>負責人:</label>
          <select v-model="assignForm.assigned_to_user_id">
            <option value="">請選擇負責人</option>
            <option v-for="user in availableUsers" :key="user.id" :value="user.id">
              {{ user.display_name }}
            </option>
          </select>
        </div>
      </template>
      <template #footer>
        <button @click="showAssignModal = false" class="btn btn-secondary">取消</button>
        <button @click="assignUser" class="btn btn-primary">確認</button>
      </template>
    </modal>
    
    <!-- Reject Modal -->
    <modal v-if="showRejectModal" @close="showRejectModal = false">
      <template #header>拒絕需求</template>
      <template #body>
        <div class="form-group">
          <label>拒絕原因:</label>
          <textarea v-model="rejectForm.notes" rows="4" required></textarea>
        </div>
      </template>
      <template #footer>
        <button @click="showRejectModal = false" class="btn btn-secondary">取消</button>
        <button @click="rejectRequest" class="btn btn-danger">確認拒絕</button>
      </template>
    </modal>
  </div>
</template>

<script>
export default {
  name: 'AddonRequestDetail',
  props: {
    requestId: {
      type: [String, Number],
      required: true
    }
  },
  data() {
    return {
      request: null,
      loading: false,
      showStatusModal: false,
      showAssignModal: false,
      showRejectModal: false,
      statusForm: {
        status: '',
        notes: ''
      },
      assignForm: {
        assigned_to_user_id: ''
      },
      rejectForm: {
        notes: ''
      },
      availableStatuses: [],
      availableUsers: []
    }
  },
  computed: {
    canPerformActions() {
      return this.request && ['pending', 'in_progress'].includes(this.request.status);
    },
    
    canEdit() {
      return this.request?.requested_by_user_id === this.$auth.user.id || 
             this.$auth.user.hasRole('admin');
    },
    
    canUpdateStatus() {
      return this.request?.assigned_to_user_id === this.$auth.user.id || 
             this.$auth.user.hasRole(['admin', 'manager']);
    },
    
    canAssign() {
      return this.$auth.user.hasRole(['admin', 'manager']);
    },
    
    canApprove() {
      // This would need to check against the approval system
      return false; // Implement based on approval logic
    },
    
    canReject() {
      // This would need to check against the approval system
      return false; // Implement based on approval logic
    },
    
    canUploadFile() {
      return this.canEdit || this.canUpdateStatus;
    }
  },
  async mounted() {
    await this.loadRequest();
  },
  methods: {
    async loadRequest() {
      this.loading = true;
      try {
        const response = await this.$http.get(`/api/addon-requests/${this.requestId}`);
        this.request = response.data.data;
      } catch (error) {
        this.$toast.error('載入需求失敗');
        this.$router.go(-1);
      } finally {
        this.loading = false;
      }
    },
    
    editRequest() {
      this.$router.push(`/addon-requests/${this.requestId}/edit`);
    },
    
    async updateStatus() {
      try {
        const response = await this.$http.patch(
          `/api/addon-requests/${this.requestId}/status`, 
          this.statusForm
        );
        
        this.$toast.success(response.data.message);
        this.showStatusModal = false;
        await this.loadRequest();
      } catch (error) {
        this.$toast.error(error.response?.data?.message || '更新狀態失敗');
      }
    },
    
    async assignUser() {
      try {
        const response = await this.$http.patch(
          `/api/addon-requests/${this.requestId}/assign`, 
          this.assignForm
        );
        
        this.$toast.success(response.data.message);
        this.showAssignModal = false;
        await this.loadRequest();
      } catch (error) {
        this.$toast.error(error.response?.data?.message || '指派失敗');
      }
    },
    
    async handleFileUpload(event) {
      const file = event.target.files[0];
      if (!file) return;
      
      const formData = new FormData();
      formData.append('file', file);
      formData.append('file_type', 'other');
      formData.append('description', file.name);
      
      try {
        const response = await this.$http.post(
          `/api/addon-requests/${this.requestId}/files`, 
          formData,
          {
            headers: {
              'Content-Type': 'multipart/form-data'
            }
          }
        );
        
        this.$toast.success('檔案上傳成功');
        await this.loadRequest();
      } catch (error) {
        this.$toast.error('檔案上傳失敗');
      }
    },
    
    async deleteFile(fileId) {
      if (!confirm('確定要刪除此檔案嗎？')) return;
      
      try {
        await this.$http.delete(`/api/addon-requests/files/${fileId}`);
        this.$toast.success('檔案已刪除');
        await this.loadRequest();
      } catch (error) {
        this.$toast.error('刪除檔案失敗');
      }
    },
    
    canDeleteFile(file) {
      return file.uploaded_by_user_id === this.$auth.user.id || 
             this.$auth.user.hasRole('admin');
    },
    
    formatDateTime(date) {
      return new Date(date).toLocaleString('zh-TW');
    },
    
    formatCurrency(amount, currency = 'TWD') {
      return new Intl.NumberFormat('zh-TW', {
        style: 'currency',
        currency: currency
      }).format(amount);
    },
    
    formatFileSize(bytes) {
      if (bytes === 0) return '0 Bytes';
      const k = 1024;
      const sizes = ['Bytes', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }
  }
}
</script>
```

### 4. Dashboard Component

```vue
<template>
  <div class="addon-request-dashboard">
    <!-- Statistics Cards -->
    <div class="stats-grid">
      <div class="stat-card">
        <div class="stat-icon">📋</div>
        <div class="stat-content">
          <h3>{{ stats.total_requests }}</h3>
          <p>總需求數</p>
        </div>
      </div>
      
      <div class="stat-card pending">
        <div class="stat-icon">⏳</div>
        <div class="stat-content">
          <h3>{{ stats.pending_requests }}</h3>
          <p>待處理</p>
        </div>
      </div>
      
      <div class="stat-card in-progress">
        <div class="stat-icon">🔄</div>
        <div class="stat-content">
          <h3>{{ stats.in_progress_requests }}</h3>
          <p>處理中</p>
        </div>
      </div>
      
      <div class="stat-card completed">
        <div class="stat-icon">✅</div>
        <div class="stat-content">
          <h3>{{ stats.completed_requests }}</h3>
          <p>已完成</p>
        </div>
      </div>
      
      <div class="stat-card overdue">
        <div class="stat-icon">⚠️</div>
        <div class="stat-content">
          <h3>{{ stats.overdue_requests }}</h3>
          <p>已逾期</p>
        </div>
      </div>
      
      <div class="stat-card cost">
        <div class="stat-icon">💰</div>
        <div class="stat-content">
          <h3>{{ formatCurrency(stats.total_cost) }}</h3>
          <p>總費用</p>
        </div>
      </div>
    </div>
    
    <!-- Charts -->
    <div class="charts-grid">
      <div class="chart-card">
        <h3>依分類統計</h3>
        <canvas ref="categoryChart"></canvas>
      </div>
      
      <div class="chart-card">
        <h3>依狀態統計</h3>
        <canvas ref="statusChart"></canvas>
      </div>
    </div>
    
    <!-- Recent Requests -->
    <div class="recent-requests">
      <h3>最近需求</h3>
      <div class="request-list">
        <div 
          v-for="request in recentRequests" 
          :key="request.id" 
          class="request-item"
          @click="viewRequest(request.id)"
        >
          <div class="request-info">
            <h4>{{ request.title }}</h4>
            <p>{{ request.request_number }} - {{ request.category?.name }}</p>
          </div>
          <div class="request-meta">
            <span class="status-badge" :class="`status-${request.status}`">
              {{ request.status_label }}
            </span>
            <span class="request-date">{{ formatDate(request.created_at) }}</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import Chart from 'chart.js/auto';

export default {
  name: 'AddonRequestDashboard',
  data() {
    return {
      stats: {
        total_requests: 0,
        pending_requests: 0,
        in_progress_requests: 0,
        completed_requests: 0,
        overdue_requests: 0,
        total_cost: 0,
        average_processing_time: 0
      },
      recentRequests: [],
      categoryChart: null,
      statusChart: null
    }
  },
  async mounted() {
    await this.loadStats();
    await this.loadRecentRequests();
    await this.loadChartData();
  },
  methods: {
    async loadStats() {
      try {
        const response = await this.$http.get('/api/addon-requests/dashboard/stats');
        this.stats = response.data.data;
      } catch (error) {
        console.error('載入統計資料失敗:', error);
      }
    },
    
    async loadRecentRequests() {
      try {
        const response = await this.$http.get('/api/addon-requests?per_page=10');
        this.recentRequests = response.data.data.data;
      } catch (error) {
        console.error('載入最近需求失敗:', error);
      }
    },
    
    async loadChartData() {
      try {
        const [categoryResponse, statusResponse] = await Promise.all([
          this.$http.get('/api/addon-requests/reports/by-category'),
          this.$http.get('/api/addon-requests/reports/by-status')
        ]);
        
        this.renderCategoryChart(categoryResponse.data.data);
        this.renderStatusChart(statusResponse.data.data);
      } catch (error) {
        console.error('載入圖表資料失敗:', error);
      }
    },
    
    renderCategoryChart(data) {
      const ctx = this.$refs.categoryChart.getContext('2d');
      
      this.categoryChart = new Chart(ctx, {
        type: 'doughnut',
        data: {
          labels: data.map(item => item.category_name),
          datasets: [{
            data: data.map(item => item.total_requests),
            backgroundColor: [
              '#3b82f6', // blue
              '#10b981', // green
              '#f59e0b', // amber
              '#ef4444', // red
              '#8b5cf6'  // purple
            ]
          }]
        },
        options: {
          responsive: true,
          plugins: {
            legend: {
              position: 'bottom'
            }
          }
        }
      });
    },
    
    renderStatusChart(data) {
      const ctx = this.$refs.statusChart.getContext('2d');
      
      this.statusChart = new Chart(ctx, {
        type: 'bar',
        data: {
          labels: data.map(item => item.status_label),
          datasets: [{
            label: '需求數量',
            data: data.map(item => item.count),
            backgroundColor: data.map(item => {
              const colors = {
                pending: '#fbbf24',
                in_progress: '#3b82f6',
                completed: '#10b981',
                cancelled: '#6b7280',
                rejected: '#ef4444'
              };
              return colors[item.status] || '#6b7280';
            })
          }]
        },
        options: {
          responsive: true,
          scales: {
            y: {
              beginAtZero: true
            }
          }
        }
      });
    },
    
    viewRequest(id) {
      this.$router.push(`/addon-requests/${id}`);
    },
    
    formatCurrency(amount) {
      return new Intl.NumberFormat('zh-TW', {
        style: 'currency',
        currency: 'TWD'
      }).format(amount);
    },
    
    formatDate(date) {
      return new Date(date).toLocaleDateString('zh-TW');
    }
  },
  beforeUnmount() {
    if (this.categoryChart) {
      this.categoryChart.destroy();
    }
    if (this.statusChart) {
      this.statusChart.destroy();
    }
  }
}
</script>
```

## State Management

### Vuex Store Example

```javascript
// store/modules/addonRequests.js
const state = {
  requests: [],
  currentRequest: null,
  categories: [],
  types: [],
  filters: {
    status: '',
    priority: '',
    category_id: ''
  },
  pagination: {
    currentPage: 1,
    totalPages: 1,
    perPage: 15
  },
  loading: false
}

const mutations = {
  SET_REQUESTS(state, requests) {
    state.requests = requests;
  },
  
  SET_CURRENT_REQUEST(state, request) {
    state.currentRequest = request;
  },
  
  SET_CATEGORIES(state, categories) {
    state.categories = categories;
  },
  
  SET_TYPES(state, types) {
    state.types = types;
  },
  
  SET_FILTERS(state, filters) {
    state.filters = { ...state.filters, ...filters };
  },
  
  SET_PAGINATION(state, pagination) {
    state.pagination = { ...state.pagination, ...pagination };
  },
  
  SET_LOADING(state, loading) {
    state.loading = loading;
  },
  
  UPDATE_REQUEST(state, updatedRequest) {
    const index = state.requests.findIndex(r => r.id === updatedRequest.id);
    if (index !== -1) {
      state.requests.splice(index, 1, updatedRequest);
    }
    
    if (state.currentRequest?.id === updatedRequest.id) {
      state.currentRequest = updatedRequest;
    }
  },
  
  ADD_REQUEST(state, newRequest) {
    state.requests.unshift(newRequest);
  },
  
  REMOVE_REQUEST(state, requestId) {
    state.requests = state.requests.filter(r => r.id !== requestId);
  }
}

const actions = {
  async fetchRequests({ commit, state }) {
    commit('SET_LOADING', true);
    
    try {
      const params = new URLSearchParams({
        page: state.pagination.currentPage.toString(),
        per_page: state.pagination.perPage.toString(),
        ...state.filters
      });
      
      const response = await this.$http.get(`/api/addon-requests?${params}`);
      
      commit('SET_REQUESTS', response.data.data.data);
      commit('SET_PAGINATION', {
        currentPage: response.data.data.current_page,
        totalPages: response.data.data.last_page
      });
      
      return response.data;
    } catch (error) {
      throw error;
    } finally {
      commit('SET_LOADING', false);
    }
  },
  
  async fetchRequest({ commit }, requestId) {
    commit('SET_LOADING', true);
    
    try {
      const response = await this.$http.get(`/api/addon-requests/${requestId}`);
      commit('SET_CURRENT_REQUEST', response.data.data);
      return response.data.data;
    } catch (error) {
      throw error;
    } finally {
      commit('SET_LOADING', false);
    }
  },
  
  async createRequest({ commit }, requestData) {
    try {
      const response = await this.$http.post('/api/addon-requests', requestData);
      commit('ADD_REQUEST', response.data.data);
      return response.data.data;
    } catch (error) {
      throw error;
    }
  },
  
  async updateRequest({ commit }, { requestId, requestData }) {
    try {
      const response = await this.$http.put(`/api/addon-requests/${requestId}`, requestData);
      commit('UPDATE_REQUEST', response.data.data);
      return response.data.data;
    } catch (error) {
      throw error;
    }
  },
  
  async updateRequestStatus({ commit }, { requestId, status, notes }) {
    try {
      const response = await this.$http.patch(`/api/addon-requests/${requestId}/status`, {
        status,
        notes
      });
      commit('UPDATE_REQUEST', response.data.data);
      return response.data.data;
    } catch (error) {
      throw error;
    }
  },
  
  async deleteRequest({ commit }, requestId) {
    try {
      await this.$http.delete(`/api/addon-requests/${requestId}`);
      commit('REMOVE_REQUEST', requestId);
    } catch (error) {
      throw error;
    }
  },
  
  async fetchCategories({ commit }) {
    try {
      const response = await this.$http.get('/api/addon-requests/categories');
      commit('SET_CATEGORIES', response.data.data);
      return response.data.data;
    } catch (error) {
      throw error;
    }
  },
  
  async fetchTypes({ commit }, categoryId) {
    try {
      const response = await this.$http.get(`/api/addon-requests/categories/${categoryId}/types`);
      commit('SET_TYPES', response.data.data);
      return response.data.data;
    } catch (error) {
      throw error;
    }
  },
  
  setFilters({ commit, dispatch }, filters) {
    commit('SET_FILTERS', filters);
    commit('SET_PAGINATION', { currentPage: 1 });
    return dispatch('fetchRequests');
  },
  
  setPage({ commit, dispatch }, page) {
    commit('SET_PAGINATION', { currentPage: page });
    return dispatch('fetchRequests');
  }
}

const getters = {
  filteredRequests: state => state.requests,
  
  requestById: state => id => state.requests.find(r => r.id === id),
  
  requestsByStatus: state => status => state.requests.filter(r => r.status === status),
  
  categoriesOptions: state => state.categories.map(c => ({
    value: c.id,
    label: c.name
  })),
  
  typesOptions: state => state.types.map(t => ({
    value: t.id,
    label: t.name,
    baseCost: t.base_cost
  })),
  
  hasFilters: state => {
    return Object.values(state.filters).some(value => value !== '');
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
}
```

## Error Handling

### Global Error Handler

```javascript
// plugins/errorHandler.js
export default {
  install(app) {
    // Global error handler for API responses
    app.config.globalProperties.$handleApiError = function(error) {
      if (error.response) {
        const { status, data } = error.response;
        
        switch (status) {
          case 400:
            if (data.errors) {
              // Validation errors
              Object.keys(data.errors).forEach(key => {
                data.errors[key].forEach(message => {
                  this.$toast.error(message);
                });
              });
            } else {
              this.$toast.error(data.message || '請求錯誤');
            }
            break;
            
          case 401:
            this.$toast.error('請先登入');
            this.$router.push('/login');
            break;
            
          case 403:
            this.$toast.error('沒有權限執行此操作');
            break;
            
          case 404:
            this.$toast.error('找不到相關資源');
            break;
            
          case 422:
            // Laravel validation errors
            if (data.errors) {
              Object.keys(data.errors).forEach(key => {
                data.errors[key].forEach(message => {
                  this.$toast.error(message);
                });
              });
            }
            break;
            
          case 500:
            this.$toast.error('伺服器錯誤，請稍後再試');
            break;
            
          default:
            this.$toast.error(data.message || '發生未知錯誤');
        }
      } else if (error.request) {
        this.$toast.error('網路連線錯誤');
      } else {
        this.$toast.error('發生錯誤: ' + error.message);
      }
    };
    
    // Axios interceptor for automatic error handling
    app.config.globalProperties.$http.interceptors.response.use(
      response => response,
      error => {
        app.config.globalProperties.$handleApiError(error);
        return Promise.reject(error);
      }
    );
  }
}
```

## Best Practices

### 1. Component Organization
```
src/
├── components/
│   ├── AddonRequest/
│   │   ├── AddonRequestList.vue
│   │   ├── AddonRequestForm.vue
│   │   ├── AddonRequestDetail.vue
│   │   ├── AddonRequestCard.vue
│   │   ├── StatusBadge.vue
│   │   └── PriorityBadge.vue
│   └── Common/
│       ├── Modal.vue
│       ├── Pagination.vue
│       └── FileUpload.vue
```

### 2. API Service Layer
```javascript
// services/addonRequestService.js
class AddonRequestService {
  constructor(http) {
    this.http = http;
  }
  
  async getRequests(params = {}) {
    const response = await this.http.get('/api/addon-requests', { params });
    return response.data;
  }
  
  async getRequest(id) {
    const response = await this.http.get(`/api/addon-requests/${id}`);
    return response.data.data;
  }
  
  async createRequest(data) {
    const response = await this.http.post('/api/addon-requests', data);
    return response.data.data;
  }
  
  async updateRequest(id, data) {
    const response = await this.http.put(`/api/addon-requests/${id}`, data);
    return response.data.data;
  }
  
  async updateStatus(id, status, notes) {
    const response = await this.http.patch(`/api/addon-requests/${id}/status`, {
      status,
      notes
    });
    return response.data.data;
  }
  
  async uploadFile(requestId, file, fileType, description) {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('file_type', fileType);
    formData.append('description', description);
    
    const response = await this.http.post(
      `/api/addon-requests/${requestId}/files`,
      formData,
      {
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      }
    );
    return response.data.data;
  }
}

export default AddonRequestService;
```

### 3. Composables (Vue 3)
```javascript
// composables/useAddonRequests.js
import { ref, reactive, computed } from 'vue';
import { useRouter } from 'vue-router';
import { useToast } from '@/composables/useToast';
import AddonRequestService from '@/services/addonRequestService';

export function useAddonRequests() {
  const router = useRouter();
  const toast = useToast();
  const service = new AddonRequestService();
  
  const requests = ref([]);
  const currentRequest = ref(null);
  const loading = ref(false);
  
  const filters = reactive({
    status: '',
    priority: '',
    category_id: ''
  });
  
  const pagination = reactive({
    currentPage: 1,
    totalPages: 1,
    perPage: 15
  });
  
  const filteredRequests = computed(() => {
    return requests.value.filter(request => {
      if (filters.status && request.status !== filters.status) return false;
      if (filters.priority && request.priority !== filters.priority) return false;
      if (filters.category_id && request.category_id !== filters.category_id) return false;
      return true;
    });
  });
  
  async function fetchRequests() {
    loading.value = true;
    try {
      const params = {
        page: pagination.currentPage,
        per_page: pagination.perPage,
        ...filters
      };
      
      const response = await service.getRequests(params);
      requests.value = response.data.data;
      pagination.currentPage = response.data.current_page;
      pagination.totalPages = response.data.last_page;
    } catch (error) {
      toast.error('載入需求失敗');
    } finally {
      loading.value = false;
    }
  }
  
  async function fetchRequest(id) {
    loading.value = true;
    try {
      currentRequest.value = await service.getRequest(id);
    } catch (error) {
      toast.error('載入需求失敗');
      router.go(-1);
    } finally {
      loading.value = false;
    }
  }
  
  async function createRequest(data) {
    try {
      const newRequest = await service.createRequest(data);
      requests.value.unshift(newRequest);
      toast.success('需求建立成功');
      return newRequest;
    } catch (error) {
      throw error;
    }
  }
  
  async function updateRequestStatus(id, status, notes) {
    try {
      const updatedRequest = await service.updateStatus(id, status, notes);
      
      // Update in list
      const index = requests.value.findIndex(r => r.id === id);
      if (index !== -1) {
        requests.value[index] = updatedRequest;
      }
      
      // Update current request
      if (currentRequest.value?.id === id) {
        currentRequest.value = updatedRequest;
      }
      
      toast.success('狀態更新成功');
      return updatedRequest;
    } catch (error) {
      throw error;
    }
  }
  
  function setFilters(newFilters) {
    Object.assign(filters, newFilters);
    pagination.currentPage = 1;
    fetchRequests();
  }
  
  function setPage(page) {
    pagination.currentPage = page;
    fetchRequests();
  }
  
  return {
    // State
    requests,
    currentRequest,
    loading,
    filters,
    pagination,
    
    // Computed
    filteredRequests,
    
    // Methods
    fetchRequests,
    fetchRequest,
    createRequest,
    updateRequestStatus,
    setFilters,
    setPage
  };
}
```

### 4. Route Configuration
```javascript
// router/addonRequests.js
export default [
  {
    path: '/addon-requests',
    name: 'AddonRequestList',
    component: () => import('@/views/AddonRequest/AddonRequestList.vue'),
    meta: {
      requiresAuth: true,
      title: '加購需求管理'
    }
  },
  {
    path: '/addon-requests/create',
    name: 'AddonRequestCreate',
    component: () => import('@/views/AddonRequest/AddonRequestForm.vue'),
    meta: {
      requiresAuth: true,
      title: '建立加購需求'
    }
  },
  {
    path: '/addon-requests/:id',
    name: 'AddonRequestDetail',
    component: () => import('@/views/AddonRequest/AddonRequestDetail.vue'),
    props: true,
    meta: {
      requiresAuth: true,
      title: '加購需求詳情'
    }
  },
  {
    path: '/addon-requests/:id/edit',
    name: 'AddonRequestEdit',
    component: () => import('@/views/AddonRequest/AddonRequestForm.vue'),
    props: true,
    meta: {
      requiresAuth: true,
      title: '編輯加購需求'
    }
  },
  {
    path: '/addon-requests/dashboard',
    name: 'AddonRequestDashboard',
    component: () => import('@/views/AddonRequest/AddonRequestDashboard.vue'),
    meta: {
      requiresAuth: true,
      roles: ['admin', 'manager'],
      title: '加購需求儀表板'
    }
  }
];
```

This comprehensive documentation provides everything needed to implement the frontend for the Add-on Request System, including complete component examples, API integration, state management, and best practices.
