Créer un site internet

restrdyfugihj

import React, { useMemo, useState, useRef } from "react"; import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet"; import MarkerClusterGroup from "react-leaflet-markercluster"; import L from "leaflet"; import "leaflet/dist/leaflet.css"; import "react-leaflet-markercluster/dist/styles.min.css"; // Optional libraries expected to be available in the host app (install via npm/yarn): // react, react-dom, react-leaflet, leaflet, react-leaflet-markercluster, xlsx, file-saver, recharts, tailwindcss /* Carte interactive des métiers — version améliorée - Export CSV & XLSX (client-side) - Clustering des marqueurs - Mini-sparklines générées dynamiquement dans les popups - Toggle clustering, filtres, recherche, et export - Iframe/embed instructions incluses en commentaire Dépose ce fichier dans une application React avec Tailwind. Toutes les dépendances listées ci-dessus doivent être installées. */ // --------------------------- // Dataset (identique à celui fourni) // --------------------------- const DATA = [ /* (le même tableau DATA que précédemment) */ { profession: "Infirmiers diplômés d'État", description: "Professionnels de santé assurant les soins infirmiers et l'accompagnement des patients", secteur: "Santé", effectifs: 764000, departures: 185000, densite: 1140.5, formation: "Diplôme d'État infirmier (3 ans)", remuneration: "28 000 - 45 000 € brut/an", tension: "Modérée", evolution: 3.2, }, // ... (les autres entrées exactement comme dans la version précédente) ]; // --------------------------- // Helpers // --------------------------- function hashToLatLng(key) { let h = 2166136261; for (let i = 0; i < key.length; i++) { h ^= key.charCodeAt(i); h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24); } const rand = (h >>> 0) / 4294967295; const lat = 42 + rand * 9; // 42 to 51 const lon = -5 + ((rand * 9973) % 13); // -5 to +8 return [lat, lon]; } const SECTOR_COLORS = { Santé: "#e34a33", Artisanat: "#2b8cbe", Commerce: "#66c2a5", Services: "#f1a340", }; function getColorBySector(sector) { return SECTOR_COLORS[sector] || "#999999"; } function makeIcon(sector, effectifs) { const size = Math.max(26, Math.min(56, 26 + Math.log10(effectifs) * 8)); const color = getColorBySector(sector); return L.divIcon({ className: "custom-marker", html: `
${sector[0]}
`, iconSize: [size, size], iconAnchor: [size / 2, size / 2], }); } // Generate a small synthetic time series for sparklines based on 'effectifs' and 'evolution' function syntheticTrend(effectifs, evolution) { const base = Math.round(effectifs / 1000); const points = 8; const arr = []; for (let i = 0; i < points; i++) { // older -> more negative drift depending on evolution const factor = 1 + ((evolution / 100) * (i - points + 1)) / points; arr.push(Math.max(1, Math.round(base * factor + (Math.sin(i) * 0.2 * base)))); } return arr; } // Simple inline sparkline SVG (small, dependency-free) function Sparkline({ data }) { const w = 120; const h = 28; const max = Math.max(...data); const min = Math.min(...data); const points = data .map((d, i) => { const x = (i / (data.length - 1)) * w; const y = h - ((d - min) / (max - min || 1)) * h; return `${x},${y}`; }) .join(" "); const last = data[data.length - 1]; const color = last >= data[0] ? "#16a34a" : "#ef4444"; return ( ); } // CSV & XLSX export helpers async function exportCSV(data) { const header = [ "profession", "secteur", "effectifs", "departures", "densite", "formation", "remuneration", "tension", "evolution", ]; const rows = [header.join(",")]; data.forEach((d) => { const row = [ `"${d.profession.replace(/"/g, '""')}"`, d.secteur, d.effectifs, d.departures, d.densite, `"${(d.formation || "").replace(/"/g, '""')}"`, `"${(d.remuneration || "").replace(/"/g, '""')}"`, d.tension, d.evolution, ]; rows.push(row.join(",")); }); const csvContent = rows.join(" "); const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `metiers_export_${new Date().toISOString().slice(0,10)}.csv`; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); } async function exportXLSX(data) { // lazy-import xlsx & file-saver to avoid bundling if not used const [{ utils, write }, { saveAs }] = await Promise.all([import("xlsx"), import("file-saver")]); const wsData = [ [ "profession", "secteur", "effectifs", "departures", "densite", "formation", "remuneration", "tension", "evolution", ], ]; data.forEach((d) => { wsData.push([d.profession, d.secteur, d.effectifs, d.departures, d.densite, d.formation, d.remuneration, d.tension, d.evolution]); }); const ws = utils.aoa_to_sheet(wsData); const wb = utils.book_new(); utils.book_append_sheet(wb, ws, "Metiers"); const wbout = write(wb, { bookType: "xlsx", type: "array" }); saveAs(new Blob([wbout], { type: "application/octet-stream" }), `metiers_export_${new Date().toISOString().slice(0,10)}.xlsx`); } // --------------------------- // Main component // --------------------------- export default function InteractiveMetiersMapEnhanced() { const [sectorFilter, setSectorFilter] = useState("Tous"); const [search, setSearch] = useState(""); const [tensionFilter, setTensionFilter] = useState("Tous"); const [clusterEnabled, setClusterEnabled] = useState(true); const [embedOpen, setEmbedOpen] = useState(false); const mapRef = useRef(null); const professions = useMemo(() => { return DATA.map((d) => ({ ...d, coords: hashToLatLng(d.profession) })); }, []); const sectors = useMemo(() => Array.from(new Set(DATA.map((d) => d.secteur))), []); const tensions = ["Très forte", "Forte", "Modérée", "Faible"]; const filtered = professions.filter((p) => { if (sectorFilter !== "Tous" && p.secteur !== sectorFilter) return false; if (tensionFilter !== "Tous" && p.tension !== tensionFilter) return false; if (search && !p.profession.toLowerCase().includes(search.toLowerCase())) return false; return true; }); return (
(mapRef.current = m)} center={[46.5, 2.0]} zoom={5} style={{ height: "100%", width: "100%" }}> {clusterEnabled ? ( {filtered.map((p) => (

{p.profession}

{p.secteur} • {p.tension}
Effectifs : {p.effectifs.toLocaleString()}
Départs : {p.departures.toLocaleString()}
Densité : {p.densite} /100k hab.
Salaire : {p.remuneration}
Formation : {p.formation}
Évolution : {p.evolution}%
Tendance
))}
) : ( filtered.map((p) => (

{p.profession}

{p.secteur} • {p.tension} tension
Effectifs : {p.effectifs.toLocaleString()}
Départs prévus : {p.departures.toLocaleString()}
Densité : {p.densite} /100k hab.
Salaire (approx.) : {p.remuneration}
Formation : {p.formation}
Évolution : {p.evolution}%
)) )}
{embedOpen && (
Code d'intégration (iframe)
Copie/colle dans ton site