Files
btpxpress-frontend/DEVELOPMENT.md
2025-10-13 05:29:32 +02:00

503 lines
11 KiB
Markdown

# 🛠️ GUIDE DE DÉVELOPPEMENT - BTPXPRESS FRONTEND
## 📋 Table des matières
- [Prérequis](#prérequis)
- [Installation](#installation)
- [Configuration](#configuration)
- [Structure du projet](#structure-du-projet)
- [Conventions de code](#conventions-de-code)
- [Workflow de développement](#workflow-de-développement)
- [Composants](#composants)
- [Services API](#services-api)
- [Gestion de l'état](#gestion-de-létat)
- [Routing](#routing)
- [Styling](#styling)
- [Debugging](#debugging)
- [Bonnes pratiques](#bonnes-pratiques)
---
## 🔧 Prérequis
### **Logiciels requis**
| Logiciel | Version | Vérification |
|----------|---------|--------------|
| **Node.js** | 20.x LTS | `node --version` |
| **npm** | 10.x | `npm --version` |
| **VS Code** | Latest | Recommandé |
### **Extensions VS Code recommandées**
- **ES7+ React/Redux/React-Native snippets**
- **Prettier - Code formatter**
- **ESLint**
- **TypeScript Vue Plugin (Volar)**
- **Tailwind CSS IntelliSense**
- **Auto Rename Tag**
- **Path Intellisense**
---
## 📦 Installation
### **1. Cloner et installer**
```bash
cd btpxpress/btpxpress-client
npm install
```
### **2. Configurer les variables d'environnement**
Créer `.env.local` :
```bash
NEXT_PUBLIC_API_URL=http://localhost:8080/api/v1
NEXT_PUBLIC_KEYCLOAK_URL=http://localhost:8180
NEXT_PUBLIC_KEYCLOAK_REALM=btpxpress
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=btpxpress-frontend
```
### **3. Lancer le serveur de développement**
```bash
npm run dev
```
Ouvrir http://localhost:3000
---
## ⚙️ Configuration
### **next.config.js**
```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
images: {
domains: ['localhost'],
},
env: {
API_URL: process.env.NEXT_PUBLIC_API_URL,
},
}
module.exports = nextConfig
```
### **tsconfig.json**
```json
{
"compilerOptions": {
"target": "ES2020",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./*"],
"@/components/*": ["./components/*"],
"@/services/*": ["./services/*"],
"@/types/*": ["./types/*"],
"@/hooks/*": ["./hooks/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
```
---
## 📁 Structure du projet
```
btpxpress-client/
├── app/ # App Router (Next.js 15)
│ ├── (auth)/ # Groupe de routes authentifiées
│ │ ├── layout.tsx # Layout avec sidebar
│ │ ├── dashboard/
│ │ │ └── page.tsx # /dashboard
│ │ ├── chantiers/
│ │ │ ├── page.tsx # /chantiers (liste)
│ │ │ ├── [id]/
│ │ │ │ └── page.tsx # /chantiers/[id] (détails)
│ │ │ └── nouveau/
│ │ │ └── page.tsx # /chantiers/nouveau
│ │ └── ...
│ ├── login/
│ │ └── page.tsx # /login
│ ├── layout.tsx # Layout racine
│ ├── page.tsx # / (accueil)
│ └── globals.css # Styles globaux
├── components/
│ ├── layout/
│ │ ├── Header.tsx
│ │ ├── Sidebar.tsx
│ │ └── Footer.tsx
│ ├── forms/
│ │ ├── ChantierForm.tsx
│ │ └── ClientForm.tsx
│ ├── tables/
│ │ └── DataTableWrapper.tsx
│ └── common/
│ ├── LoadingSpinner.tsx
│ └── ErrorMessage.tsx
├── services/
│ ├── api/
│ │ ├── chantierService.ts
│ │ ├── clientService.ts
│ │ └── apiClient.ts
│ ├── auth/
│ │ └── keycloakService.ts
│ └── utils/
│ └── formatters.ts
├── types/
│ ├── models/
│ │ ├── Chantier.ts
│ │ └── Client.ts
│ └── api/
│ └── responses.ts
├── hooks/
│ ├── useAuth.ts
│ ├── useChantiers.ts
│ └── useToast.ts
├── contexts/
│ ├── AuthContext.tsx
│ └── ThemeContext.tsx
└── public/
├── images/
└── icons/
```
---
## 📝 Conventions de code
### **Nommage**
| Élément | Convention | Exemple |
|---------|------------|---------|
| **Composants** | PascalCase | `ChantierForm.tsx` |
| **Fichiers** | PascalCase (composants), camelCase (autres) | `Header.tsx`, `apiClient.ts` |
| **Variables** | camelCase | `const userName = ...` |
| **Constantes** | UPPER_SNAKE_CASE | `const API_URL = ...` |
| **Types/Interfaces** | PascalCase | `interface ChantierDTO` |
| **Hooks** | camelCase avec `use` | `useAuth()` |
### **Structure d'un composant**
```typescript
'use client'; // Si nécessaire (composant client)
import React from 'react';
import { Button } from 'primereact/button';
import type { ChantierDTO } from '@/types/models/Chantier';
interface ChantierFormProps {
chantier?: ChantierDTO;
onSubmit: (data: ChantierDTO) => void;
onCancel: () => void;
}
export const ChantierForm: React.FC<ChantierFormProps> = ({
chantier,
onSubmit,
onCancel,
}) => {
// Hooks
const [loading, setLoading] = React.useState(false);
// Handlers
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
// ...
};
// Render
return (
<form onSubmit={handleSubmit}>
{/* JSX */}
</form>
);
};
```
---
## 🔄 Workflow de développement
### **1. Créer une branche**
```bash
git checkout -b feature/nom-de-la-fonctionnalite
```
### **2. Développer**
1. Créer le type TypeScript (`types/models/`)
2. Créer le service API (`services/api/`)
3. Créer le composant (`components/`)
4. Créer la page (`app/`)
5. Tester
### **3. Tester localement**
```bash
npm run dev
npm run lint
npm test
```
### **4. Commit et Push**
```bash
git add .
git commit -m "feat: ajout de la fonctionnalité X"
git push origin feature/nom-de-la-fonctionnalite
```
---
## 🧩 Composants
### **Composant fonctionnel avec TypeScript**
```typescript
import React from 'react';
interface Props {
title: string;
count: number;
onIncrement: () => void;
}
export const Counter: React.FC<Props> = ({ title, count, onIncrement }) => {
return (
<div>
<h2>{title}</h2>
<p>Count: {count}</p>
<button onClick={onIncrement}>Increment</button>
</div>
);
};
```
### **Composant avec PrimeReact**
```typescript
'use client';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
export const ChantierTable = ({ chantiers }) => {
const actionBodyTemplate = (rowData) => {
return (
<Button
icon="pi pi-pencil"
className="p-button-rounded p-button-text"
onClick={() => handleEdit(rowData)}
/>
);
};
return (
<DataTable value={chantiers} paginator rows={10}>
<Column field="nom" header="Nom" sortable />
<Column field="code" header="Code" sortable />
<Column field="statut" header="Statut" />
<Column body={actionBodyTemplate} header="Actions" />
</DataTable>
);
};
```
---
## 🔌 Services API
### **Client API de base**
```typescript
// services/api/apiClient.ts
import axios from 'axios';
const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
'Content-Type': 'application/json',
},
});
// Intercepteur pour ajouter le token
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
export default apiClient;
```
### **Service spécifique**
```typescript
// services/api/chantierService.ts
import apiClient from './apiClient';
import type { ChantierDTO } from '@/types/models/Chantier';
export const chantierService = {
getAll: async (): Promise<ChantierDTO[]> => {
const response = await apiClient.get('/chantiers');
return response.data;
},
getById: async (id: string): Promise<ChantierDTO> => {
const response = await apiClient.get(`/chantiers/${id}`);
return response.data;
},
create: async (data: ChantierDTO): Promise<ChantierDTO> => {
const response = await apiClient.post('/chantiers', data);
return response.data;
},
update: async (id: string, data: ChantierDTO): Promise<ChantierDTO> => {
const response = await apiClient.put(`/chantiers/${id}`, data);
return response.data;
},
delete: async (id: string): Promise<void> => {
await apiClient.delete(`/chantiers/${id}`);
},
};
```
---
## 🎨 Styling
### **PrimeReact Theme**
```typescript
// app/layout.tsx
import 'primereact/resources/themes/lara-light-blue/theme.css';
import 'primereact/resources/primereact.min.css';
import 'primeicons/primeicons.css';
```
### **CSS Modules**
```typescript
// components/Header.module.css
.header {
background-color: #fff;
padding: 1rem;
}
// components/Header.tsx
import styles from './Header.module.css';
export const Header = () => {
return <header className={styles.header}>...</header>;
};
```
---
## 🐛 Debugging
### **React DevTools**
Installer l'extension Chrome/Firefox : React Developer Tools
### **Console logs**
```typescript
console.log('Debug:', data);
console.error('Error:', error);
console.table(array);
```
### **Debugger**
```typescript
const handleClick = () => {
debugger; // Point d'arrêt
// ...
};
```
---
## ✅ Bonnes pratiques
### **1. Typage strict**
```typescript
// ❌ Mauvais
const data: any = ...
// ✅ Bon
const data: ChantierDTO = ...
```
### **2. Gestion des erreurs**
```typescript
try {
const data = await chantierService.getAll();
setChantiers(data);
} catch (error) {
console.error('Erreur:', error);
toast.current?.show({
severity: 'error',
summary: 'Erreur',
detail: 'Impossible de charger les chantiers',
});
}
```
### **3. Loading states**
```typescript
const [loading, setLoading] = useState(false);
const loadData = async () => {
setLoading(true);
try {
const data = await service.getData();
setData(data);
} finally {
setLoading(false);
}
};
```
---
**Dernière mise à jour** : 2025-09-30
**Version** : 1.0
**Auteur** : Équipe BTPXpress