Files
btpxpress-frontend/COMPONENTS.md
2025-10-01 01:39:07 +00:00

508 lines
10 KiB
Markdown

# 🧩 COMPOSANTS - BTPXPRESS FRONTEND
## 📋 Table des matières
- [Vue d'ensemble](#vue-densemble)
- [Composants Layout](#composants-layout)
- [Composants Formulaires](#composants-formulaires)
- [Composants Tableaux](#composants-tableaux)
- [Composants Graphiques](#composants-graphiques)
- [Composants Communs](#composants-communs)
- [PrimeReact](#primereact)
- [Création de composants](#création-de-composants)
---
## 🎯 Vue d'ensemble
L'application utilise **PrimeReact 10.2.1** comme bibliothèque de composants UI principale, complétée par des composants personnalisés.
### **Organisation**
```
components/
├── layout/ # Composants de mise en page
├── forms/ # Formulaires
├── tables/ # Tableaux de données
├── charts/ # Graphiques
└── common/ # Composants réutilisables
```
---
## 🏗️ Composants Layout
### **Header**
En-tête de l'application avec navigation et profil utilisateur.
**Fichier** : `components/layout/Header.tsx`
```typescript
'use client';
import { Menubar } from 'primereact/menubar';
import { Avatar } from 'primereact/avatar';
import { useAuth } from '@/hooks/useAuth';
export const Header = () => {
const { user, logout } = useAuth();
const items = [
{ label: 'Dashboard', icon: 'pi pi-home', url: '/dashboard' },
{ label: 'Chantiers', icon: 'pi pi-building', url: '/chantiers' },
{ label: 'Clients', icon: 'pi pi-users', url: '/clients' },
{ label: 'Matériel', icon: 'pi pi-wrench', url: '/materiels' },
];
const end = (
<div className="flex align-items-center gap-2">
<Avatar label={user?.initials} shape="circle" />
<span>{user?.name}</span>
</div>
);
return <Menubar model={items} end={end} />;
};
```
**Props** : Aucune
**Utilisation** :
```typescript
import { Header } from '@/components/layout/Header';
<Header />
```
---
### **Sidebar**
Menu latéral de navigation.
**Fichier** : `components/layout/Sidebar.tsx`
```typescript
'use client';
import { PanelMenu } from 'primereact/panelmenu';
import { MenuItem } from 'primereact/menuitem';
export const Sidebar = () => {
const items: MenuItem[] = [
{
label: 'Dashboard',
icon: 'pi pi-home',
url: '/dashboard',
},
{
label: 'Chantiers',
icon: 'pi pi-building',
items: [
{ label: 'Liste', url: '/chantiers' },
{ label: 'Nouveau', url: '/chantiers/nouveau' },
{ label: 'Planning', url: '/chantiers/planning' },
],
},
{
label: 'Clients',
icon: 'pi pi-users',
items: [
{ label: 'Liste', url: '/clients' },
{ label: 'Nouveau', url: '/clients/nouveau' },
],
},
];
return (
<div className="sidebar">
<PanelMenu model={items} />
</div>
);
};
```
---
### **Breadcrumb**
Fil d'Ariane pour la navigation.
**Fichier** : `components/layout/Breadcrumb.tsx`
```typescript
'use client';
import { BreadCrumb } from 'primereact/breadcrumb';
import { MenuItem } from 'primereact/menuitem';
interface BreadcrumbProps {
items: MenuItem[];
}
export const AppBreadcrumb: React.FC<BreadcrumbProps> = ({ items }) => {
const home = { icon: 'pi pi-home', url: '/dashboard' };
return <BreadCrumb model={items} home={home} />;
};
```
**Utilisation** :
```typescript
const items = [
{ label: 'Chantiers', url: '/chantiers' },
{ label: 'Villa Moderne' },
];
<AppBreadcrumb items={items} />
```
---
## 📝 Composants Formulaires
### **ChantierForm**
Formulaire de création/modification de chantier.
**Fichier** : `components/forms/ChantierForm.tsx`
```typescript
'use client';
import { useForm, Controller } from 'react-hook-form';
import { InputText } from 'primereact/inputtext';
import { Dropdown } from 'primereact/dropdown';
import { Calendar } from 'primereact/calendar';
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,
}) => {
const { control, handleSubmit, formState: { errors } } = useForm<ChantierDTO>({
defaultValues: chantier || {},
});
const statutOptions = [
{ label: 'Planifié', value: 'PLANIFIE' },
{ label: 'En cours', value: 'EN_COURS' },
{ label: 'Terminé', value: 'TERMINE' },
];
return (
<form onSubmit={handleSubmit(onSubmit)} className="p-fluid">
<div className="field">
<label htmlFor="nom">Nom *</label>
<Controller
name="nom"
control={control}
rules={{ required: 'Le nom est obligatoire' }}
render={({ field }) => (
<InputText
id="nom"
{...field}
className={errors.nom ? 'p-invalid' : ''}
/>
)}
/>
{errors.nom && <small className="p-error">{errors.nom.message}</small>}
</div>
<div className="field">
<label htmlFor="statut">Statut</label>
<Controller
name="statut"
control={control}
render={({ field }) => (
<Dropdown
id="statut"
{...field}
options={statutOptions}
placeholder="Sélectionner un statut"
/>
)}
/>
</div>
<div className="field">
<label htmlFor="dateDebut">Date de début</label>
<Controller
name="dateDebut"
control={control}
render={({ field }) => (
<Calendar
id="dateDebut"
{...field}
dateFormat="dd/mm/yy"
showIcon
/>
)}
/>
</div>
<div className="flex gap-2 justify-content-end">
<Button
label="Annuler"
icon="pi pi-times"
className="p-button-text"
onClick={onCancel}
type="button"
/>
<Button
label="Enregistrer"
icon="pi pi-check"
type="submit"
/>
</div>
</form>
);
};
```
---
## 📊 Composants Tableaux
### **DataTableWrapper**
Wrapper pour DataTable de PrimeReact avec fonctionnalités communes.
**Fichier** : `components/tables/DataTableWrapper.tsx`
```typescript
'use client';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
interface DataTableWrapperProps<T> {
data: T[];
columns: ColumnConfig[];
onEdit?: (item: T) => void;
onDelete?: (item: T) => void;
loading?: boolean;
}
interface ColumnConfig {
field: string;
header: string;
sortable?: boolean;
body?: (rowData: any) => React.ReactNode;
}
export function DataTableWrapper<T>({
data,
columns,
onEdit,
onDelete,
loading = false,
}: DataTableWrapperProps<T>) {
const actionBodyTemplate = (rowData: T) => {
return (
<div className="flex gap-2">
{onEdit && (
<Button
icon="pi pi-pencil"
className="p-button-rounded p-button-text"
onClick={() => onEdit(rowData)}
/>
)}
{onDelete && (
<Button
icon="pi pi-trash"
className="p-button-rounded p-button-text p-button-danger"
onClick={() => onDelete(rowData)}
/>
)}
</div>
);
};
return (
<DataTable
value={data}
paginator
rows={10}
loading={loading}
emptyMessage="Aucune donnée disponible"
>
{columns.map((col) => (
<Column
key={col.field}
field={col.field}
header={col.header}
sortable={col.sortable}
body={col.body}
/>
))}
{(onEdit || onDelete) && (
<Column
body={actionBodyTemplate}
header="Actions"
style={{ width: '10rem' }}
/>
)}
</DataTable>
);
}
```
**Utilisation** :
```typescript
const columns = [
{ field: 'nom', header: 'Nom', sortable: true },
{ field: 'code', header: 'Code', sortable: true },
{ field: 'statut', header: 'Statut' },
];
<DataTableWrapper
data={chantiers}
columns={columns}
onEdit={handleEdit}
onDelete={handleDelete}
loading={loading}
/>
```
---
## 📈 Composants Graphiques
### **ChartBar**
Graphique en barres avec Chart.js.
**Fichier** : `components/charts/ChartBar.tsx`
```typescript
'use client';
import { Chart } from 'primereact/chart';
interface ChartBarProps {
data: {
labels: string[];
datasets: {
label: string;
data: number[];
backgroundColor?: string;
}[];
};
title?: string;
}
export const ChartBar: React.FC<ChartBarProps> = ({ data, title }) => {
const options = {
responsive: true,
plugins: {
legend: {
position: 'top' as const,
},
title: {
display: !!title,
text: title,
},
},
};
return <Chart type="bar" data={data} options={options} />;
};
```
**Utilisation** :
```typescript
const data = {
labels: ['Jan', 'Fév', 'Mar', 'Avr'],
datasets: [
{
label: 'Chantiers',
data: [12, 19, 8, 15],
backgroundColor: 'rgba(54, 162, 235, 0.5)',
},
],
};
<ChartBar data={data} title="Chantiers par mois" />
```
---
## 🔧 Composants Communs
### **LoadingSpinner**
Indicateur de chargement.
```typescript
'use client';
import { ProgressSpinner } from 'primereact/progressspinner';
export const LoadingSpinner = () => {
return (
<div className="flex justify-content-center align-items-center" style={{ height: '400px' }}>
<ProgressSpinner />
</div>
);
};
```
### **ErrorMessage**
Message d'erreur.
```typescript
'use client';
import { Message } from 'primereact/message';
interface ErrorMessageProps {
message: string;
}
export const ErrorMessage: React.FC<ErrorMessageProps> = ({ message }) => {
return <Message severity="error" text={message} />;
};
```
---
## 🎨 PrimeReact
### **Composants utilisés**
| Composant | Usage |
|-----------|-------|
| **DataTable** | Tableaux de données |
| **Button** | Boutons |
| **InputText** | Champs texte |
| **Dropdown** | Listes déroulantes |
| **Calendar** | Sélecteur de date |
| **Dialog** | Modales |
| **Toast** | Notifications |
| **Chart** | Graphiques |
| **Menubar** | Menu principal |
| **PanelMenu** | Menu latéral |
### **Documentation**
https://primereact.org/
---
**Dernière mise à jour** : 2025-09-30
**Version** : 1.0
**Auteur** : Équipe BTPXpress