SPB Git forge

spb/fabri-ka

Public

Agrégateur de produits québécois — www.fabri-ka.com

217commits 1branches 0releases
66.1 MBsize
maindefault branch
5 h agolast push
Python 38.4% HTML 30.3% TypeScript 17.2% CSS 11% JavaScript 3.2%
6.7 KB · 102 lines tsx
Raw Blame History
1// ---------------------------------------------------------------------------2// Fabri-Ka — Agrégateur de produits fabriqués au Québec3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// fiche/ContextCards.tsx : « La boutique » (logo, lieu, origine, liens),5//   « Aussi chez la boutique » (cartes produit), « Dossier de la fiche »,6//   « Sources et méthodologie ».7// ---------------------------------------------------------------------------8import { Link } from 'react-router-dom'9import { CATEGORY_LABELS, ProductDetail, categorySlug, formatRelativeDate, hostnameOf } from '../api'10import { Ico } from '../components/KaIcons'11import OriginBadge from '../components/OriginBadge'12import ProductCard from '../components/ProductCard'13import StoreLogo from '../components/StoreLogo'14import { Accordion, SectionCard } from './ui'15import { origineLabel } from './synthese'1617export function StoreCard({ p }: { p: ProductDetail }) {18  return (19    <SectionCard id="boutique" title="La boutique" icon={<Ico name="store" size={18} />}>20      <div className="fb-store">21        <StoreLogo storeId={p.store_id} name={p.store_name} size="lg" />22        <div style={{ minWidth: 0 }}>23          <Link to={`/boutiques/${encodeURIComponent(p.store_id)}`} className="fb-store-name">{p.store_name}</Link>24          <div className="fb-store-loc">{[p.store_city, p.store_region].filter(Boolean).join(', ') || 'Québec'}</div>25          <div className="fb-price-row" style={{ marginTop: 8 }}>26            <span className="fb-capsule fb-capsule-origin"><OriginBadge origin={p.origin_class} withLabel /></span>27          </div>28        </div>29      </div>30      <div className="fb-actions" style={{ marginTop: 12 }}>31        <Link className="fb-btn fb-btn-ghost" style={{ flex: 1 }} to={`/boutiques/${encodeURIComponent(p.store_id)}`}>Tous ses produits</Link>32        {p.store_url && <a className="fb-btn fb-btn-ghost" style={{ flex: 1 }} href={p.store_url} target="_blank" rel="noopener noreferrer">{hostnameOf(p.store_url)} <Ico name="external" size={15} /></a>}33      </div>34      <p className="fb-fine">{origineLabel(p)} — classe d'origine attribuée par Fabri-Ka à la boutique d'après ses pages « à propos », ses certifications et ses mentions de fabrication ; elle s'applique à l'ensemble de la boutique, pas à ce produit en particulier.</p>35    </SectionCard>36  )37}3839export function RelatedCard({ p }: { p: ProductDetail }) {40  if (!p.related || p.related.length === 0) return null41  return (42    <SectionCard id="aussi" title={`Aussi chez ${p.store_name}`} icon={<Ico name="layers" size={18} />}>43      <div className="fb-related">{p.related.slice(0, 6).map((x) => <ProductCard key={x.uid} product={x} />)}</div>44      {p.category && (45        <p className="fb-fine" style={{ marginTop: 12 }}>46          <Link to={`/categorie/${categorySlug(p.category)}`}>Voir toute la catégorie {CATEGORY_LABELS[p.category] ?? p.category} →</Link>47        </p>48      )}49    </SectionCard>50  )51}5253export function Dossier({ p }: { p: ProductDetail }) {54  const det = p.details ?? {}55  return (56    <SectionCard id="dossier" title="Dossier de la fiche" icon={<Ico name="folder" size={18} />} sub="Ce que Fabri-Ka observe réellement : boutique, suivi, identifiants">57      <Accordion title={<><Ico name="history" size={15} className="fb-acc-ico" />Suivi Fabri-Ka</>} meta={p.listing_status === 'published' ? 'publié' : p.listing_status || undefined}>58        <div className="fb-facts-rows" style={{ marginTop: 0 }}>59          {det.created_at && <div className="fb-kv"><span className="k">Créé par la boutique</span><span className="v">{new Date(det.created_at).toLocaleDateString('fr-CA')}</span></div>}60          {det.updated_at && <div className="fb-kv"><span className="k">Modifié par la boutique</span><span className="v">{formatRelativeDate(det.updated_at) ?? det.updated_at}</span></div>}61          <div className="fb-kv"><span className="k">Statut</span><span className="v">{p.available ? 'Disponible' : 'Non disponible'}{p.listing_status && p.listing_status !== 'published' ? ` · ${p.listing_status}` : ''}</span></div>62        </div>63        <p className="fb-fine">Synchronisé plusieurs fois par jour depuis la boutique ; les dates ci-dessus sont celles publiées par la plateforme marchande.</p>64      </Accordion>65      <Accordion title={<><Ico name="tag" size={15} className="fb-acc-ico" />Identifiants</>} meta={det.sku || p.uid.slice(0, 8)}>66        <div className="fb-facts-rows" style={{ marginTop: 0 }}>67          {det.sku && <div className="fb-kv"><span className="k">SKU</span><span className="v">{det.sku}</span></div>}68          {det.gtin && <div className="fb-kv"><span className="k">GTIN / MPN</span><span className="v">{det.gtin}</span></div>}69          <div className="fb-kv"><span className="k">Identifiant Fabri-Ka</span><span className="v">{p.uid}</span></div>70          <div className="fb-kv"><span className="k">Fiche originale</span><span className="v"><a href={p.url} target="_blank" rel="noopener noreferrer">{hostnameOf(p.url)} ↗</a></span></div>71        </div>72      </Accordion>73    </SectionCard>74  )75}7677export function Sources({ p, onShare }: { p: ProductDetail; onShare: () => void }) {78  const srcs = [79    { n: p.store_name, r: 'Titre, prix, images, options, description et disponibilité (source originale)', href: p.url },80    { n: 'Fabri-Ka', r: 'Classe d\'origine de la boutique, catégorie canonique, déduplication — calculs maison, indicatifs' },81  ]82  return (83    <SectionCard id="sources" title="Sources et méthodologie" icon={<Ico name="folder" size={18} />} sub="Chaque donnée de cette fiche renvoie à sa source ; les calculs Fabri-Ka sont indicatifs et documentés">84      <ul className="fb-sources">85        {srcs.map((s) => (86          <li key={s.n}>87            <span className="n">{s.href ? <a href={s.href} target="_blank" rel="noopener noreferrer">{s.n}</a> : s.n}</span>88            <span className="r">{s.r}</span>89          </li>90        ))}91      </ul>92      <div className="fb-end" style={{ marginTop: 14 }}>93        <a href="/boutiques"><Ico name="store" size={18} />Boutiques d'ici<small>Toutes les boutiques Fabri-Ka</small></a>94        <a href="/contact"><Ico name="alert" size={18} />Signaler une erreur<small>Prix, image, origine…</small></a>95        <button type="button" onClick={onShare}><Ico name="share" size={18} />Partager ce produit<small>Lien de la fiche</small></button>96        <a href="/a-propos"><Ico name="info" size={18} />Classes d'origine<small>A · B · C · D · E expliquées</small></a>97      </div>98      <p className="fb-fine" style={{ marginTop: 12 }}>Les prix et disponibilités sont ceux affichés par la boutique — chaque fiche renvoie à la page produit originale. Fabri-Ka est un agrégateur indépendant.</p>99    </SectionCard>100  )101}102