// Savings Recommendations page — Cai-curated, actionable function SavingsPage() { const [filter, setFilter] = useState('all'); // all | easy | high const [accepted, setAccepted] = useState({}); const [dismissed, setDismissed] = useState({}); const [expanded, setExpanded] = useState(null); const [busy, setBusy] = useState(false); const savings = D.savings || {}; const allItems = savings.items || []; const aiConfigured = D.aiStatus?.configured; const hasReport = allItems.length > 0; const items = useMemo(() => { let arr = allItems.filter(i => !dismissed[i.id]); if (filter === 'easy') arr = arr.filter(i => i.effort === 'easy'); if (filter === 'high') arr = arr.filter(i => i.impact === 'high'); if (filter === 'subs') arr = arr.filter(i => i.category === 'חשבונות ומנויים'); // sort by impact * confidence desc const score = (i) => (i.annual || 0) * (i.confidence || 0.5) * (i.effort === 'easy' ? 1.2 : i.effort === 'medium' ? 1 : 0.8); return arr.sort((a, b) => score(b) - score(a)); }, [filter, dismissed, hasReport]); const acceptedItems = allItems.filter(i => accepted[i.id]); const acceptedAnnual = acceptedItems.reduce((s, i) => s + (i.annual || 0), 0); const acceptedMonthly = acceptedItems.reduce((s, i) => s + (i.monthly || 0), 0); const totalAvailableAnnual = items.reduce((s, i) => s + (i.annual || 0), 0); const generate = async () => { if (!aiConfigured) { alert('AI לא מוגדר. הגדר ANTHROPIC_API_KEY ב-environment והפעל את השרת מחדש.'); return; } if (busy) return; if (hasReport && !confirm('הניתוח הקיים יוחלף בניתוח חדש (יקח כדקה). להמשיך?')) return; setBusy(true); try { const r = await apiFetch('/api/ai/savings', { method: 'POST' }); if (!r.ok) { const err = await r.json().catch(() => ({})); throw new Error(err.detail?.message || 'הניתוח נכשל'); } await window.APP_DATA_REFRESH(); } catch (err) { alert('שגיאה בניתוח: ' + err.message); } finally { setBusy(false); } }; const updatedLabel = savings.createdAt ? new Date(savings.createdAt).toLocaleString('he-IL', { dateStyle: 'short', timeStyle: 'short' }) : null; const modelLabel = savings.model || D.aiStatus?.model || 'Cai'; return (
{/* Header */}

איפה אפשר לחסוך

המלצות מבוססות נתונים · 12 חודשים אחרונים · {updatedLabel ? `עודכן ${updatedLabel}` : 'לא נוצר עדיין'} · {modelLabel}
{/* Hero — total potential */}
פוטנציאל חיסכון שנתי
{savings.summary}
{/* Accepted progress bar */} {acceptedItems.length > 0 && (
סימנת {acceptedItems.length} מתוך {allItems.length} המלצות לביצוע
חיסכון מצטבר: {fmtILS(acceptedMonthly)}/חודש · {fmtILS(acceptedAnnual)} בשנה
)} {/* Filter chips */}
setFilter('all')} /> setFilter('easy')} icon="⚡" /> setFilter('high')} icon="↑" /> setFilter('subs')} /> מציג {items.length} · סך פוטנציאל {fmtILS(totalAvailableAnnual)}/שנה
{/* Recommendations list */}
{items.map((it, i) => ( setExpanded(expanded === it.id ? null : it.id)} onAccept={() => setAccepted(a => ({ ...a, [it.id]: !a[it.id] }))} onDismiss={() => setDismissed(d => ({ ...d, [it.id]: true }))} /> ))} {items.length === 0 && ( )}
{/* Footer note */}
איך החישוב נעשה:{' '} ההמלצות מבוססות על ניתוח של 12 חודשים אחרונים, הצלבה עם תעריפי שוק, ודפוסי שימוש שזוהו אוטומטית. רמת הביטחון משקפת את איכות הנתונים — לא הבטחה. אתה תמיד שומר על שליטה: שום פעולה לא מתבצעת אוטומטית.
); } function SavingsHeroStat({ label, value, sub, accent }) { return (
{label}
{sub}
); } function FilterChip({ label, on, onClick, icon }) { return ( ); } function SavingItem({ item, rank, isAccepted, isExpanded, onToggle, onAccept, onDismiss }) { const cat = D.categories[item.category] || { color: 'var(--ink-soft)', icon: '·' }; const effortLabel = { easy: 'קל', medium: 'בינוני', hard: 'מורכב' }[item.effort]; const impactLabel = { low: 'נמוך', medium: 'בינוני', high: 'גבוה' }[item.impact]; const impactColor = { low: 'var(--ink-soft)', medium: 'var(--accent)', high: 'var(--positive)' }[item.impact]; return (
{/* Rank + category icon */}
#{rank}
{cat.icon}
{/* Body */}
{item.title} {isAccepted && }
{item.category} · {item.sub}
{/* Meta strip */}
{/* Annual savings */}
בשנה
{fmtILS(item.annual)}
{isExpanded ? '⌃ סגור' : '⌄ הרחב'}
{/* Expanded detail */} {isExpanded && (
{/* Why */}
למה זה אפשרי
{item.why}
{/* Evidence */} {item.evidence && (
ראיות
    {item.evidence.map((e, i) =>
  • {e}
  • )}
)} {/* Actions */}
)}
); } function Meta({ label, value, accent, dot }) { return (
{label} {dot && } {value}
); } function MetaDivider() { return ; } function ConfidenceBar({ value }) { const pct = Math.round(value * 100); return (
ביטחון
{pct}%
); } function Pill({ color, label }) { return ( {label} ); } window.SavingsPage = SavingsPage;