// Reusable UI components
const { useState, useEffect, useRef, useMemo } = React;
// Generic avatar circle with initials
function Avatar({ initials, color, size = 36, ring = false }) {
return (
{initials}
);
}
function StatusBadge({ status }) {
const map = {
new: { label: "Новый", cls: "bg-[#4F8EF7]/15 text-[#7BA8F9] border-[#4F8EF7]/30" },
in_progress: { label: "В работе", cls: "bg-[#eab308]/15 text-[#eab308] border-[#eab308]/30" },
closed: { label: "Закрыт", cls: "bg-zinc-500/15 text-zinc-400 border-zinc-600/40" },
};
const cfg = map[status] || map.new;
return (
{cfg.label}
);
}
function PlanBadge({ plan }) {
const map = {
Pro: "bg-gradient-to-r from-[#A855F7] to-[#4F8EF7] text-white",
Basic: "bg-[#1f2a44] text-[#7BA8F9] border border-[#4F8EF7]/30",
Trial: "bg-[#3d3320] text-[#eab308] border border-[#eab308]/30",
};
return (
{plan}
);
}
function SubStatus({ status }) {
const map = {
active: { label: "Активна", color: "text-[#22c55e]", dot: "bg-[#22c55e]" },
expiring: { label: "Истекает", color: "text-[#eab308]", dot: "bg-[#eab308]" },
blocked: { label: "Заблокирована", color: "text-[#ef4444]", dot: "bg-[#ef4444]" },
};
const cfg = map[status] || map.active;
return (
{cfg.label}
);
}
// Icons (inline SVG, stroke-based)
function Icon({ name, className = "w-4 h-4", strokeWidth = 1.75 }) {
const paths = {
search: <>>,
bell: <>>,
menu: <>>,
send: <>>,
paperclip: <>>,
chevronDown: <>>,
chevronRight: <>>,
x: <>>,
check: <>>,
plus: <>>,
edit: <>>,
trash: <>>,
chat: <>>,
chart: <>>,
settings: <>>,
bellRing: <>>,
image: <>>,
sparkles: <>>,
user: <>>,
refresh: <>>,
key: <>>,
plus2: <>>,
arrowLeft: <>>,
arrowRight: <>>,
calendar: <>>,
operators: <>>,
book: <>>,
clock: <>>,
server: <>>,
};
return (
);
}
function Toast({ msg }) {
if (!msg) return null;
return (
);
}
Object.assign(window, { Avatar, StatusBadge, PlanBadge, SubStatus, Icon, Toast });