// ════════════════════════════════════════════════════════════════ // /admin/cost — расходы API // super_admin видит полную детализацию, admin — только итоги // ════════════════════════════════════════════════════════════════ function CostBreakdown({ currentUser }) { const A = window.SDH_ADMIN; const isSuperAdmin = currentUser.role === 'super_admin'; const usdRate = 92; // RUB per USD (mock) const totalUsd = 4.23; const budgetUsd = A.settings.budget_monthly_usd; const pctUsed = totalUsd / budgetUsd; // Per service const services = isSuperAdmin ? [ { id: 'glm', label: 'LLM провайдер A', share: 0.80, calls: 11304, usd: totalUsd * 0.80, hue: 'purple' }, { id: 'gemini', label: 'LLM провайдер B', share: 0.15, calls: 1872, usd: totalUsd * 0.15, hue: 'amber' }, { id: 'voyage', label: 'эмбеддинги', share: 0.05, calls: 5022, usd: totalUsd * 0.05, hue: 'info' }, ] : null; // Per user const perUser = isSuperAdmin ? [ { user: 'orlov', role: 'super_admin', usd: 1.23, share: 0.29 }, { user: 'mcp-agent', role: 'agent', usd: 2.45, share: 0.58 }, { user: 'lida', role: 'collaborator', usd: 0.55, share: 0.13 }, ] : null; // 30-day series const daily = React.useMemo(() => { const out = []; let seed = 731; for (let i = 29; i >= 0; i--) { seed = (seed * 9301 + 49297) % 233280; const r = seed / 233280; out.push({ date: new Date(new Date('2026-05-23T10:00:00Z').getTime() - i * 86400000).toISOString().slice(0, 10), usd: 0.06 + r * 0.18, }); } return out; }, []); const dailyAvg = daily.reduce((s, d) => s + d.usd, 0) / daily.length; const projectedMonth = dailyAvg * 30; return (
{/* Top: budget gauge */}
расходы в этом месяце
${totalUsd} / ${budgetUsd} ({Math.round(pctUsed * 100)}%)
≈ {Math.round(totalUsd * usdRate).toLocaleString('ru-RU')} ₽ по курсу ЦБ
прогноз на конец месяца
budgetUsd ? 'var(--danger)' : 'var(--success)', }}>${projectedMonth.toFixed(2)}
{projectedMonth > budgetUsd ? превысит бюджет на ${(projectedMonth - budgetUsd).toFixed(2)} : в рамках бюджета · запас ${(budgetUsd - projectedMonth).toFixed(2)}}
средний день
${dailyAvg.toFixed(2)}
за последние 30 дней
{/* 30-day chart */}

$/день за 30 дней

среднее: ${dailyAvg.toFixed(2)}
{/* Per service (super_admin only) */} {isSuperAdmin && (

распределение по сервисам

{services.map(s => (
{s.label}
{s.calls.toLocaleString('ru-RU')} вызовов ${s.usd.toFixed(2)} · {Math.round(s.share * 100)}%
))}
)} {/* Per user (super_admin only) */} {isSuperAdmin && (

расходы по пользователям

{perUser.map(u => (
{u.user}
${u.usd.toFixed(2)}
))}
)}
); } function CostSparkline({ daily }) { const max = Math.max(...daily.map(d => d.usd)); return ( {/* gridlines */} {[0, 0.25, 0.5, 0.75, 1].map(p => ( ))} {/* area */} `${(i / (daily.length - 1)) * 600},${130 - (d.usd / max) * 120}`), '600,130', ].join(' ')} fill="var(--brand-bronze)" fillOpacity="0.12" /> {/* line */} `${(i / (daily.length - 1)) * 600},${130 - (d.usd / max) * 120}`).join(' ')} fill="none" stroke="var(--brand-bronze)" strokeWidth="1.5" /> {/* points */} {daily.map((d, i) => ( ))} ); } window.CostBreakdown = CostBreakdown;