518 lines
23 KiB
TypeScript
Executable File
518 lines
23 KiB
TypeScript
Executable File
'use client';
|
|
|
|
import React, { useState, useRef, useEffect } from 'react';
|
|
import { DataView } from 'primereact/dataview';
|
|
import { Card } from 'primereact/card';
|
|
import { Button } from 'primereact/button';
|
|
import { Tag } from 'primereact/tag';
|
|
import { Rating } from 'primereact/rating';
|
|
import { InputText } from 'primereact/inputtext';
|
|
import { Dropdown } from 'primereact/dropdown';
|
|
import { MultiSelect } from 'primereact/multiselect';
|
|
import { Slider } from 'primereact/slider';
|
|
import { Toast } from 'primereact/toast';
|
|
import { Badge } from 'primereact/badge';
|
|
import { Avatar } from 'primereact/avatar';
|
|
import { Chip } from 'primereact/chip';
|
|
import { Divider } from 'primereact/divider';
|
|
|
|
interface EntrepriseProfile {
|
|
id: string;
|
|
nomCommercial: string;
|
|
description: string;
|
|
slogan?: string;
|
|
logoUrl?: string;
|
|
secteurActivite: string;
|
|
specialites: string[];
|
|
certifications: string[];
|
|
ville: string;
|
|
departement: string;
|
|
region: string;
|
|
noteGlobale: number;
|
|
nombreAvis: number;
|
|
nombreProjetsRealises: number;
|
|
nombreClientsServis: number;
|
|
certifie: boolean;
|
|
typeAbonnement: 'GRATUIT' | 'PREMIUM' | 'ENTERPRISE';
|
|
zonesIntervention: string[];
|
|
siteWeb?: string;
|
|
emailContact: string;
|
|
telephoneCommercial?: string;
|
|
photosRealisations?: string[];
|
|
budgetMinProjet?: number;
|
|
budgetMaxProjet?: number;
|
|
delaiMoyenIntervention?: number;
|
|
derniereActivite: string;
|
|
}
|
|
|
|
const AnnuaireProfessionnel = () => {
|
|
const [entreprises, setEntreprises] = useState<EntrepriseProfile[]>([]);
|
|
const [filteredEntreprises, setFilteredEntreprises] = useState<EntrepriseProfile[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
|
|
// Filtres
|
|
const [regionFilter, setRegionFilter] = useState('');
|
|
const [secteurFilter, setSecteurFilter] = useState('');
|
|
const [specialitesFilter, setSpecialitesFilter] = useState<string[]>([]);
|
|
const [noteMinFilter, setNoteMinFilter] = useState(0);
|
|
const [certifiesUniquement, setCertifiesUniquement] = useState(false);
|
|
const [budgetRange, setBudgetRange] = useState<[number, number]>([0, 1000000]);
|
|
|
|
const [layout, setLayout] = useState<'grid' | 'list'>('grid');
|
|
const [first, setFirst] = useState(0);
|
|
const [rows, setRows] = useState(12);
|
|
|
|
const toast = useRef<Toast>(null);
|
|
|
|
const regions = [
|
|
{ label: 'Toutes les régions', value: '' },
|
|
{ label: 'Île-de-France', value: 'IDF' },
|
|
{ label: 'Auvergne-Rhône-Alpes', value: 'ARA' },
|
|
{ label: 'Nouvelle-Aquitaine', value: 'NA' },
|
|
{ label: 'Occitanie', value: 'OCC' },
|
|
{ label: 'Hauts-de-France', value: 'HDF' },
|
|
{ label: 'Grand Est', value: 'GE' },
|
|
{ label: 'Provence-Alpes-Côte d\'Azur', value: 'PACA' },
|
|
{ label: 'Pays de la Loire', value: 'PDL' },
|
|
{ label: 'Bretagne', value: 'BRE' },
|
|
{ label: 'Normandie', value: 'NOR' }
|
|
];
|
|
|
|
const secteurs = [
|
|
{ label: 'Tous secteurs', value: '' },
|
|
{ label: 'Gros œuvre', value: 'GROS_OEUVRE' },
|
|
{ label: 'Second œuvre', value: 'SECOND_OEUVRE' },
|
|
{ label: 'Génie civil', value: 'GENIE_CIVIL' },
|
|
{ label: 'Rénovation', value: 'RENOVATION' },
|
|
{ label: 'Aménagement', value: 'AMENAGEMENT' }
|
|
];
|
|
|
|
const specialitesDisponibles = [
|
|
{ label: 'Maçonnerie', value: 'MACONNERIE' },
|
|
{ label: 'Charpenterie', value: 'CHARPENTERIE' },
|
|
{ label: 'Couverture', value: 'COUVERTURE' },
|
|
{ label: 'Plomberie', value: 'PLOMBERIE' },
|
|
{ label: 'Électricité', value: 'ELECTRICITE' },
|
|
{ label: 'Carrelage', value: 'CARRELAGE' },
|
|
{ label: 'Peinture', value: 'PEINTURE' },
|
|
{ label: 'Menuiserie', value: 'MENUISERIE' },
|
|
{ label: 'Terrassement', value: 'TERRASSEMENT' },
|
|
{ label: 'Étanchéité', value: 'ETANCHEITE' }
|
|
];
|
|
|
|
useEffect(() => {
|
|
loadEntreprises();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
applyFilters();
|
|
}, [entreprises, globalFilter, regionFilter, secteurFilter, specialitesFilter, noteMinFilter, certifiesUniquement, budgetRange]);
|
|
|
|
const loadEntreprises = async () => {
|
|
setLoading(true);
|
|
try {
|
|
// TODO: Remplacer par un appel API réel
|
|
// const data = await annuaireService.getEntreprises();
|
|
|
|
// Afficher une liste vide plutôt que des données fictives
|
|
const data: EntrepriseProfile[] = [];
|
|
|
|
setEntreprises(data);
|
|
} catch (error) {
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Impossible de charger l\'annuaire',
|
|
life: 3000
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const applyFilters = () => {
|
|
let filtered = [...entreprises];
|
|
|
|
// Filtre global (recherche textuelle)
|
|
if (globalFilter) {
|
|
filtered = filtered.filter(entreprise =>
|
|
entreprise.nomCommercial.toLowerCase().includes(globalFilter.toLowerCase()) ||
|
|
entreprise.description.toLowerCase().includes(globalFilter.toLowerCase()) ||
|
|
entreprise.ville.toLowerCase().includes(globalFilter.toLowerCase()) ||
|
|
entreprise.specialites.some(s => s.toLowerCase().includes(globalFilter.toLowerCase()))
|
|
);
|
|
}
|
|
|
|
// Filtre par région
|
|
if (regionFilter) {
|
|
filtered = filtered.filter(entreprise => entreprise.region === regionFilter);
|
|
}
|
|
|
|
// Filtre par secteur
|
|
if (secteurFilter) {
|
|
filtered = filtered.filter(entreprise => entreprise.secteurActivite === secteurFilter);
|
|
}
|
|
|
|
// Filtre par spécialités
|
|
if (specialitesFilter.length > 0) {
|
|
filtered = filtered.filter(entreprise =>
|
|
specialitesFilter.some(spec => entreprise.specialites.includes(spec))
|
|
);
|
|
}
|
|
|
|
// Filtre par note minimale
|
|
if (noteMinFilter > 0) {
|
|
filtered = filtered.filter(entreprise => entreprise.noteGlobale >= noteMinFilter);
|
|
}
|
|
|
|
// Filtre certifiés uniquement
|
|
if (certifiesUniquement) {
|
|
filtered = filtered.filter(entreprise => entreprise.certifie);
|
|
}
|
|
|
|
// Filtre par budget
|
|
if (budgetRange[0] > 0 || budgetRange[1] < 1000000) {
|
|
filtered = filtered.filter(entreprise =>
|
|
entreprise.budgetMinProjet && entreprise.budgetMaxProjet &&
|
|
entreprise.budgetMaxProjet >= budgetRange[0] && entreprise.budgetMinProjet <= budgetRange[1]
|
|
);
|
|
}
|
|
|
|
setFilteredEntreprises(filtered);
|
|
setFirst(0); // Reset pagination
|
|
};
|
|
|
|
const getAbonnementSeverity = (type: string) => {
|
|
switch (type) {
|
|
case 'ENTERPRISE': return 'info';
|
|
case 'PREMIUM': return 'warning';
|
|
default: return 'info';
|
|
}
|
|
};
|
|
|
|
const getAbonnementLabel = (type: string) => {
|
|
switch (type) {
|
|
case 'ENTERPRISE': return 'Enterprise';
|
|
case 'PREMIUM': return 'Premium';
|
|
default: return 'Standard';
|
|
}
|
|
};
|
|
|
|
const contacterEntreprise = (entreprise: EntrepriseProfile) => {
|
|
// Logique de contact à implémenter
|
|
toast.current?.show({
|
|
severity: 'info',
|
|
summary: 'Contact',
|
|
detail: `Contactez ${entreprise.nomCommercial} via ${entreprise.emailContact}`,
|
|
life: 3000
|
|
});
|
|
};
|
|
|
|
const voirProfil = (entreprise: EntrepriseProfile) => {
|
|
// Navigation vers le profil détaillé
|
|
console.log('Voir profil:', entreprise);
|
|
};
|
|
|
|
const itemTemplate = (entreprise: EntrepriseProfile) => {
|
|
if (layout === 'list') {
|
|
return (
|
|
<div className="col-12">
|
|
<Card className="border-1 surface-border border-round m-2">
|
|
<div className="flex flex-column xl:flex-row xl:align-items-start p-4 gap-4">
|
|
<div className="flex align-items-center xl:align-items-start flex-column sm:flex-row gap-4">
|
|
<Avatar
|
|
image={entreprise.logoUrl}
|
|
icon="pi pi-building"
|
|
size="xlarge"
|
|
shape="circle"
|
|
className="bg-primary"
|
|
/>
|
|
<div className="flex flex-column align-items-center sm:align-items-start gap-3">
|
|
<div className="text-2xl font-bold text-900">
|
|
{entreprise.nomCommercial}
|
|
{entreprise.certifie && (
|
|
<Badge value="Certifiée" severity="success" className="ml-2" />
|
|
)}
|
|
</div>
|
|
<div className="flex align-items-center gap-3">
|
|
<Rating value={entreprise.noteGlobale} readOnly cancel={false} />
|
|
<span className="text-500">({entreprise.nombreAvis} avis)</span>
|
|
</div>
|
|
<div className="flex align-items-center gap-2">
|
|
<Tag value={secteurs.find(s => s.value === entreprise.secteurActivite)?.label || entreprise.secteurActivite}
|
|
severity="info" />
|
|
<Tag value={getAbonnementLabel(entreprise.typeAbonnement)}
|
|
severity={getAbonnementSeverity(entreprise.typeAbonnement)} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-column align-items-end">
|
|
<span className="text-xl font-semibold text-900 mb-2">{entreprise.ville}</span>
|
|
<div className="flex flex-wrap gap-1 mb-3">
|
|
{entreprise.specialites.slice(0, 3).map((spec, index) => (
|
|
<Chip key={index} label={spec} className="mr-1 mb-1" />
|
|
))}
|
|
{entreprise.specialites.length > 3 && (
|
|
<Chip label={`+${entreprise.specialites.length - 3}`} className="bg-primary" />
|
|
)}
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button
|
|
icon="pi pi-eye"
|
|
label="Voir profil"
|
|
className="p-button-outlined"
|
|
onClick={() => voirProfil(entreprise)}
|
|
/>
|
|
<Button
|
|
icon="pi pi-envelope"
|
|
label="Contacter"
|
|
onClick={() => contacterEntreprise(entreprise)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="surface-100 p-3 border-round">
|
|
<p className="text-700 line-height-3 m-0">
|
|
{entreprise.description}
|
|
</p>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="col-12 sm:col-6 lg:col-4 xl:col-3">
|
|
<Card className="border-1 surface-border border-round m-2 h-full">
|
|
<div className="flex flex-column h-full">
|
|
<div className="text-center mb-3">
|
|
<Avatar
|
|
image={entreprise.logoUrl}
|
|
icon="pi pi-building"
|
|
size="xlarge"
|
|
shape="circle"
|
|
className="bg-primary mb-3"
|
|
/>
|
|
<div className="text-xl font-bold text-900 mb-1">
|
|
{entreprise.nomCommercial}
|
|
</div>
|
|
<div className="flex justify-content-center align-items-center gap-2 mb-2">
|
|
<Rating value={entreprise.noteGlobale} readOnly cancel={false} />
|
|
<span className="text-500 text-sm">({entreprise.nombreAvis})</span>
|
|
</div>
|
|
<div className="flex justify-content-center gap-1 mb-2">
|
|
<Tag value={getAbonnementLabel(entreprise.typeAbonnement)}
|
|
severity={getAbonnementSeverity(entreprise.typeAbonnement)} />
|
|
{entreprise.certifie && (
|
|
<Tag value="Certifiée" severity="success" icon="pi pi-check" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-grow-1 mb-3">
|
|
<div className="text-center mb-2">
|
|
<i className="pi pi-map-marker text-primary mr-1"></i>
|
|
<span className="text-600">{entreprise.ville}</span>
|
|
</div>
|
|
|
|
<div className="text-center mb-3">
|
|
<Tag value={secteurs.find(s => s.value === entreprise.secteurActivite)?.label || entreprise.secteurActivite}
|
|
severity="info" className="mb-2" />
|
|
</div>
|
|
|
|
<div className="flex flex-wrap justify-content-center gap-1 mb-3">
|
|
{entreprise.specialites.slice(0, 2).map((spec, index) => (
|
|
<Chip key={index} label={spec} className="text-xs" />
|
|
))}
|
|
{entreprise.specialites.length > 2 && (
|
|
<Chip label={`+${entreprise.specialites.length - 2}`} className="bg-primary text-xs" />
|
|
)}
|
|
</div>
|
|
|
|
<p className="text-600 text-sm line-height-3">
|
|
{entreprise.description.substring(0, 120)}...
|
|
</p>
|
|
|
|
<div className="text-center text-500 text-sm">
|
|
<div><strong>{entreprise.nombreProjetsRealises}</strong> projets réalisés</div>
|
|
<div><strong>{entreprise.nombreClientsServis}</strong> clients servis</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
<Button
|
|
icon="pi pi-eye"
|
|
className="p-button-outlined flex-1"
|
|
onClick={() => voirProfil(entreprise)}
|
|
tooltip="Voir le profil"
|
|
/>
|
|
<Button
|
|
icon="pi pi-envelope"
|
|
className="flex-1"
|
|
onClick={() => contacterEntreprise(entreprise)}
|
|
tooltip="Contacter"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const header = () => {
|
|
return (
|
|
<div className="grid grid-nogutter">
|
|
<div className="col-12 md:col-6">
|
|
<div className="flex flex-column md:flex-row gap-3">
|
|
<span className="p-input-icon-left">
|
|
<i className="pi pi-search" />
|
|
<InputText
|
|
type="search"
|
|
value={globalFilter}
|
|
onChange={(e) => setGlobalFilter(e.target.value)}
|
|
placeholder="Rechercher une entreprise..."
|
|
className="w-full"
|
|
/>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="col-12 md:col-6">
|
|
<div className="flex justify-content-center md:justify-content-end gap-2">
|
|
<Button
|
|
icon="pi pi-th-large"
|
|
className={`p-button-outlined ${layout === 'grid' ? 'p-button-info' : ''}`}
|
|
onClick={() => setLayout('grid')}
|
|
tooltip="Vue grille"
|
|
/>
|
|
<Button
|
|
icon="pi pi-bars"
|
|
className={`p-button-outlined ${layout === 'list' ? 'p-button-info' : ''}`}
|
|
onClick={() => setLayout('list')}
|
|
tooltip="Vue liste"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="grid">
|
|
<Toast ref={toast} />
|
|
|
|
<div className="col-12">
|
|
<h2>Annuaire Professionnel BTP</h2>
|
|
<p className="text-600">Découvrez notre réseau d'entreprises BTP certifiées et trouvez le partenaire idéal pour vos projets.</p>
|
|
|
|
<Divider />
|
|
|
|
{/* Filtres */}
|
|
<Card title="Filtres de recherche" className="mb-4">
|
|
<div className="grid">
|
|
<div className="col-12 md:col-6 lg:col-3">
|
|
<label htmlFor="region" className="block text-900 font-medium mb-2">Région</label>
|
|
<Dropdown
|
|
id="region"
|
|
value={regionFilter}
|
|
options={regions}
|
|
onChange={(e) => setRegionFilter(e.value)}
|
|
placeholder="Toutes les régions"
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-6 lg:col-3">
|
|
<label htmlFor="secteur" className="block text-900 font-medium mb-2">Secteur d'activité</label>
|
|
<Dropdown
|
|
id="secteur"
|
|
value={secteurFilter}
|
|
options={secteurs}
|
|
onChange={(e) => setSecteurFilter(e.value)}
|
|
placeholder="Tous secteurs"
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-6 lg:col-3">
|
|
<label htmlFor="specialites" className="block text-900 font-medium mb-2">Spécialités</label>
|
|
<MultiSelect
|
|
id="specialites"
|
|
value={specialitesFilter}
|
|
options={specialitesDisponibles}
|
|
onChange={(e) => setSpecialitesFilter(e.value)}
|
|
placeholder="Sélectionner spécialités"
|
|
maxSelectedLabels={2}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-6 lg:col-3">
|
|
<label htmlFor="note" className="block text-900 font-medium mb-2">Note minimale: {noteMinFilter}/5</label>
|
|
<Slider
|
|
id="note"
|
|
value={noteMinFilter}
|
|
onChange={(e) => setNoteMinFilter(e.value as number)}
|
|
min={0}
|
|
max={5}
|
|
step={0.5}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid mt-3">
|
|
<div className="col-12 md:col-6">
|
|
<div className="field-checkbox">
|
|
<input
|
|
type="checkbox"
|
|
id="certifies"
|
|
checked={certifiesUniquement}
|
|
onChange={(e) => setCertifiesUniquement(e.target.checked)}
|
|
/>
|
|
<label htmlFor="certifies" className="ml-2">Entreprises certifiées uniquement</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-6">
|
|
<label className="block text-900 font-medium mb-2">
|
|
Budget projet: {budgetRange[0].toLocaleString()}€ - {budgetRange[1].toLocaleString()}€
|
|
</label>
|
|
<Slider
|
|
value={budgetRange}
|
|
onChange={(e) => setBudgetRange(e.value as [number, number])}
|
|
range
|
|
min={0}
|
|
max={1000000}
|
|
step={10000}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
<DataView
|
|
value={filteredEntreprises}
|
|
itemTemplate={itemTemplate}
|
|
layout={layout}
|
|
header={header()}
|
|
paginator
|
|
rows={rows}
|
|
first={first}
|
|
onPage={(e) => setFirst(e.first)}
|
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown"
|
|
rowsPerPageOptions={[12, 24, 48]}
|
|
loading={loading}
|
|
emptyMessage="Aucune entreprise ne correspond à vos critères."
|
|
paginatorLeft={
|
|
<span className="text-600">
|
|
{filteredEntreprises.length} entreprise{filteredEntreprises.length !== 1 ? 's' : ''} trouvée{filteredEntreprises.length !== 1 ? 's' : ''}
|
|
</span>
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AnnuaireProfessionnel; |