11 KiB
11 KiB
🛠️ GUIDE DE DÉVELOPPEMENT - BTPXPRESS FRONTEND
📋 Table des matières
- Prérequis
- Installation
- Configuration
- Structure du projet
- Conventions de code
- Workflow de développement
- Composants
- Services API
- Gestion de l'état
- Routing
- Styling
- Debugging
- 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
cd btpxpress/btpxpress-client
npm install
2. Configurer les variables d'environnement
Créer .env.local :
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
npm run dev
Ouvrir http://localhost:3000
⚙️ Configuration
next.config.js
/** @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
{
"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
'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
git checkout -b feature/nom-de-la-fonctionnalite
2. Développer
- Créer le type TypeScript (
types/models/) - Créer le service API (
services/api/) - Créer le composant (
components/) - Créer la page (
app/) - Tester
3. Tester localement
npm run dev
npm run lint
npm test
4. Commit et Push
git add .
git commit -m "feat: ajout de la fonctionnalité X"
git push origin feature/nom-de-la-fonctionnalite
🧩 Composants
Composant fonctionnel avec 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
'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
// 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
// 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
// app/layout.tsx
import 'primereact/resources/themes/lara-light-blue/theme.css';
import 'primereact/resources/primereact.min.css';
import 'primeicons/primeicons.css';
CSS Modules
// 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
console.log('Debug:', data);
console.error('Error:', error);
console.table(array);
Debugger
const handleClick = () => {
debugger; // Point d'arrêt
// ...
};
✅ Bonnes pratiques
1. Typage strict
// ❌ Mauvais
const data: any = ...
// ✅ Bon
const data: ChantierDTO = ...
2. Gestion des erreurs
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
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