Application HSE #17270
Unanswered
eloumaima251-coder
asked this question in
Show and tell
Application HSE
#17270
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
import { useState } from "react";
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip,
ResponsiveContainer, PieChart, Pie, Cell, Legend, LineChart, Line
} from "recharts";
// ── Design tokens ────────────────────────────────────────────────────────
const C = {
bg: "#0A1628", panel: "#0F1F38", card: "#162844", border: "#1E3A5A",
accent: "#F97316", accentSoft: "#38BDF8", danger: "#EF4444",
warn: "#FBBF24", ok: "#22C55E", purple: "#A78BFA",
text: "#E2EAF4", muted: "#6B8CAE", white: "#FFFFFF",
};
// ── ALL DATA ─────────────────────────────────────────────────────────────
const DATA = {
site: "HOTEL BRISTOL TANGER",
dateReport: "12/06/2026",
// Incident
incident: {
ref: "FO.M2.11.v3", date: "11/06/2026", heure: "11H57",
type: "Premier soin", chef: "LOUAH ABDELTIF",
victime: { nom: "ETTAYAA", prenom: "MOUHCINE" },
description: "Lors des travaux de montage des poteaux de construction, une équipe d'ouvriers procédait à l'installation des éléments métalliques. Le chef d'équipe Qadouri Lhcen a tenté de dégager un élément métallique à l'aide d'une brosse métallique, qui a glissé et heurté un autre ouvrier au niveau de la joue.",
causesDirect: ["Glissement incontrôlé de la barre métallique lors de la manipulation", "Présence d'un opérateur dans la trajectoire de l'élément libéré"],
causesAnalyse: ["Méthode de dégagement inadaptée", "Difficulté de manutention de l'élément métallique", "Évaluation insuffisante des risques avant l'intervention", "Absence de zone d'exclusion autour de l'opération"],
actionsImm: ["Arrêt immédiat des travaux", "Prise en charge du blessé et transfert à l'infirmerie", "Sécurisation de la zone", "Information du responsable HSE et de l'encadrement"],
actionsCorr: ["Vérification de l'état de santé et suivi médical jusqu'au rétablissement", "Analyse de l'incident avec l'équipe pour identifier causes et facteurs contributifs", "Révision de la méthode de travail et mise en place d'un mode opératoire sécurisé"],
actionsPrev: ["Sensibilisation aux risques liés aux éléments sous tension ou bloqués", "Mise en place d'une zone d'exclusion lors des opérations de dégagement", "Utilisation d'outils adaptés à la tâche", "Réalisation d'une analyse de risque avant toute intervention similaire"],
},
// KPIs VCEA
kpi: {
modeOp: 18, pretask: 18, comiteAnalyse: 52, prestartMeeting: 84,
sanctionsNeg: 34, sanctionsPos: 0, stopRealises: 0,
risquesMajeurs: [
{ name: "Chutes de hauteur", val: 1 }, { name: "Électricité", val: 1 },
{ name: "Effondrement", val: 1 }, { name: "Collisions engin/piéton", val: 1 },
{ name: "Manut. mécanique", val: 1 }, { name: "Manut. manuelle", val: 0 },
{ name: "Organes en mvt", val: 0 }, { name: "Chutes d'objets", val: 0 },
{ name: "Circulation routière", val: 0 },
],
vhse: [
{ role: "RESP QSE", pct: 80 }, { role: "RESP INDUSTRIE", pct: 80 },
{ role: "RESP. MATERIEL", pct: 80 }, { role: "CHEF CHANTIER", pct: 80 },
{ role: "CDTX", pct: 80 }, { role: "DIR. TRAVAUX", pct: 0 },
{ role: "DIR. PROJET", pct: 0 }, { role: "DEX", pct: 0 },
{ role: "CHEF D'AGENCE", pct: 0 }, { role: "DR", pct: 0 },
],
prestartByMonth: [
{ m: "Jan", v: 18 }, { m: "Fév", v: 18 }, { m: "Mar", v: 23 },
{ m: "Avr", v: 25 }, { m: "Mai", v: 0 }, { m: "Jun", v: 0 },
],
},
// Weekly KPI indicators (18–24 Mai 2026)
weeklyKPI: {
week: "18–24 MAI 2026",
indicators: [
{ label: "HSSE Observations", mon: 1, tue: 1, wed: 1, thu: 1, fri: 1, sat: 0, sun: 0 },
{ label: "Job Safety Analysis", mon: "", tue: "", wed: "", thu: "", fri: "", sat: 0, sun: 0 },
{ label: "Tool Box Talks TBT", mon: 1, tue: 1, wed: 1, thu: 1, fri: 1, sat: 0, sun: 0 },
{ label: "Medical Treatment MTC", mon: "-", tue: 1, wed: 2, thu: 1, fri: "-", sat: "-", sun: "-" },
{ label: "Lost Time Injury LTI", mon: "-", tue: "-", wed: "-", thu: "-", fri: "-", sat: "-", sun: "-" },
{ label: "Near Miss NM", mon: "-", tue: "-", wed: "-", thu: "-", fri: "-", sat: "-", sun: "-" },
{ label: "First Aid Case FAC", mon: "-", tue: "-", wed: "-", thu: "-", fri: "-", sat: "-", sun: "-" },
{ label: "Environmental Incident", mon: "-", tue: "-", wed: "-", thu: "-", fri: "-", sat: "-", sun: "-" },
],
},
// Manpower
manpower: {
week: "18–24 MAI 2026",
companies: [
{ name: "SOGEA", d18: 170, d19: 171, d20: 113, d21: 101, d22: 52, d23: 1, d24: "-" },
{ name: "TECHLIGHT", d18: "-", d19: "-", d20: "-", d21: "-", d22: "-", d23: "-", d24: "-" },
{ name: "GICC", d18: 5, d19: 0, d20: 0, d21: 0, d22: 0, d23: "-", d24: "-" },
{ name: "FREYSSINET", d18: 1, d19: 1, d20: 1, d21: 1, d22: 1, d23: "-", d24: "-" },
{ name: "COJAR", d18: 1, d19: 1, d20: 1, d21: 1, d22: 1, d23: "-", d24: "-" },
{ name: "SOTHECA", d18: 13, d19: 14, d20: 12, d21: 4, d22: 0, d23: "-", d24: "-" },
{ name: "HIKMAT", d18: 0, d19: 0, d20: 0, d21: 0, d22: 0, d23: "-", d24: "-" },
{ name: "STARTEEN", d18: 6, d19: 0, d20: 0, d21: 0, d22: 0, d23: "-", d24: "-" },
{ name: "MIF MAROC", d18: 18, d19: 22, d20: 15, d21: 17, d22: 8, d23: 14, d24: "-" },
{ name: "PLATE DE REVE", d18: 0, d19: 0, d20: 0, d21: 0, d22: 0, d23: "-", d24: "-" },
],
ptd: { attendance: 779, totalWorkforce: 6621.5, hoursConsumed: 6621.5 },
chartData: [
{ day: "18/05", SOGEA: 170, GICC: 5, FREYSSINET: 1, COJAR: 1, SOTHECA: 13, STARTEEN: 6, MIF: 18 },
{ day: "19/05", SOGEA: 171, GICC: 0, FREYSSINET: 1, COJAR: 1, SOTHECA: 14, STARTEEN: 0, MIF: 22 },
{ day: "20/05", SOGEA: 113, GICC: 0, FREYSSINET: 1, COJAR: 1, SOTHECA: 12, STARTEEN: 0, MIF: 15 },
{ day: "21/05", SOGEA: 101, GICC: 0, FREYSSINET: 1, COJAR: 1, SOTHECA: 4, STARTEEN: 0, MIF: 17 },
{ day: "22/05", SOGEA: 52, GICC: 0, FREYSSINET: 1, COJAR: 1, SOTHECA: 0, STARTEEN: 0, MIF: 8 },
],
},
// Induction
inductions: [
{ id: 751, nom: "BENCHEIKH", prenom: "BADR", cin: "GK116817", nat: "MORROCCAN", co: "SOTHECA", role: "SOUDEUR", date: "18/05/2026" },
{ id: 752, nom: "NASSIN", prenom: "OUWRAYEN", cin: "K481458", nat: "MORROCCAN", co: "MIF MAROC", role: "VISITEUR", date: "18/05/2026" },
{ id: 753, nom: "NAHJE", prenom: "EL MEHDI", cin: "BH390358", nat: "MORROCCAN", co: "MIF MAROC", role: "VISITEUR", date: "18/05/2026" },
{ id: 754, nom: "TAOUFIK", prenom: "OCHAN", cin: "L573189", nat: "MORROCCAN", co: "MIF MAROC", role: "VISITEUR", date: "18/05/2026" },
{ id: 756, nom: "QAOUFI", prenom: "MOHAMED", cin: "", nat: "MORROCCAN", co: "FREYSSINET", role: "VISITEUR DR", date: "19/05/2026" },
{ id: 757, nom: "COUSUUOD", prenom: "DAREON", cin: "", nat: "MORROCCAN", co: "FREYSSINET", role: "VISITEUR", date: "19/05/2026" },
{ id: 758, nom: "FRASOR", prenom: "DROMMEND", cin: "", nat: "MORROCCAN", co: "FREYSSINET", role: "VISITEUR", date: "19/05/2026" },
],
// Training
trainings: [
{ id: 1, dates: "31/07–01/08/2025", trainer: "ADIL BILLI", topic: "SST", duration: "2 JOURS", participants: 15, hours: 240 },
{ id: 2, dates: "08/10/2025", trainer: "KHAZAALI MOHAMED", topic: "LIFTING & RIGGING", duration: "1 JOUR", participants: 8, hours: 64 },
{ id: 3, dates: "11/10/2025", trainer: "ADIL BILLI", topic: "FIRE FIGHTING", duration: "1 JOUR", participants: 15, hours: 120 },
{ id: 4, dates: "10–14/11/2025", trainer: "-", topic: "SAFAR SKILLS", duration: "5 JOURS", participants: 17, hours: 680 },
{ id: 5, dates: "17–18/12/2025", trainer: "ABDELLAH", topic: "TRAVAIL EN HAUTEUR", duration: "2 JOURS", participants: 40, hours: 320 },
{ id: 6, dates: "06–07/02/2026", trainer: "ADIL BILLI", topic: "SST", duration: "2 JOURS", participants: 21, hours: 168 },
],
// Equipment
equipment: [
{ n: 1, type: "GRUE A TOUR 1", co: "SOGEA MAROC", report: "CR-LY/MA-25-253/MAZ-001", serial: "MD265 B1 F11020", operator: "KOUSKIR KAMAL", cin: "IC120022", expTrain: "14.05.2026", expAuth: "14.05.2026", obs: "N,T,R" },
{ n: 2, type: "GRUE A TOUR 2", co: "SOGEA MAROC", report: "BCTI/LEY/143/2025", serial: "MDT 268 SM/DM F1848", operator: "SAID CHAHBOUT TABAA MOHAMED", cin: "Z484894", expTrain: "11.04.2027", expAuth: "11.04.2027", obs: "N,T,R" },
{ n: 3, type: "PELLETEUSE", co: "ICONSTRUCTION", report: "RML6232025", serial: "CAT0336DHKDJ 00316", operator: "JAMAL SOUHNI", cin: "CB243907", expTrain: "05.07.2027", expAuth: "05.07.2027", obs: "R,A,S" },
],
// Chemical products
chemicals: [
{ name: "ASTRAL", use: "METAL PAINTING", storage: "SOGEA STORE", maxQty: "100 KG", consumed: "25 KG", msds: true, drip: true, fire: true, trained: true, spill: false, ventilated: true },
{ name: "COLLE DIVLYNE", use: "Assembly of materials", storage: "SOGEA STORE", maxQty: "-", consumed: "-", msds: true, drip: true, fire: true, trained: true, spill: false, ventilated: true },
{ name: "OLA PVC", use: "PVC pipes / watering", storage: "SOGEA STORE", maxQty: "-", consumed: "-", msds: true, drip: false, fire: false, trained: true, spill: false, ventilated: true },
{ name: "DILUANT", use: "Mixing with NPU 150", storage: "SOGEA STORE", maxQty: "-", consumed: "-", msds: true, drip: true, fire: true, trained: true, spill: false, ventilated: true },
{ name: "GAZOIL", use: "ENGINE", storage: "SOGEA STORE", maxQty: "-", consumed: "-", msds: true, drip: true, fire: true, trained: true, spill: false, ventilated: true },
{ name: "SIKA LATEX", use: "Mixing concrete", storage: "SOGEA STORE", maxQty: "-", consumed: "-", msds: true, drip: false, fire: false, trained: true, spill: false, ventilated: true },
{ name: "GALIOTTE WHITE SPIRIT", use: "Cleaner painting equip.", storage: "SOGEA STORE", maxQty: "-", consumed: "-", msds: true, drip: true, fire: true, trained: true, spill: false, ventilated: true },
{ name: "NOVA TRUST", use: "METAL PAINTING", storage: "SOGEA STORE", maxQty: "-", consumed: "-", msds: true, drip: true, fire: true, trained: true, spill: false, ventilated: true },
{ name: "EMUFAL SOLID", use: "Protection surfaces", storage: "SOGEA STORE", maxQty: "-", consumed: "-", msds: true, drip: false, fire: true, trained: true, spill: false, ventilated: true },
],
// Inspections tools
inspections: [
{ id: "INS-001", outil: "Meuleuse", ref: "FO.M2.68", emoji: "⚙️", checks: ["État interrupteur", "Disque de meulage", "Fixation disque", "Carter de protection", "Cordon alimentation", "Câble électrique", "N° identification", "Taggage code couleur", "Code couleur trimestre"] },
{ id: "INS-002", outil: "Scie circulaire", ref: "FO.M2.68", emoji: "🔪", checks: ["Carter de protection", "État de la lame", "Fixation de la lame", "Cordon alimentation", "Câble électrique", "N° identification", "Taggage code couleur"] },
{ id: "INS-003", outil: "Perceuse", ref: "FO.M2.68", emoji: "🔩", checks: ["Fonctionnement gâchette", "Serrage du mandrin", "Fixation de la miche", "Cordon alimentation", "Câble alimentation", "N° identification", "Taggage code couleur"] },
{ id: "INS-004", outil: "Benne à gravats", ref: "FO.M2.71.v1", emoji: "🪣", checks: ["Crochet de suspension (corrosion)", "Système verrouillage/déverrouillage", "Palonnier rabattable", "Taggage code couleur", "État général (propreté)"] },
],
// HSE Visits
hseVisits: [
{ date: "24/04/2026", inspector: "Aya Fatih Tahri", phase: "Excavation sous sol à R/C", team: "4 Poseurs / 4 Maçons / 5 Manutentionnaires / 1 Soudeur", conclGeneral: "Chantier propre, bien rangé avec quelques bonnes pratiques (attention à l'éclairage)", observations: ["Mettre des leds dans les plafonds des locaux", "Marquer les zones à risque avec de la peinture au sol"] },
{ date: "29/04/2026", inspector: "ZARROU EL MOSTAKBIL", phase: "Travaux Façade Extérieur", team: "Abdislam CF", conclGeneral: "Procéder à changer l'accès au client & Sécuriser l'équipe de pont, et raccrocher les Harnaix", observations: ["Horaires & Accès Chantier — RAS", "Modifier accès client et sécurité"] },
{ date: "01/04/2026", inspector: "SUSTANI SHERIBA / Abdache Saidi", phase: "Travaux G.O.", team: "-", conclGeneral: "Chantier propre", observations: ["Magasin dédié aux produits dangereux avec rétention", "Attention à faire le check quotidien des PTE — 2 ponts à régulariser", "Interface avec TSCC à améliorer (ne pas câbler les balustres)"] },
],
};
// ── UI Helpers ─────────────────────────────────────────────────────────────
const Badge = ({ color, children, small }) => (
<span style={{ background: color + "22", color, border:
1px solid ${color}44, borderRadius: 4, padding: small ? "1px 6px" : "2px 9px", fontSize: small ? 10 : 11, fontWeight: 700, whiteSpace: "nowrap" }}>{children});
const Card = ({ title, icon, children, style }) => (
// ── Navigation ─────────────────────────────────────────────────────────────⚠️ " },
const navItems = [
{ id: "dashboard", label: "Dashboard", icon: "📊" },
{ id: "incident", label: "Presque-Accident", icon: "
{ id: "weekly", label: "Rapport Hebdo", icon: "📅" },
{ id: "manpower", label: "Main d'Œuvre", icon: "👷" },
{ id: "induction", label: "Induction", icon: "📋" },
{ id: "training", label: "Formations", icon: "🎓" },
{ id: "equipment", label: "Équipements", icon: "🏗️" },
{ id: "chemical", label: "Produits Chim.", icon: "⚗️" },
{ id: "inspection", label: "Inspections", icon: "🔍" },
{ id: "hsevisit", label: "Visites HSE", icon: "👁️" },
];
// ── App ─────────────────────────────────────────────────────────────────────
export default function HSEApp() {
const [tab, setTab] = useState("dashboard");
return (
<div style={{ fontFamily: "'Inter','Segoe UI',sans-serif", background: C.bg, minHeight: "100vh", color: C.text, display: "flex", flexDirection: "column" }}>
{/* Header */}
<header style={{ background: C.panel, borderBottom:
2px solid ${C.accent}, padding: "0 20px", display: "flex", alignItems: "center", justifyContent: "space-between", height: 52, flexShrink: 0 }}><div style={{ display: "flex", alignItems: "center", gap: 10 }}>
<div style={{ width: 30, height: 30, background: C.accent, borderRadius: 6, display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 900, fontSize: 15, flexShrink: 0 }}>S
<div style={{ fontWeight: 800, fontSize: 14 }}>SOGEA MAROC — Système Digital HSE
<div style={{ fontSize: 10, color: C.muted }}>{DATA.site} · {DATA.dateReport}
<div style={{ display: "flex", gap: 6 }}>
EN COURS
HÔTEL BRISTOL
);
}
// ── DASHBOARD ──────────────────────────────────────────────────────────────
function DashboardView() {
const k = DATA.kpi;
return (
<div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 10, marginBottom: 20 }}>
);
}
// ── INCIDENT ───────────────────────────────────────────────────────────────
function IncidentView() {
const [open, setOpen] = useState("causes");
const inc = DATA.incident;
return (
<SectionHeader title="Rapport Presque-Accident / Premier Soin" sub={
Réf. ${inc.ref} · ${inc.date} · ${inc.heure}} /><div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10, marginBottom: 16 }}>
{[["Date", inc.date], ["Heure", inc.heure], ["Site", DATA.site], ["Zone", "Chantier"], ["Chef", inc.chef], ["Type", inc.type]].map(([l, v]) => (
))}
<Card title="Victime" icon="🧑🦱" style={{ marginBottom: 12 }}>
<div style={{ display: "flex", gap: 24, fontSize: 13 }}>
<span style={{ color: C.muted }}>Nom : {inc.victime.nom}
<span style={{ color: C.muted }}>Prénom : {inc.victime.prenom}
<span style={{ color: C.muted }}>Employeur : SOGEA
<Card title="Description de l'événement" icon="📝" style={{ marginBottom: 12 }}>
<p style={{ fontSize: 13, lineHeight: 1.7, margin: 0 }}>{inc.description}
{[
{ key: "causes", title: "Causes Directes", icon: "🎯", items: inc.causesDirect, color: C.danger },
{ key: "analyse", title: "Analyse des Causes", icon: "🔬", items: inc.causesAnalyse, color: C.warn },
{ key: "imm", title: "Actions Immédiates", icon: "🚨", items: inc.actionsImm, color: C.accentSoft },
{ key: "corr", title: "Actions Correctives", icon: "🔧", items: inc.actionsCorr, color: C.ok },
{ key: "prev", title: "Actions Préventives", icon: "🛡️", items: inc.actionsPrev, color: C.accent },
].map(({ key, title, icon, items, color }) => (
<div key={key} style={{ marginBottom: 6 }}>
<button onClick={() => setOpen(open === key ? null : key)} style={{
width: "100%", background: open === key ? color + "15" : C.card, border:
1px solid ${open === key ? color + "66" : C.border},borderRadius: open === key ? "8px 8px 0 0" : 8, padding: "11px 16px", cursor: "pointer",
color: C.text, display: "flex", alignItems: "center", justifyContent: "space-between", fontSize: 13, fontWeight: 700,
}}>
{icon} {title}
{items.length}
{open === key && (
<div style={{ background: C.panel, border:
1px solid ${color + "44"}, borderTop: "none", borderRadius: "0 0 8px 8px", padding: "10px 16px" }}>{items.map((item, i) => (
<div key={i} style={{ display: "flex", gap: 8, alignItems: "flex-start", padding: "5px 0", borderBottom: i < items.length - 1 ?
1px solid ${C.border}: "none" }}><span style={{ color, fontSize: 9, marginTop: 6, flexShrink: 0 }}>◆
<span style={{ fontSize: 13, color: C.text }}>{item}
))}
)}
))}
);
}
// ── WEEKLY KPI ─────────────────────────────────────────────────────────────
function WeeklyView() {
const { indicators } = DATA.weeklyKPI;
const days = ["18/05", "19/05", "20/05", "21/05", "22/05", "23/05", "24/05"];
const dayKeys = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
const dayLabels = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"];
const getColor = (v) => {
if (v === "-" || v === "") return C.muted;
if (Number(v) >= 2) return C.danger;
if (Number(v) >= 1) return C.warn;
return C.muted;
};
return (
<Card style={{ overflowX: "auto" }}>
<table style={{ width: "100%", borderCollapse: "collapse" }}>
<TH style={{ minWidth: 200 }}>Indicateur
{dayLabels.map((d, i) => <TH key={i} style={{ textAlign: "center", minWidth: 90 }}>{d}
<span style={{ fontWeight: 400, fontSize: 10, color: C.muted }}>{days[i]})}
{indicators.map((ind, ri) => (
<tr key={ri} style={{ background: ri % 2 === 0 ? C.panel : C.card }}>
<td style={{ padding: "8px 10px", color: C.text, fontSize: 12, fontWeight: 600, borderBottom:
1px solid ${C.border}}}>{ind.label}{dayKeys.map((dk, di) => {
const v = ind[dk];
return (
<td key={di} style={{ textAlign: "center", borderBottom:
1px solid ${C.border}, padding: "6px" }}><span style={{ fontWeight: 700, fontSize: 13, color: getColor(v) }}>{v === "" ? "—" : v}
);
})}
))}
<div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 12, marginTop: 14 }}>
);
}
// ── MANPOWER ───────────────────────────────────────────────────────────────
function ManpowerView() {
const { companies, ptd, chartData } = DATA.manpower;
const days = ["18/05", "19/05", "20/05", "21/05", "22/05", "23/05", "24/05"];
const keys = ["d18", "d19", "d20", "d21", "d22", "d23", "d24"];
const colors = [C.accentSoft, C.accent, C.ok, C.warn, C.purple, "#F472B6", "#34D399", "#60A5FA", "#FB923C", C.danger];
return (
<div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 12, marginBottom: 16 }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14, marginBottom: 14 }}>
<XAxis dataKey="day" tick={{ fill: C.muted, fontSize: 10 }} />
<YAxis tick={{ fill: C.muted, fontSize: 10 }} />
<Tooltip contentStyle={{ background: C.card, border:
1px solid ${C.border}, color: C.text, fontSize: 11 }} /><Legend wrapperStyle={{ fontSize: 10 }} />
{["SOGEA", "GICC", "SOTHECA", "MIF"].map((k, i) => )}
<Pie data={[
{ name: "SOGEA (608)", value: 608 },
{ name: "MIF MAROC (106)", value: 106 },
{ name: "SOTHECA (44)", value: 44 },
{ name: "Autres (21)", value: 21 },
]} cx="50%" cy="50%" innerRadius={50} outerRadius={75} paddingAngle={3} dataKey="value">
{colors.map((c, i) => )}
<Legend wrapperStyle={{ fontSize: 10 }} />
<Tooltip contentStyle={{ background: C.card, border:
1px solid ${C.border}, color: C.text, fontSize: 11 }} /><Card title="Détail Journalier par Entreprise" icon="📋" style={{ overflowX: "auto" }}>
<table style={{ width: "100%", borderCollapse: "collapse" }}>
<TH style={{ minWidth: 120 }}>Entreprise
{days.map(d => <TH key={d} style={{ textAlign: "center" }}>{d})}
{companies.map((c, ri) => {
const total = keys.reduce((s, k) => { const v = c[k]; return s + (typeof v === "number" ? v : 0); }, 0);
return (
<tr key={ri} style={{ background: ri % 2 === 0 ? C.panel : C.card }}>
<td style={{ padding: "6px 10px", fontWeight: 700, fontSize: 12, color: colors[ri % colors.length], borderBottom:
1px solid ${C.border}}}>{c.name}{keys.map(k => <TD key={k} center style={{ color: typeof c[k] === "number" && c[k] > 0 ? C.text : C.muted }}>{c[k]})}
);
})}
);
}
// ── INDUCTION ─────────────────────────────────────────────────────────────
function InductionView() {
const [filter, setFilter] = useState("TOUS");
const companies = ["TOUS", ...new Set(DATA.inductions.map(i => i.co))];
const filtered = filter === "TOUS" ? DATA.inductions : DATA.inductions.filter(i => i.co === filter);
return (
<div style={{ display: "flex", gap: 8, marginBottom: 16, flexWrap: "wrap" }}>
{companies.map(c => (
<button key={c} onClick={() => setFilter(c)} style={{ background: filter === c ? C.accent : C.card, border:
1px solid ${filter === c ? C.accent : C.border}, color: filter === c ? C.white : C.text, borderRadius: 6, padding: "5px 12px", cursor: "pointer", fontSize: 12, fontWeight: filter === c ? 700 : 400 }}>{c}))}
<div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 10, marginBottom: 16 }}>
<Stat label="Entreprises" value={new Set(DATA.inductions.map(i => i.co)).size} color={C.ok} />
<Card style={{ overflowX: "auto" }}>
<table style={{ width: "100%", borderCollapse: "collapse" }}>
{["#", "Nom", "Prénom", "CIN", "Nationalité", "Entreprise", "Rôle", "Date"].map(h => {h})}
{filtered.map((ind, i) => (
<tr key={i} style={{ background: i % 2 === 0 ? C.panel : C.card }}>
<span style={{ color: C.muted, fontFamily: "monospace" }}>{ind.id}
{ind.nom}
{ind.prenom}
<span style={{ fontFamily: "monospace", fontSize: 11, color: C.muted }}>{ind.cin || "—"}
{ind.nat}
<b style={{ color: C.accent }}>{ind.co}
<Badge color={ind.role.includes("VISITEUR") ? C.warn : C.ok} small>{ind.role}
{ind.date}
))}
);
}
// ── TRAINING ───────────────────────────────────────────────────────────────
function TrainingView() {
const totalH = DATA.trainings.reduce((s, t) => s + t.hours, 0);
const totalP = DATA.trainings.reduce((s, t) => s + t.participants, 0);
return (
<div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 10, marginBottom: 16 }}>
<div style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: 14, marginBottom: 14 }}>
<XAxis dataKey="id" tick={{ fill: C.muted, fontSize: 10 }} />
<YAxis tick={{ fill: C.muted, fontSize: 10 }} />
<Tooltip formatter={(v, n, p) => [v + " h", p.payload.topic]} contentStyle={{ background: C.card, border:
1px solid ${C.border}, color: C.text, fontSize: 11 }} /><BarChart data={DATA.trainings} layout="vertical" margin={{ left: 80, right: 20 }}>
<XAxis type="number" tick={{ fill: C.muted, fontSize: 9 }} />
<YAxis type="category" dataKey="topic" tick={{ fill: C.text, fontSize: 9 }} width={80} />
<Tooltip contentStyle={{ background: C.card, border:
1px solid ${C.border}, color: C.text, fontSize: 11 }} /><Card style={{ overflowX: "auto" }}>
<table style={{ width: "100%", borderCollapse: "collapse" }}>
{["#", "Date(s)", "Thème", "Formateur", "Durée", "Participants", "Vol. Horaire"].map(h => {h})}
{DATA.trainings.map((t, i) => (
<tr key={i} style={{ background: i % 2 === 0 ? C.panel : C.card }}>
<span style={{ color: C.muted }}>{t.id}
{t.dates}
<b style={{ color: C.accent }}>{t.topic}
{t.trainer}
{t.duration}
{t.participants}
<b style={{ color: C.ok }}>{t.hours} h
))}
);
}
// ── EQUIPMENT ─────────────────────────────────────────────────────────────
function EquipmentView() {
return (
<div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 10, marginBottom: 16 }}>
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
{DATA.equipment.map((eq, i) => {
const expired = eq.expTrain.includes("2026");
return (
<Card key={i} style={{ borderLeft:
3px solid ${expired ? C.warn : C.ok}}}><div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 12 }}>
<div style={{ fontWeight: 800, fontSize: 15, color: C.white }}>{eq.type}
<div style={{ fontSize: 11, color: C.muted }}>{eq.co} · N° Rapport : {eq.report}
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", justifyContent: "flex-end" }}>
Good
{expired && ⚠ Exp. 2026}
{eq.obs}
<div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8 }}>
{[["Série / Plaque", eq.serial], ["Opérateur", eq.operator], ["CIN", eq.cin], ["Exp. Formation", eq.expTrain]].map(([l, v]) => (
<div key={l} style={{ background: C.panel, borderRadius: 6, padding: "8px 10px" }}>
<div style={{ fontSize: 9, color: C.muted, textTransform: "uppercase", marginBottom: 2 }}>{l}
<div style={{ fontSize: 12, fontWeight: 600 }}>{v}
))}
);
})}
);
}
// ── CHEMICAL ─────────────────────────────────────────────────────────────
function ChemicalView() {
const noSpill = DATA.chemicals.filter(c => !c.spill).length;
return (
<div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 10, marginBottom: 16 }}>
<Stat label="Zone Ventilée" value={DATA.chemicals.filter(c => c.ventilated).length} color={C.ok} />
<div style={{ background: C.danger + "15", border:
1px solid ${C.danger}44, borderRadius: 8, padding: "10px 14px", marginBottom: 14, fontSize: 12, color: C.danger }}><Card style={{ overflowX: "auto" }}>
<table style={{ width: "100%", borderCollapse: "collapse" }}>
{["Produit", "Utilisation", "Stockage", "MSDS", "Bac de rétention", "Extincteur", "Personnel formé", "Spill Kit", "Ventilé"].map(h => {h})}
{DATA.chemicals.map((ch, i) => (
<tr key={i} style={{ background: i % 2 === 0 ? C.panel : C.card }}>
<b style={{ color: C.accent }}>{ch.name}
<span style={{ fontSize: 11, color: C.muted }}>{ch.use}
<span style={{ fontSize: 11 }}>{ch.storage}
))}
);
}
// ── INSPECTION ────────────────────────────────────────────────────────────
function InspectionView() {
const [sel, setSel] = useState(DATA.inspections[0]);
const months = ["Jan", "Fév", "Mar", "Avr", "Mai", "Jun", "Jul", "Aoû", "Sep", "Oct", "Nov", "Déc"];
const [checks, setChecks] = useState({});
const toggle = (m, c) => { const key =
${sel.id}:${m}:${c}; setChecks(p => ({ ...p, [key]: !p[key] })); };const filled = Object.values(checks).filter(Boolean).length;
return (
<div style={{ display: "flex", gap: 8, marginBottom: 16, flexWrap: "wrap" }}>
{DATA.inspections.map(ins => (
<button key={ins.id} onClick={() => setSel(ins)} style={{ background: sel.id === ins.id ? C.accent : C.card, border:
1px solid ${sel.id === ins.id ? C.accent : C.border}, color: sel.id === ins.id ? C.white : C.text, borderRadius: 8, padding: "8px 14px", cursor: "pointer", fontSize: 13, fontWeight: 600, display: "flex", alignItems: "center", gap: 6 }}>{ins.emoji} {ins.outil}
))}
<div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 10, marginBottom: 14 }}>
<Card title={
${sel.emoji} Fiche d'Inspection — ${sel.outil}} icon="🔍" style={{ overflowX: "auto" }}><div style={{ display: "flex", gap: 8, marginBottom: 12, flexWrap: "wrap" }}>
Réf : {sel.ref}
HOTEL BRISTOL TANGER
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
<TH style={{ minWidth: 220 }}>Élément à contrôler
{months.map(m => <TH key={m} style={{ textAlign: "center", minWidth: 38, padding: "6px 4px" }}>{m})}
{sel.checks.map((chk, ci) => (
<tr key={ci} style={{ background: ci % 2 === 0 ? C.panel : C.card }}>
<td style={{ padding: "7px 10px", color: C.text, borderBottom:
1px solid ${C.border}}}>{chk}{months.map(m => {
const key =
${sel.id}:${m}:${chk};const v = checks[key];
return (
<td key={m} style={{ textAlign: "center", borderBottom:
1px solid ${C.border}, padding: 3 }}><button onClick={() => toggle(m, chk)} style={{ width: 26, height: 26, borderRadius: 4, border:
1px solid ${v ? C.ok : C.border}, background: v ? C.ok + "22" : "transparent", cursor: "pointer", color: v ? C.ok : C.muted, fontSize: 13, lineHeight: 1 }}>{v ? "✓" : ""});
})}
))}
<div style={{ marginTop: 10, display: "flex", gap: 20, fontSize: 11, color: C.muted }}>
Date d'inspection : ___________
Initiales de l'inspecteur : ___________
);
}
// ── HSE VISITS ────────────────────────────────────────────────────────────
function HSEVisitView() {
const [sel, setSel] = useState(0);
const visit = DATA.hseVisits[sel];
return (
<div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
{DATA.hseVisits.map((v, i) => (
<button key={i} onClick={() => setSel(i)} style={{ background: sel === i ? C.accent : C.card, border:
1px solid ${sel === i ? C.accent : C.border}, color: sel === i ? C.white : C.text, borderRadius: 8, padding: "7px 14px", cursor: "pointer", fontSize: 12, fontWeight: sel === i ? 700 : 400 }}>Visite {i + 1} · {v.date}))}
<div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 10, marginBottom: 14 }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14, marginBottom: 14 }}>
{visit.observations.map((obs, i) => (
<div key={i} style={{ display: "flex", gap: 8, padding: "7px 0", borderBottom: i < visit.observations.length - 1 ?
1px solid ${C.border}: "none" }}><span style={{ color: C.accent, fontSize: 10, marginTop: 5 }}>◆
<span style={{ fontSize: 13, color: C.text }}>{obs}
))}
<div style={{ background: C.ok + "15", border:
1px solid ${C.ok}44, borderRadius: 8, padding: "12px 14px", fontSize: 13, color: C.text, lineHeight: 1.7 }}>{visit.conclGeneral}
<div style={{ marginTop: 12, display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 8 }}>
{[["Main d'Œuvre", C.ok], ["Matériel", C.ok], ["Milieu", C.warn]].map(([l, c]) => (
<div key={l} style={{ background: c + "15", border:
1px solid ${c}44, borderRadius: 6, padding: "8px", textAlign: "center", fontSize: 11, color: c, fontWeight: 700 }}><div style={{ fontSize: 16 }}>{c === C.ok ? "✓" : "⚠"}{l}
))}
<div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 8 }}>
{[
{ title: "1. Main d'Œuvre", items: ["Aptitude (médicale, qualification)", "Formation (Accueil HSE, Accueil au poste)", "Équipement (EPI)", "Comportement", "Sécouristes (nombre, validité)"] },
{ title: "2. Matériel", items: ["Outil – Machine", "État du matériel", "Conformité (vérification réglementaire)", "Matériel adapté aux travaux", "Moyens de lutte contre incendie"] },
{ title: "3. Matériaux Dangereux", items: ["Conditions de stockage", "Fiche de données sécurité", "Prise en compte des risques / FDS"] },
{ title: "4. Méthodes de Travail", items: ["Organisation (plans méthodes)", "Mode opératoire (Prétask)", "PréStart", "EPC (blindage, protection trémies)", "Ergonomie (position de travail)"] },
{ title: "5. Milieu", items: ["Ordre, propreté, stockage, 1er secours", "Accès", "Coactivité", "Éclairage", "Ambiance (bruit, météo, poussière)"] },
{ title: "Légende", items: ["S = Satisfaisant", "NS = Non Satisfaisant", "SO = Sans Objet", "NE = Non Examiné", "SD = Situation Dangereuse", "BP = Bonne Pratique"] },
].map((section, si) => (
<div key={si} style={{ background: C.panel, border:
1px solid ${C.border}, borderRadius: 8, padding: "10px 12px" }}><div style={{ fontSize: 11, fontWeight: 700, color: C.accentSoft, marginBottom: 6 }}>{section.title}
{section.items.map((item, ii) => (
<div key={ii} style={{ fontSize: 11, color: C.muted, padding: "2px 0", display: "flex", gap: 5 }}>
<span style={{ color: C.border }}>·{item}
))}
))}
);
}
// ── Shared helpers ─────────────────────────────────────────────────────────
function SectionHeader({ title, sub }) {
return (
<div style={{ marginBottom: 18 }}>
<div style={{ fontSize: 20, fontWeight: 800, color: C.white }}>{title}
<div style={{ fontSize: 12, color: C.muted }}>{sub}
);
}
function InfoBox({ label, value }) {
return (
<div style={{ background: C.panel, border:
1px solid ${C.border}, borderRadius: 7, padding: "9px 12px" }}><div style={{ fontSize: 9, color: C.muted, textTransform: "uppercase", letterSpacing: 0.5, marginBottom: 2 }}>{label}
<div style={{ fontSize: 12, fontWeight: 600, color: C.text }}>{value}
);
}
Beta Was this translation helpful? Give feedback.
All reactions