508 lines
10 KiB
Markdown
Executable File
508 lines
10 KiB
Markdown
Executable File
# 🧩 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
|
|
|