""" ui_components.py — HTML card builders for each legal source type + tab panel. Doctrine.fr-inspired design: clean white cards, subtle borders, navy accents. """ # ── Shared card wrapper ── _CARD_STYLE = ( "border:1px solid #e2e8f0;border-radius:10px;padding:16px 20px;" "margin-bottom:12px;background:#ffffff;" "box-shadow:0 1px 3px rgba(0,0,0,0.04);" "transition:box-shadow 0.15s,border-color 0.15s" ) # ── Badge colors per source ── _BADGE = { "article": ("background:#dbeafe;color:#1e40af", "Code"), "decision": ("background:#fce7f3;color:#9d174d", "Jurisprudence"), "circulaire": ("background:#d1fae5;color:#065f46", "Circulaire"), "reponse": ("background:#fef3c7;color:#92400e", "Q&R"), } def _badge(style: str, text: str) -> str: return ( f'{text}' ) def _meta_line(*parts: str) -> str: items = [p for p in parts if p] return ( '
' + " · ".join(items) + '
' ) def build_article_card(result: dict, related_decisions: list[dict] | None = None) -> str: code = result.get("code_name", "Code") num = result.get("num", result.get("id_legifrance", "")) snippet = (result.get("chunk_text") or "")[:250] lf_id = result.get("id_legifrance", "") url = f"https://www.legifrance.gouv.fr/codes/article_lc/{lf_id}" if lf_id else "#" etat = result.get("article_etat", "") etat_html = "" if etat: color = "#059669" if etat == "VIGUEUR" else "#94a3b8" etat_html = f'{etat}' cross_ref_html = "" if related_decisions: mini_cards = "" for dec in related_decisions: dec_url = dec.get("url_judilibre", "#") dec_date = dec.get("date_decision", "") dec_jur = dec.get("jurisdiction", "") dec_snip = dec.get("chunk_text", "")[:120] mini_cards += f"""
{dec_jur} · {dec_date}
{dec_snip}…
Voir la decision →
""" n = len(related_decisions) cross_ref_html = f"""
{n} decision{"s" if n > 1 else ""} liee{"s" if n > 1 else ""} {mini_cards}
""" return f"""
{_badge(*_BADGE["article"][:1], code)} Art. {num} {etat_html}

{snippet}…

{_meta_line(f'Legifrance →')} {cross_ref_html}
""" def build_decision_card(result: dict) -> str: juris = result.get("jurisdiction", "") chamber = result.get("chamber", "") date = result.get("date_decision", "") fiche = result.get("fiche_arret") or "" snippet = fiche[:250] if fiche else (result.get("chunk_text") or "")[:250] url = result.get("url_judilibre", "#") src_id = result.get("source_id", "") label = juris + (f" · {chamber}" if chamber else "") return f"""
{_badge(_BADGE["decision"][0], label)} {date} {f'n° {src_id}' if src_id else ""}

{snippet}…

{_meta_line(f'Cour de cassation →')}
""" def build_circulaire_card(result: dict) -> str: ministere = result.get("ministere", "") numero = result.get("numero", result.get("source_id", "")) objet = (result.get("objet") or result.get("chunk_text") or "")[:250] date = (result.get("date_parution") or "")[:10] url = result.get("url_legifrance", "#") return f"""
{_badge(_BADGE["circulaire"][0], f"Min. {ministere}")} Circ. n° {numero}

{objet}…

{_meta_line(date, f'Legifrance →')}
""" def build_reponse_card(result: dict) -> str: ministere = result.get("ministere", "") num_q = result.get("numero_question", result.get("source_id", "")) question = (result.get("question_text") or result.get("chunk_text") or "")[:250] date = (result.get("date_reponse") or "")[:10] url = result.get("url_legifrance", "#") return f"""
{_badge(_BADGE["reponse"][0], ministere)} Q. n° {num_q}

{question}…

{_meta_line(date, f'Legifrance →')}
""" def build_tabs_html(results_dict: dict, loading_status: dict) -> str: tabs_config = [ ("articles", "Articles", build_article_card), ("jurisprudence", "Jurisprudence", build_decision_card), ("circulaires", "Circulaires", build_circulaire_card), ("reponses", "Q&R", build_reponse_card), ] tab_buttons = "" tab_panels = "" for i, (key, label, builder) in enumerate(tabs_config): results = results_dict.get(key, []) count = len(results) # Active tab styling if i == 0: bg, color, weight, border = "#e8f0fe", "#1e3a5f", "600", "2px solid #1e3a5f" else: bg, color, weight, border = "transparent", "#64748b", "400", "2px solid transparent" tab_buttons += f""" """ if not loading_status.get(key, False): content = '

Source temporairement indisponible

' elif not results: content = '

Aucun resultat pour cette source.

' else: content = "".join(builder(r) for r in results) display = "block" if i == 0 else "none" tab_panels += f"""
{content}
""" js = """ """ return f"""
{tab_buttons}
{tab_panels} {js}
"""