- Full grant strategy framework for renewable energy & green hydrogen - AI-powered grant studio, partner outreach, financial modeling - Umami analytics with data-performance tracking - Live Degelas metrics connected to solar.degelas.be - Trilingual (EN/FR/AR) with i18n support - Dockerized with Nginx frontend + Express API proxy
205 lines
10 KiB
TypeScript
205 lines
10 KiB
TypeScript
import { useState } from "react";
|
||
import { deploymentStages } from "../data/deployment";
|
||
import { deploymentStagesDarija } from "../data/deployment.darija";
|
||
import { deploymentStagesFr } from "../data/deployment.fr";
|
||
import { Section } from "./Section";
|
||
import { cn } from "../utils/cn";
|
||
|
||
function CriticalityBadge({ level, suffix }: { level: "CRITICAL" | "HIGH" | "MEDIUM"; suffix: string }) {
|
||
const styles = {
|
||
CRITICAL: "bg-rose-100 text-rose-700 border-rose-300",
|
||
HIGH: "bg-amber-100 text-amber-700 border-amber-300",
|
||
MEDIUM: "bg-teal-100 text-teal-700 border-teal-300",
|
||
};
|
||
return (
|
||
<span className={cn("rounded-full border px-3 py-1 text-xs font-semibold", styles[level])}>
|
||
{level} {suffix}
|
||
</span>
|
||
);
|
||
}
|
||
|
||
import { useI18n } from "../i18n";
|
||
|
||
export function DeploymentPlaybook() {
|
||
const { t, locale } = useI18n();
|
||
const [expanded, setExpanded] = useState<string>("0");
|
||
|
||
const currentStages = locale === "darija" ? deploymentStagesDarija : locale === "fr" ? deploymentStagesFr : deploymentStages;
|
||
|
||
return (
|
||
<Section
|
||
id="deployment"
|
||
eyebrow={t.deployment.eyebrow}
|
||
title={t.deployment.title}
|
||
intro={t.deployment.intro}
|
||
>
|
||
<div className="space-y-4">
|
||
{currentStages.map((stage) => {
|
||
const isOpen = expanded === stage.phase;
|
||
return (
|
||
<div
|
||
key={stage.phase}
|
||
className={cn(
|
||
"overflow-hidden rounded-3xl border transition-all",
|
||
isOpen ? "border-emerald-300 bg-white shadow-xl shadow-emerald-500/10" : "border-slate-200 bg-white"
|
||
)}
|
||
>
|
||
<button
|
||
onClick={() => setExpanded(isOpen ? "" : stage.phase)}
|
||
className="w-full px-6 py-6 text-left sm:px-8"
|
||
>
|
||
<div className="flex items-start justify-between gap-4">
|
||
<div className="min-w-0 flex-1">
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-emerald-500 to-teal-600 text-sm font-bold text-white">
|
||
{stage.phase}
|
||
</span>
|
||
<div>
|
||
<h3 className="text-lg font-bold text-emerald-950 sm:text-xl">{stage.title}</h3>
|
||
<p className="text-xs text-slate-500">{stage.subtitle}</p>
|
||
</div>
|
||
</div>
|
||
<p className="mt-3 text-sm leading-relaxed text-slate-600">{stage.objective}</p>
|
||
</div>
|
||
<span
|
||
className={cn(
|
||
"flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-slate-200 text-slate-400 transition-transform",
|
||
isOpen && "rotate-45 border-emerald-400 text-emerald-500"
|
||
)}
|
||
>
|
||
<svg className="h-5 w-5" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" d="M12 5v14M5 12h14" />
|
||
</svg>
|
||
</span>
|
||
</div>
|
||
</button>
|
||
|
||
<div
|
||
className={cn(
|
||
"grid transition-all duration-300",
|
||
isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
|
||
)}
|
||
>
|
||
<div className="overflow-hidden">
|
||
<div className="border-t border-slate-100 px-6 pb-8 pt-6 sm:px-8 space-y-8">
|
||
{/* Key Metrics */}
|
||
<div className="grid gap-4 sm:grid-cols-2">
|
||
<div className="rounded-2xl border border-slate-100 bg-slate-50 p-5">
|
||
<div className="text-xs font-semibold text-slate-500 uppercase tracking-widest">{t.deployment.duration}</div>
|
||
<div className="mt-2 text-xl font-bold text-emerald-950">{stage.duration}</div>
|
||
</div>
|
||
<div className="rounded-2xl border border-slate-100 bg-slate-50 p-5">
|
||
<div className="text-xs font-semibold text-slate-500 uppercase tracking-widest">{t.deployment.priority}</div>
|
||
<div className="mt-2">
|
||
<CriticalityBadge level={stage.criticality} suffix={t.deployment.pathSuffix} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Actions */}
|
||
<div>
|
||
<h4 className="mb-4 text-sm font-bold uppercase tracking-widest text-slate-500">{t.deployment.actions}</h4>
|
||
<div className="space-y-4">
|
||
{stage.actions.map((action, idx) => (
|
||
<div key={idx} className="rounded-2xl border border-slate-100 bg-slate-50/50 p-5">
|
||
<div className="flex items-start justify-between gap-3">
|
||
<div className="min-w-0 flex-1">
|
||
<h5 className="font-semibold text-emerald-950">{action.title}</h5>
|
||
<p className="mt-2 text-sm text-slate-600">{action.description}</p>
|
||
<div className="mt-4 flex flex-wrap gap-3 text-xs">
|
||
<div className="flex items-center gap-1">
|
||
<span className="font-semibold text-slate-500">{t.deployment.owner}:</span>
|
||
<span className="text-slate-600">{action.owner}</span>
|
||
</div>
|
||
<div className="flex items-center gap-1">
|
||
<span className="font-semibold text-slate-500">{t.deployment.timeline}:</span>
|
||
<span className="text-slate-600">{action.timeline}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{action.resources.length > 0 && (
|
||
<div className="mt-4 pt-4 border-t border-slate-100">
|
||
<div className="text-xs font-semibold text-slate-500 uppercase tracking-widest mb-2">
|
||
{t.deployment.resources}
|
||
</div>
|
||
<div className="flex flex-wrap gap-2">
|
||
{action.resources.map((r) => (
|
||
<span key={r} className="rounded-full bg-white px-2.5 py-1 text-xs text-slate-600">
|
||
{r}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Checkpoints */}
|
||
<div>
|
||
<h4 className="mb-4 text-sm font-bold uppercase tracking-widest text-slate-500">{t.deployment.checkpoints}</h4>
|
||
<div className="space-y-3">
|
||
{stage.checkpoints.map((cp, idx) => (
|
||
<div key={idx} className="rounded-2xl border border-emerald-100 bg-emerald-50/40 p-5">
|
||
<h5 className="font-semibold text-emerald-950">{cp.name}</h5>
|
||
<p className="mt-1 text-sm text-emerald-900/70">{cp.description}</p>
|
||
<ul className="mt-3 space-y-1.5">
|
||
{cp.success.map((s) => (
|
||
<li key={s} className="flex items-center gap-2 text-xs text-emerald-800">
|
||
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
|
||
{s}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Success Metrics */}
|
||
<div>
|
||
<h4 className="mb-4 text-sm font-bold uppercase tracking-widest text-slate-500">
|
||
{t.deployment.metrics}
|
||
</h4>
|
||
<div className="grid gap-3 sm:grid-cols-2">
|
||
{stage.successMetrics.map((metric, idx) => (
|
||
<div
|
||
key={idx}
|
||
className="flex items-start gap-3 rounded-2xl border border-teal-100 bg-teal-50/40 p-4"
|
||
>
|
||
<span className="mt-1 h-2 w-2 shrink-0 rounded-full bg-teal-500" />
|
||
<span className="text-xs leading-relaxed text-teal-900">{metric}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Pitfalls */}
|
||
{stage.pitfalls.length > 0 && (
|
||
<div>
|
||
<h4 className="mb-4 text-sm font-bold uppercase tracking-widest text-slate-500">
|
||
⚠️ {t.deployment.pitfalls}
|
||
</h4>
|
||
<div className="space-y-2.5">
|
||
{stage.pitfalls.map((pitfall, idx) => (
|
||
<div key={idx} className="flex items-start gap-3 rounded-2xl border border-rose-100 bg-rose-50/40 p-4">
|
||
<span className="mt-0.5 shrink-0 text-lg">🚫</span>
|
||
<span className="text-sm text-rose-900">{pitfall}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</Section>
|
||
);
|
||
}
|