Initial commit
This commit is contained in:
502
DEVELOPMENT.md
Normal file
502
DEVELOPMENT.md
Normal file
@@ -0,0 +1,502 @@
|
||||
# 🛠️ 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
|
||||
|
||||
Reference in New Issue
Block a user