# 🚀 Frontend Components V2.1 - Developer Guide

Bienvenue dans le guide développeur pour les 4 nouveaux composants Vue 3 de MyRoadTrip V2.1 !

---

## 📋 Table des Matières

1. [Architecture Générale](#architecture-générale)
2. [Composants Détaillés](#composants-détaillés)
3. [Props & Events](#props--events)
4. [State Management](#state-management)
5. [API Integration](#api-integration)
6. [Styling Guidelines](#styling-guidelines)
7. [Testing Strategy](#testing-strategy)
8. [Performance Tips](#performance-tips)
9. [Common Pitfalls](#common-pitfalls)

---

## Architecture Générale

### Structure des Modules

```
apps/web/src/modules/
├── weather/
│   └── components/
│       └── WeatherWidget.vue          (350 lignes)
├── budget/
│   └── components/
│       └── BudgetDashboard.vue        (650 lignes)
├── analytics/
│   └── components/
│       └── AnalyticsDashboard.vue     (550 lignes)
└── social/
    └── components/
        └── SocialFeed.vue             (700 lignes)
```

### Pattern de Composition

Tous les composants suivent le **Composition API** de Vue 3:

```vue
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useQuasar } from 'quasar'
import axios from 'axios'

// Props typées
interface Props {
  planId: string
  autoRefresh?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  autoRefresh: false
})

// State réactif
const loading = ref(false)
const data = ref<any>(null)

// Computed properties
const processedData = computed(() => {
  return data.value ? transform(data.value) : null
})

// Lifecycle hooks
onMounted(() => {
  loadData()
})

// Methods
const loadData = async () => {
  loading.value = true
  try {
    const response = await axios.get('/api/endpoint')
    data.value = response.data
  } finally {
    loading.value = false
  }
}
</script>
```

---

## Composants Détaillés

### 1. WeatherWidget.vue ☁️

**Purpose:** Afficher météo et analyse de risque pour un voyage

**Key Features:**
- Intégration OpenWeatherMap API
- Algorithme de calcul de risque custom
- Auto-refresh sur changement stages
- Détails expandables par étape

**Code Structure:**
```typescript
// State Management
const loading = ref(false)
const error = ref('')
const weatherData = ref<RouteWeatherAnalysis | null>(null)

// Computed Styling
const riskIcon = computed(() => {
  switch (weatherData.value?.overallRisk) {
    case 'high': return 'mdi-alert-octagon'
    case 'medium': return 'mdi-alert'
    default: return 'mdi-check-circle'
  }
})

// API Integration
const refreshWeather = async () => {
  const response = await axios.post('/api/weather/route/analyze', {
    planId: props.planId,
    stages: props.stages.map(stage => ({
      id: stage.id,
      location: stage.location,
      date: stage.estimatedArrival
    }))
  })
  weatherData.value = response.data
}

// Watchers
watch(() => props.stages, (newStages) => {
  if (newStages?.length && props.autoRefresh) {
    refreshWeather()
  }
}, { deep: true })
```

**Customization Points:**
```typescript
// Change cache TTL
const WEATHER_CACHE_TTL = 600 // 10 minutes

// Modify risk algorithm
const calculateRiskLevel = (weather: WeatherData): RiskLevel => {
  // Custom logic here
  if (weather.windSpeed > 80) return 'high'
  // ...
}

// Add new weather icons
const getWeatherIcon = (condition: string) => {
  const icons: Record<string, string> = {
    // Add your custom mappings
    'tornado': 'mdi-weather-tornado'
  }
}
```

---

### 2. BudgetDashboard.vue 💰

**Purpose:** Gestion budget multi-devises avec split dépenses

**Key Features:**
- Création budget avec catégories
- Split expenses (equal/percentage/custom)
- Multi-currency via ExchangeRate.host
- Debt optimization algorithm
- Budget projections

**Code Structure:**
```typescript
// Complex State
const budget = ref<Budget | null>(null)
const expenses = ref<Expense[]>([])
const groupBalance = ref<GroupExpenseBalance[]>([])
const projection = ref<BudgetProjection | null>(null)

// Business Logic
const spentPercent = computed(() => {
  if (!budget.value) return 0
  return Math.round((budget.value.spent / budget.value.totalBudget) * 100)
})

const categoriesWithSpent = computed(() => {
  return budget.value?.categories.map(cat => {
    const spent = expenses.value
      .filter(e => e.category === cat.name)
      .reduce((sum, e) => sum + e.amount, 0)
    return {
      ...cat,
      spent,
      spentPercent: Math.round((spent / cat.allocated) * 100)
    }
  })
})

// Currency Formatting
const formatCurrency = (amount: number) => {
  return new Intl.NumberFormat('fr-FR', {
    style: 'currency',
    currency: budget.value?.currency || 'EUR'
  }).format(amount)
}
```

**Extension Points:**
```typescript
// Add new split types
const splitTypeOptions = [
  { label: 'Égal', value: 'equal' },
  { label: 'Pourcentage', value: 'percentage' },
  { label: 'Personnalisé', value: 'custom' },
  // Add your custom type:
  { label: 'Par poids', value: 'weighted' }
]

// Customize categories
const defaultCategories = [
  'Transport',
  'Hébergement',
  'Nourriture',
  'Activités',
  // Add your categories:
  'Shopping',
  'Souvenirs'
]

// Modify projection algorithm
const calculateProjection = (expenses: Expense[], totalDays: number) => {
  // Your custom logic
  const velocity = calculateSpendingVelocity(expenses)
  return velocity * totalDays
}
```

---

### 3. AnalyticsDashboard.vue 📊

**Purpose:** Product analytics avec funnels, A/B tests, rétention

**Key Features:**
- Key metrics avec growth indicators
- Funnel analysis avec dropoff detection
- A/B testing avec statistical significance
- Cohort retention analysis
- Top features tracking

**Code Structure:**
```typescript
// Analytics State
const metrics = ref({
  activeUsers: 0,
  plansCreated: 0,
  avgSessionDuration: 0,
  conversionRate: 0,
  userGrowth: 0,
  planGrowth: 0,
  sessionGrowth: 0,
  conversionGrowth: 0
})

const funnels = ref<Funnel[]>([])
const activeFunnel = ref<Funnel | null>(null)
const retentionCohorts = ref<RetentionCohort[]>([])
const abTests = ref<ABTest[]>([])

// Data Transformations
const formatNumber = (num: number) => {
  return new Intl.NumberFormat('fr-FR').format(num)
}

const formatDuration = (seconds: number) => {
  const minutes = Math.floor(seconds / 60)
  if (minutes < 60) return `${minutes}m`
  const hours = Math.floor(minutes / 60)
  const remainingMins = minutes % 60
  return `${hours}h ${remainingMins}m`
}

const getRetentionStyle = (percent: number) => {
  const opacity = Math.max(0.2, percent / 100)
  return {
    backgroundColor: `rgba(var(--q-primary-rgb), ${opacity})`,
    color: percent > 50 ? 'white' : 'inherit'
  }
}
```

**Integration Points:**
```typescript
// Connect to Mixpanel
import mixpanel from 'mixpanel-browser'

const trackAnalyticsEvent = (event: string, props?: any) => {
  if (import.meta.env.VITE_MIXPANEL_TOKEN) {
    mixpanel.track(event, props)
  }
  // Also send to backend
  axios.post('/api/analytics/track', { event, ...props })
}

// Custom funnels
const customFunnels = [
  {
    id: 'onboarding',
    name: 'User Onboarding',
    steps: [
      { name: 'Sign Up', event: 'signup_complete' },
      { name: 'Add Profile', event: 'profile_created' },
      { name: 'First Plan', event: 'plan_created' }
    ]
  }
]
```

---

### 4. SocialFeed.vue 🤝

**Purpose:** Feed social avec trending, follow, comments

**Key Features:**
- Personalized feed avec infinite scroll
- Trending algorithm (Reddit-style)
- Comments avec @mentions
- Like/save/share actions
- Following management

**Code Structure:**
```typescript
// Feed State
const feedItems = ref<FeedItem[]>([])
const trendingPlans = ref<TrendingPlan[]>([])
const followingUsers = ref<User[]>([])

const page = ref(1)
const hasMore = ref(true)

// Infinite Scroll
const loadMoreFeed = async (index: number, done: () => void) => {
  if (!hasMore.value) {
    done()
    return
  }

  page.value++
  
  try {
    const response = await axios.get('/api/social/feed', {
      params: { userId: props.currentUserId, page: page.value }
    })
    
    if (response.data.length === 0) {
      hasMore.value = false
    } else {
      feedItems.value.push(...response.data)
    }
  } finally {
    done()
  }
}

// Trending Algorithm (Frontend sorting)
const sortByTrendingScore = (plans: Plan[]) => {
  return plans.sort((a, b) => {
    const scoreA = calculateTrendingScore(a)
    const scoreB = calculateTrendingScore(b)
    return scoreB - scoreA
  })
}

const calculateTrendingScore = (plan: Plan) => {
  const ageHours = (Date.now() - plan.createdAt.getTime()) / 3600000
  const engagement = plan.views + (plan.likes * 3) + (plan.saves * 5)
  return engagement / Math.pow(ageHours + 2, 1.5)
}
```

**Customization:**
```typescript
// Modify trending weights
const TRENDING_WEIGHTS = {
  view: 1,
  like: 3,
  save: 5,
  clone: 10,
  comment: 2
}

// Custom activity types
type ActivityType = 
  | 'plan_created'
  | 'plan_completed'
  | 'achievement_unlocked'
  // Add your types:
  | 'stage_added'
  | 'collaboration_started'

// Format timestamps
const formatTime = (timestamp: string) => {
  // Your custom logic
  return dayjs(timestamp).fromNow()
}
```

---

## Props & Events

### WeatherWidget

**Props:**
```typescript
interface WeatherWidgetProps {
  planId: string              // Required - ID du plan
  stages: Stage[]             // Required - Étapes avec localisation
  autoRefresh?: boolean       // Optional - Auto-refresh sur change
}
```

**Events:**
None - Read-only component

**Usage:**
```vue
<WeatherWidget
  :plan-id="currentPlan.id"
  :stages="sortedStages"
  :auto-refresh="true"
/>
```

---

### BudgetDashboard

**Props:**
```typescript
interface BudgetDashboardProps {
  planId: string                          // Required - ID du plan
  userId: string                          // Required - ID user courant
  participants?: Participant[]            // Optional - Collaborateurs
}
```

**Events:**
None - Interactions via dialogs

**Usage:**
```vue
<BudgetDashboard
  :plan-id="plan.id"
  :user-id="auth.userId"
  :participants="plan.collaborators"
/>
```

---

### AnalyticsDashboard

**Props:**
None - Global dashboard

**Events:**
None - Read-only analytics

**Usage:**
```vue
<AnalyticsDashboard />
```

---

### SocialFeed

**Props:**
```typescript
interface SocialFeedProps {
  currentUserId: string       // Required - ID user courant
}
```

**Events:**
None - Interactions via API calls

**Usage:**
```vue
<SocialFeed :current-user-id="auth.userId" />
```

---

## State Management

### Local vs Store

**Règle générale:**
- ✅ **Local state (ref/reactive)** pour UI state (loading, errors, form data)
- ✅ **Pinia store** pour shared state (user auth, current plan, global settings)

**Example WeatherWidget:**
```typescript
// LOCAL STATE (component-specific)
const loading = ref(false)
const error = ref('')
const weatherData = ref<WeatherData | null>(null)

// STORE STATE (shared across components)
import { usePlannerStore } from '@/stores/plannerStore'
const plannerStore = usePlannerStore()
const currentPlan = computed(() => plannerStore.currentPlan)
```

**When to create a new store:**
- State needed by 3+ components
- State persisted across route changes
- State synced with backend (e.g., user preferences)

---

## API Integration

### Axios Configuration

**Base setup** (`src/services/api.ts`):
```typescript
import axios from 'axios'

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
})

// Auth interceptor
api.interceptors.request.use((config) => {
  const token = localStorage.getItem('authToken')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

// Error interceptor
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Redirect to login
      router.push('/login')
    }
    return Promise.reject(error)
  }
)

export default api
```

**Usage in components:**
```typescript
import axios from '@/services/api'

const loadData = async () => {
  try {
    const { data } = await axios.get('/api/endpoint')
    // Handle success
  } catch (err: any) {
    const message = err.response?.data?.message || 'Erreur réseau'
    $q.notify({ type: 'negative', message })
  }
}
```

---

## Styling Guidelines

### CSS Architecture

**1. Scoped Styles**
```vue
<style scoped lang="scss">
// Component-specific styles
.weather-widget {
  width: 100%;
  margin: 1rem 0;
}
</style>
```

**2. Global Classes** (use sparingly)
```scss
// apps/web/src/styles/components/_panels.scss
.glass-panel {
  background: rgba(15, 23, 42, 0.6);
  backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
}
```

**3. Quasar Variables**
```scss
// Use Quasar design tokens
.my-component {
  color: var(--q-primary);
  background: var(--q-dark-page);
  border-color: var(--q-separator-color);
}
```

**4. Responsive Design**
```scss
.component {
  // Mobile first
  padding: 1rem;
  
  // Tablet
  @media (min-width: 768px) {
    padding: 1.5rem;
  }
  
  // Desktop
  @media (min-width: 1024px) {
    padding: 2rem;
  }
}
```

### Color Palette

```scss
:root {
  --q-primary: #6366f1;      // Indigo
  --q-secondary: #8b5cf6;    // Purple
  --q-positive: #10b981;     // Green
  --q-negative: #ef4444;     // Red
  --q-warning: #f59e0b;      // Orange
  --q-info: #3b82f6;         // Blue
  
  --q-dark-page: #0f172a;    // Dark background
  --q-separator-color: rgba(255, 255, 255, 0.12);
}
```

---

## Testing Strategy

### Unit Tests (Vitest)

**Component test example:**
```typescript
// WeatherWidget.test.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi } from 'vitest'
import WeatherWidget from './WeatherWidget.vue'

describe('WeatherWidget', () => {
  it('renders loading state', () => {
    const wrapper = mount(WeatherWidget, {
      props: {
        planId: 'test-plan',
        stages: []
      }
    })
    
    expect(wrapper.find('.q-spinner-dots').exists()).toBe(true)
  })
  
  it('displays weather data when loaded', async () => {
    // Mock API
    vi.mock('axios', () => ({
      default: {
        post: vi.fn().mockResolvedValue({
          data: {
            overallRisk: 'low',
            stages: [
              { stageId: '1', weather: { temperature: 20 } }
            ]
          }
        })
      }
    }))
    
    const wrapper = mount(WeatherWidget, {
      props: {
        planId: 'test-plan',
        stages: [{ id: '1', name: 'Paris', location: { coordinates: { lat: 48, lng: 2 } } }]
      }
    })
    
    await wrapper.vm.$nextTick()
    
    expect(wrapper.text()).toContain('20°')
  })
})
```

### Integration Tests

**Test API integration:**
```typescript
// budgetDashboard.integration.test.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import BudgetDashboard from './BudgetDashboard.vue'

describe('BudgetDashboard Integration', () => {
  it('creates budget and adds expense', async () => {
    const wrapper = mount(BudgetDashboard, {
      props: {
        planId: 'test-plan',
        userId: 'test-user'
      }
    })
    
    // Click create budget
    await wrapper.find('[data-testid="create-budget-btn"]').trigger('click')
    
    // Fill form
    const input = wrapper.find('[data-testid="budget-amount"]')
    await input.setValue(1000)
    
    // Submit
    await wrapper.find('[data-testid="submit-budget"]').trigger('click')
    
    // Verify budget created
    expect(wrapper.find('[data-testid="budget-progress"]').exists()).toBe(true)
  })
})
```

---

## Performance Tips

### 1. Lazy Loading

```typescript
// Lazy load heavy components
const AnalyticsDashboard = defineAsyncComponent(
  () => import('./components/AnalyticsDashboard.vue')
)
```

### 2. Computed Caching

```typescript
// ✅ GOOD - Computed is cached
const expensiveComputation = computed(() => {
  return heavyCalculation(props.data)
})

// ❌ BAD - Recalculated every render
const expensiveComputation = () => {
  return heavyCalculation(props.data)
}
```

### 3. Debounce User Input

```typescript
import { useDebounceFn } from '@vueuse/core'

const searchQuery = ref('')
const debouncedSearch = useDebounceFn((query) => {
  performSearch(query)
}, 300)

watch(searchQuery, (newQuery) => {
  debouncedSearch(newQuery)
})
```

### 4. Virtual Scrolling

```typescript
// For long lists (1000+ items)
import { useVirtualList } from '@vueuse/core'

const { list, containerProps, wrapperProps } = useVirtualList(
  longList,
  { itemHeight: 60 }
)
```

### 5. Memoization

```typescript
import { useMemoize } from '@vueuse/core'

const expensiveFunction = useMemoize((param: string) => {
  // Heavy computation
  return result
})
```

---

## Common Pitfalls

### 1. Forgetting to unwatch

```typescript
// ❌ BAD - Memory leak
onMounted(() => {
  watch(someRef, () => {})
})

// ✅ GOOD - Cleanup
onMounted(() => {
  const unwatch = watch(someRef, () => {})
  
  onUnmounted(() => {
    unwatch()
  })
})
```

### 2. Mutating Props

```typescript
// ❌ BAD
props.stages.push(newStage)

// ✅ GOOD
const localStages = ref([...props.stages])
localStages.value.push(newStage)
emit('update:stages', localStages.value)
```

### 3. Not handling loading/error states

```typescript
// ✅ ALWAYS include
<template>
  <div v-if="loading">Loading...</div>
  <div v-else-if="error">{{ error }}</div>
  <div v-else><!-- content --></div>
</template>
```

### 4. Forgetting .value for refs

```typescript
// ❌ BAD
if (loading) { }

// ✅ GOOD
if (loading.value) { }
```

### 5. Blocking main thread

```typescript
// ❌ BAD - Blocks UI
const result = expensiveSync Calculation()

// ✅ GOOD - Non-blocking
const result = await new Promise((resolve) => {
  setTimeout(() => {
    resolve(expensiveCalculation())
  }, 0)
})
```

---

## 🚀 Next Steps

1. **Read** `QUICK_TEST_GUIDE_V2_1.md` pour tester composants
2. **Review** `FRONTEND_COMPONENTS_V2_1.md` pour détails implémentation
3. **Check** lint warnings et fix si nécessaire
4. **Write** unit tests pour chaque composant
5. **Deploy** staging pour user testing

---

**Questions?** Check documentation complète ou demander sur Slack #dev-frontend

**Version:** 2.1.0  
**Dernière mise à jour:** 1er Novembre 2025
