503 lines
11 KiB
Markdown
Executable File
503 lines
11 KiB
Markdown
Executable File
# 🛠️ 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
|
|
|