// Medway MSS — Catalogue page
// Consumes window.MEDWAY_CATALOGUE from public/catalogue/data.js
const ACCENT = '#1ea34d';
// ── Subcategory illustrations (Halo-style geometric specimens) ────────────────
function SubcatIllustration({ sub, accent = ACCENT }) {
const ink = '#171717';
const sw = 1.75;
const wrap = (children) => (
);
switch (sub) {
case 'biochem':
return wrap(<>
>);
case 'hematology':
return wrap(<>
>);
case 'control_calib':
return wrap(<>
>);
case 'hplc_cleaning':
return wrap(<>
>);
case 'gel_cards':
return wrap(<>
{[0,1,2,3,4,5,6,7].map(i => (
))}
>);
case 'rbc':
return wrap(<>
{[60, 110, 160].map((cx,i) => (
))}
>);
case 'diluents':
return wrap(<>
>);
case 'equipment':
return wrap(<>
>);
default: // x-ray subcategories use photos, but keep a default
return wrap(<>
>);
}
}
// ── Product card ──────────────────────────────────────────────────────────────
function ProductCard({ p, accent = ACCENT }) {
const hasPhoto = !!p.image;
return (
{ e.currentTarget.style.borderColor = 'var(--fg-1)'; }}
onMouseLeave={(e) => { e.currentTarget.style.borderColor = 'var(--border)'; }}
>
{hasPhoto ? (
) : (
)}
{p.name}
{p.method && (
{p.method}
)}
{p.refs && p.refs.length > 0 && (
{p.refs.slice(0, 6).map((r) => (
{r}
))}
{p.refs.length > 6 && (
+{p.refs.length - 6}
)}
)}
);
}
// ── Subcategory section ───────────────────────────────────────────────────────
function SubcategorySection({ sub, products, t, lang, meta, accent = ACCENT }) {
const isMobile = useIsMobile();
const label = meta.subcategories[sub]?.[lang] || meta.subcategories[sub]?.fr || sub;
return (
{label}
{products.length} {t('cat.items')}
{products.map((p, i) =>
)}
);
}
// ── Category section ──────────────────────────────────────────────────────────
function CategorySection({ cat, products, t, lang, meta, n }) {
const isMobile = useIsMobile();
const label = meta.labels[cat]?.[lang] || meta.labels[cat]?.fr || cat;
// Preserve subcategory order from data (first-occurrence)
const subOrder = [];
for (const p of products) if (!subOrder.includes(p.sub)) subOrder.push(p.sub);
return (
§ 0{n}
{products.length} {t('cat.items')}
{label}
{subOrder.map((sub) => {
const list = products.filter((p) => p.sub === sub);
if (!list.length) return null;
return (
);
})}
);
}
// ── Top bar (sticky) ──────────────────────────────────────────────────────────
function CatalogueTopBar({ t, lang, setLang, query, setQuery, scrollToCat }) {
const [scrolled, setScrolled] = React.useState(false);
const isMobile = useIsMobile();
React.useEffect(() => {
const on = () => setScrolled(window.scrollY > 8);
window.addEventListener('scroll', on);
return () => window.removeEventListener('scroll', on);
}, []);
return (
);
}
// ── Hero ──────────────────────────────────────────────────────────────────────
function CatalogueHero({ t, lang, meta, productsByCat, scrollToCat }) {
const isMobile = useIsMobile();
const cats = Object.keys(productsByCat);
return (
Medway MSS · Lomé, Togo
{t('cat.title')}
{t('cat.subtitle')}
{cats.map((cat) => {
const list = productsByCat[cat] || [];
const label = meta.labels[cat]?.[lang] || meta.labels[cat]?.fr || cat;
return (
);
})}
);
}
// ── App ───────────────────────────────────────────────────────────────────────
function CategoryButton({ cat, label, count, onClick }) {
return (
);
}
function App() {
const { lang, setLang, t } = useI18n();
const isMobile = useIsMobile();
const [query, setQuery] = React.useState('');
React.useEffect(() => { document.documentElement.lang = lang; }, [lang]);
React.useEffect(() => { if (window.lucide) window.lucide.createIcons(); });
const data = window.MEDWAY_CATALOGUE || { _meta: { labels: {}, subcategories: {} }, products: [] };
const meta = data._meta;
const all = data.products;
// Apply name overrides per language where present
const translated = all.map((p) => {
if (lang === 'en' && p.name_en) return { ...p, name: p.name_en };
return p;
});
const filtered = React.useMemo(() => {
const q = query.trim().toLowerCase();
if (!q) return translated;
return translated.filter((p) => {
if (p.name.toLowerCase().includes(q)) return true;
if (p.method && p.method.toLowerCase().includes(q)) return true;
if (p.refs && p.refs.some((r) => r.toLowerCase().includes(q))) return true;
const subLabel = meta.subcategories[p.sub]?.[lang] || '';
if (subLabel.toLowerCase().includes(q)) return true;
return false;
});
}, [query, translated, lang]);
// Group by category in fixed order
const CAT_ORDER = ['reaktif', 'kan_gruplama', 'radyoloji'];
const byCat = {};
for (const c of CAT_ORDER) byCat[c] = filtered.filter((p) => p.category === c);
const scrollToCat = (cat) => {
const el = document.getElementById(`cat-${cat}`);
if (el) {
const top = el.getBoundingClientRect().top + window.scrollY - 68;
window.scrollTo({ top, behavior: 'smooth' });
}
};
return (
{filtered.length === 0 ? (
{t('cat.no_results')}
) : (
CAT_ORDER.map((cat, i) => {
const list = byCat[cat];
if (!list || list.length === 0) return null;
return (
);
})
)}
Medway MSS
{t('cat.note')}
{isMobile && (
)}
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render();