spb/chat-spboucher-ai
Public
1"use client";2import { useState } from "react";3import Link from "next/link";4import { Pencil, Trash2, Check, X, MessageSquare, Sparkles, Hand, Upload } from "lucide-react";5import type { MemoryDTO } from "@/lib/client/types";6import { api, relTime } from "@/lib/client/fetcher";7import { Badge, Toggle, cn, toast } from "../ui";8import { CATEGORIES, categoryLabel } from "./memory-shared";910export function MemoryCard({ m, onChanged, candidate }: { m: MemoryDTO; onChanged: () => void; candidate?: boolean }) {11 const [editing, setEditing] = useState(false); const [draft, setDraft] = useState(m.content); const [cat, setCat] = useState(m.category); const [busy, setBusy] = useState(false);12 async function patch(p: Record<string, unknown>, msg?: string) { setBusy(true); try { await api(`/api/memories/${m.id}`, { method: "PATCH", json: p }); if (msg) toast(msg, "ok"); onChanged(); } catch (e) { toast((e as Error).message, "error"); } finally { setBusy(false); } }13 async function del() { if (!candidate && !confirm("Supprimer ce souvenir ?")) return; setBusy(true); try { await api(`/api/memories/${m.id}`, { method: "DELETE" }); toast(candidate ? "Candidat rejeté" : "Souvenir supprimé", "ok"); onChanged(); } finally { setBusy(false); } }14 const SourceIcon = m.source === "manual" ? Hand : m.source === "imported" ? Upload : Sparkles;15 return (16 <div className={cn("rounded-xl border bg-surface p-3.5 transition-colors", candidate ? "border-warn/40" : m.status === "disabled" ? "border-border opacity-60" : "border-border", busy && "opacity-60")}>17 <div className="mb-2 flex flex-wrap items-center gap-2 text-[11px] text-muted">18 <Badge tone={candidate ? "warn" : "amber"}>{categoryLabel(m.category)}</Badge>19 <span className="inline-flex items-center gap-1" title={`Source : ${m.source}`}><SourceIcon className="h-3 w-3" />{m.source === "manual" ? "manuel" : m.source === "imported" ? "importé" : "extrait"}</span>20 {m.source_conversation_id && <Link href={`/c/${m.source_conversation_id}`} className="inline-flex items-center gap-1 hover:text-accent"><MessageSquare className="h-3 w-3" />{m.source_title ?? "Source"}</Link>}21 {m.use_count > 0 && <span>utilisé {m.use_count}×</span>}22 <span className="ml-auto">{relTime(m.updated_at)}</span>23 </div>24 {editing ? (25 <div className="space-y-2">26 <textarea autoFocus value={draft} onChange={e => setDraft(e.target.value)} rows={3} className="input text-[14px]" />27 <div className="flex flex-wrap items-center gap-2">28 <select value={cat} onChange={e => setCat(e.target.value)} className="input !w-auto !py-1 text-[13px]">{CATEGORIES.map(c => <option key={c.id} value={c.id}>{c.label}</option>)}</select>29 <span className="flex-1" />30 <button onClick={() => { setEditing(false); setDraft(m.content); setCat(m.category); }} className="inline-flex items-center gap-1 rounded-lg px-2.5 py-1.5 text-[13px] text-fg-2 hover:bg-surface-2"><X className="h-3.5 w-3.5" /> Annuler</button>31 <button onClick={async () => { await patch({ content: draft.trim(), category: cat }, "Souvenir modifié"); setEditing(false); }} disabled={draft.trim().length < 3} className="inline-flex items-center gap-1 rounded-lg bg-accent px-2.5 py-1.5 text-[13px] font-medium text-accent-ink disabled:opacity-40"><Check className="h-3.5 w-3.5" /> Enregistrer</button>32 </div>33 </div>34 ) : <p className="text-[14px] leading-relaxed">{m.content}</p>}35 {!editing && (36 <div className="mt-2.5 flex items-center gap-1 text-[12px]">37 {candidate ? (<>38 <button onClick={() => patch({ status: "active" }, "Souvenir approuvé")} className="inline-flex items-center gap-1 rounded-lg bg-ok/15 px-2.5 py-1 font-medium text-ok hover:bg-ok/25"><Check className="h-3.5 w-3.5" /> Approuver</button>39 <button onClick={del} className="inline-flex items-center gap-1 rounded-lg px-2.5 py-1 text-fg-2 hover:bg-surface-2 hover:text-danger"><X className="h-3.5 w-3.5" /> Rejeter</button>40 <button onClick={() => setEditing(true)} className="inline-flex items-center gap-1 rounded-lg px-2.5 py-1 text-fg-2 hover:bg-surface-2"><Pencil className="h-3.5 w-3.5" /> Modifier</button>41 </>) : (<>42 <label className="inline-flex items-center gap-2 text-fg-2"><Toggle checked={m.status === "active"} onChange={v => patch({ status: v ? "active" : "disabled" })} label="Actif" />{m.status === "active" ? "actif" : "désactivé"}</label>43 <span className="flex-1" />44 <button onClick={() => setEditing(true)} className="inline-flex items-center gap-1 rounded-lg px-2 py-1 text-fg-2 hover:bg-surface-2 hover:text-fg"><Pencil className="h-3.5 w-3.5" /> Modifier</button>45 <button onClick={del} className="inline-flex items-center gap-1 rounded-lg px-2 py-1 text-fg-2 hover:bg-surface-2 hover:text-danger"><Trash2 className="h-3.5 w-3.5" /> Supprimer</button>46 </>)}47 </div>48 )}49 </div>50 );51}52