// MDM SORP — Home, Deals grid, Deal card, Parties grid, Party card. const { useState, useEffect, useMemo, useRef, useCallback, createContext, useContext } = React; /* ===================== HOME ===================== */ function HomeScreen({ navigate }) { const { t, lang } = useI18n(); return (

{lang === "ru" ? "Доброе утро, Екатерина" : "Good morning, Ekaterina"}

{t("home_sub")}
{lang === "ru" ? "Среда, 27 мая 2026" : "Wednesday, May 27, 2026"} · 14:32
{t("last_synced")}: Amo 03:14 · Asana 08:02 · Drive 11:45
{/* Today's queues */}

{t("home_queues")}

обновлено 30 сек назад
{/* Activity */}

{t("home_activity")}

за последние 12 часов
{/* Recent deals */}

{t("home_recent")}

кликом — карточка сделки
); } const KPI = ({ lbl, v, delta, deltaTone = "up", sub }) => (
{lbl}
{v}
{delta}
{sub}
); const QueueCard = ({ navigate, kind, count, hot, title, desc, icon, danger }) => { const { lang } = useI18n(); return (
navigate(kind)} style={{ display: "flex", alignItems: "center", gap: 14, padding: "12px 16px", borderBottom: "1px solid var(--neutral-100)", cursor: "pointer", }} onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-page)"} onMouseLeave={(e) => e.currentTarget.style.background = ""}>
{title}
{desc}
{hot > 0 && {hot} срочно}
{count}
); }; const Activity = ({ actor, action, sub, time }) => (
{actor} · {action}
{sub}
{time}
); /* ===================== DEALS GRID (Airtable-style) ===================== */ function DealsScreen({ navigate }) { const { t, lang } = useI18n(); // Enrich deal rows with linked-record metadata const rows = useMemo(() => window.DATA.deals.map(d => { const c = findParty(d.customer); const eu = findParty(d.end_user); const v = findParty(d.vehicle); return { ...d, title: lang === "en" ? d.title_en : d.title, stage: stageKey(d.stage_en), _customer: c, _customer_pills: c ? [{ id: c.id, label: c.short, flag: c.country }] : [], _eu_pills: eu ? [{ id: eu.id, label: eu.short, flag: eu.country }] : [], _vehicle_pills: v ? [{ id: v.id, label: v.short, flag: v.country }] : [], items: { done: d.items_done, total: d.items_count }, systems: { amo: !!d.amo_id, asana: !!d.asana_gid, drive: !!d.drive, onec: false }, }; }), [lang]); const stages = [ { value: "inquiry", label: "Заявка", color: "gray" }, { value: "sourcing", label: "Поиск поставщиков", color: "yellow" }, { value: "spec", label: "Спецификация", color: "blue" }, { value: "proposal", label: "КП заказчику", color: "purple" }, { value: "procurement", label: "Закупка", color: "orange" }, { value: "delivery", label: "Поставка", color: "teal" }, { value: "closed", label: "Завершено", color: "green" }, ]; const columns = [ { id: "id", label: "SORP-ключ", type: "text", width: 110, mono: true }, { id: "title", label: "Сделка", type: "primary", width: 320 }, { id: "_customer_pills", label: "Заказчик", type: "linked", width: 200 }, { id: "_eu_pills", label: "Конечник", type: "linked", width: 180 }, { id: "_vehicle_pills", label: "Заход", type: "linked", width: 180 }, { id: "stage", label: "Этап", type: "status_workflow", width: 230, options: stages }, { id: "value_usd", label: "Сумма", type: "currency", ccy: "USD", width: 130 }, { id: "margin_pct", label: "Маржа", type: "percent", width: 90 }, { id: "deadline", label: "Дедлайн", type: "date", width: 160 }, { id: "items", label: "Позиции", type: "progress", width: 150 }, { id: "systems", label: "Системы", type: "systems", width: 130 }, { id: "managers", label: "Менеджеры", type: "users", width: 100 }, { id: "sanctions", label: "Санкции", type: "sanctions", width: 120, options: [ { value: "clean", label: "Чисто", color: "green" }, { value: "review", label: "Review", color: "yellow" }, { value: "flag", label: "Flag", color: "red" }, ] }, ]; const views = [ { name: "Все сделки", kind: "grid", count: rows.length, sort: [{ field: "deadline", dir: "asc" }] }, { name: "По этапу", kind: "kanban", stackBy: "stage" }, { name: "Календарь дедлайнов", kind: "calendar", dateField: "deadline" }, { name: "Галерея high-value", kind: "gallery", filter: [{ field: "value_usd", op: "gt", value: 100000 }] }, { name: "Срочные ≤ 30д", kind: "grid", filter: [{ field: "stage", op: "not", value: "closed" }], sort: [{ field: "deadline", dir: "asc" }], colorBy: "sanctions" }, { name: "Группа по этапу", kind: "grid", groupBy: "stage", sort: [{ field: "value_usd", dir: "desc" }] }, ]; return ( navigate("deal", { id: r.id })} /> ); } function stageKey(en) { return ({ "Inquiry": "inquiry", "Sourcing": "sourcing", "Spec sheet": "spec", "Proposal sent": "proposal", "Procurement": "procurement", "Delivery": "delivery", "Closed": "closed", })[en] || "spec"; } /* ===================== DEAL CARD ===================== */ function DealCardScreen({ navigate, params }) { const { t, lang } = useI18n(); const id = params?.id || "SORP-3929"; const d = findDeal(id) || window.DATA.deals[0]; const cust = findParty(d.customer); const eu = findParty(d.end_user); const veh = findParty(d.vehicle); const [tab, setTab] = useState("items"); return (
{/* Header */}
SORP-key· {lang === "ru" ? "обновлено" : "updated"} {d.updated} · {lang === "ru" ? "создано" : "created"} {d.created}

{d.id} {lang === "en" ? d.title_en : d.title}

{lang === "en" ? d.stage_en : d.stage} {cust?.short} · {lang === "ru" ? "заказчик" : "customer"} {eu?.id !== cust?.id && {eu?.short} · {lang === "ru" ? "конечник" : "end-user"}} {veh?.short} · {lang === "ru" ? "заход" : "vehicle"} {fmtMoney(d.value_usd, "USD")} · {d.margin_pct}% маржа {lang === "ru" ? "дедлайн" : "deadline"}: {d.deadline}
{/* Cross-system links */}
{[ { id: "items", label: t("items"), n: d.items_count }, { id: "sourcing", label: t("sourcing"), n: 4 }, { id: "docs", label: t("docs"), n: 18 }, { id: "history", label: t("history"), n: 42 }, { id: "parties", label: lang === "ru" ? "Стороны" : "Parties", n: 5 }, ].map(x => )}
{/* Body */} {tab === "items" && } {tab === "sourcing" && } {tab === "docs" && } {tab === "history" && } {tab === "parties" && }
); } const SystemLink = ({ color, label, id, sub, status }) => (
{label} {id}
{sub}
{status === "ok" && } {status === "pending" && soon}
); function DealItemsTab() { const { lang } = useI18n(); return (
{window.DATA.dealItems_3929.map((it, i) => { const sup = it.supplier ? findParty(it.supplier) : null; const empty = !it.name_acc || it.name_acc === "—"; return ( ); })}
{lang === "ru" ? "Запрошено" : "Requested"} {lang === "ru" ? "Принято / артикул" : "Accepted / SKU"} HS {lang === "ru" ? "Кол-во" : "Qty"} {lang === "ru" ? "Поставщик" : "Supplier"} {lang === "ru" ? "Закупка" : "Buy"} {lang === "ru" ? "Продажа" : "Sell"} {lang === "ru" ? "Маржа" : "Margin"} {lang === "ru" ? "Срок" : "Lead"} {lang === "ru" ? "Сертификат" : "Cert"} {lang === "ru" ? "Сан." : "Sanc."}
{it.idx} {it.name_req} {empty ? {lang === "ru" ? "не подтверждено" : "not accepted"} : (
{it.name_acc}
)}
{it.hs || "—"} {empty ? {it.qty_req} запр. : it.qty_acc} {sup ?
{sup.short}
: {lang === "ru" ? "в сорсинге" : "in sourcing"}}
{it.buy ? fmtMoney(it.buy.price, it.buy.ccy) : } {it.sell ? fmtMoney(it.sell.price, it.sell.ccy) : } {it.margin_pct ? it.margin_pct + "%" : "—"} {it.lead_days ? it.lead_days + "д" : "—"} {it.cert === "ok" ? EAC/CT-1 : it.cert === "pending" ? Ожидается : } {it.sanctions ? Flag : OK}
1 позиция без поставщика — отправить в сорсинг? Итого: 5 позиций · $98 460 закупка · $112 100 продажа · 13.9% маржа взвешенная Курсы: USD 78.42 · EUR 84.10 · AED 21.35 · от 2026-05-27 ЦБ РФ
); } function DealSourcingTab() { return (

Сорсинг: подшипник радиально-упорный 7224 · HS 8482.10

4 кандидата
{[ { name: "Wuxi WLR Bearings Co., Ltd.", country: "CN", price: "$142", lead: 28, sanc: "clean", cap: 0.91, note: "Прямой производитель, MOQ 100" }, { name: "ZWZ Wafangdian Bearing Group", country: "CN", price: "$165", lead: 35, sanc: "clean", cap: 0.85, note: "В каталоге аналог 7224C" }, { name: "Harbin HRB Group", country: "CN", price: "$180", lead: 42, sanc: "clean", cap: 0.78, note: "Через дистрибутора в OAE" }, { name: "NSK Asia Ltd.", country: "JP", price: "$210", lead: 60, sanc: "review", cap: 0.95, note: "Может отказать — комплаенс смотрит" }, ].map((c, i) => (
{c.name}
{c.note}
Совпадение возможностей
{Math.round(c.cap * 100)}%
{c.price}
{c.lead}д
))}
); } function DealDocsTab() { const docs = [ { name: "Заявка-Северсталь-Метиз_v1.pdf", type: "inquiry", typed: 0.99, size: "1.2 MB", date: "2026-04-12" }, { name: "КП-Schaeffler-SORP-3929-v3.pdf", type: "proposal", typed: 0.98, size: "486 KB", date: "2026-05-26" }, { name: "Spec-bearings-confirmed.xlsx", type: "spec", typed: 0.95, size: "112 KB", date: "2026-05-22" }, { name: "Invoice INV-2026-04321.pdf", type: "invoice", typed: 0.92, size: "230 KB", date: "—", scanned: true }, { name: "Bill_of_Lading_MSC_38421.pdf", type: "customs", typed: 0.81, size: "1.8 MB", date: "—", scanned: true }, { name: "Pmt-Caspian-USD-104k.pdf", type: "payment", typed: 0.71, size: "210 KB", date: "—" }, { name: "Photo-pre-shipment.jpg", type: "photo", typed: 0.96, size: "3.4 MB", date: "—" }, { name: "EAC-cert-SKF-bearings.pdf", type: "cert", typed: 0.94, size: "780 KB", date: "—" }, ]; const types = { inquiry: "Заявка", proposal: "КП", spec: "Спецификация", invoice: "Инвойс", customs: "Таможня", payment: "Платёж", photo: "Фото", cert: "Сертификат" }; return (

Папка Google Drive · 18 документов · OCR готов

{docs.map((d, i) => ( ))}
Файл Тип (ИИ-распознан) Размер Дата в документе Источник
{d.name} {d.scanned && скан · OCR}
{types[d.type]}
{d.size} {d.date} Drive
); } function DealHistoryTab() { const events = [ { who: "Е. Соколова", what: "stage", old: "Поиск поставщиков", neu: "Спецификация", when: "сегодня 14:08", src: "manual" }, { who: "ИИ Claude", what: "items[03].supplier", old: "—", neu: "Schaeffler Middle East FZE", when: "сегодня 12:18", src: "ai", conf: 0.94 }, { who: "Service", what: "amo_id", old: "—", neu: "47821", when: "сегодня 03:14", src: "amo" }, { who: "Д. Петров", what: "deadline", old: "2026-07-04", neu: "2026-07-18", when: "вчера 16:42", src: "manual" }, { who: "Service", what: "asana_gid", old: "—", neu: "1206734821093", when: "2026-05-20 11:30", src: "asana" }, { who: "С. Кравцов", what: "title", old: "Подшипники SKF для ТМК", neu: "Промышленные подшипники SKF для ТМК Волжский", when: "2026-04-12 10:12", src: "manual" }, ]; return (

История изменений

{events.map((e, i) => (
{e.who}
{e.what} {e.old} {e.neu} {e.when}
))}
); } function DealPartiesTab({ d, cust, eu, veh }) { const parties = [ { p: cust, role: "customer", note: "Подписант: А. Морозов · ген. директор" }, { p: eu, role: "end_user", note: "Конечник = заказчику · single-tier поставка" }, { p: veh, role: "vehicle", note: "Наша компания-заход · Dubai (DAFZA)" }, { p: findParty("p_2001"), role: "supplier", note: "Подшипники · поз. 01-02-05" }, { p: findParty("p_2003"), role: "supplier", note: "Подшипники конические · поз. 03 · ⚠️ возможный дубль" }, ]; return (
{parties.filter(x => x.p).map(({ p, role, note }) => (
{p.full}
{note}
))}
); } function DealsCompact({ navigate }) { const { lang } = useI18n(); return ( {window.DATA.deals.map(d => { const cust = findParty(d.customer); return ( navigate("deal", { id: d.id })}> ); })}
ID {lang === "ru" ? "Сделка" : "Deal"} {lang === "ru" ? "Заказчик" : "Customer"} {lang === "ru" ? "Этап" : "Stage"} {lang === "ru" ? "Сумма" : "Value"} {lang === "ru" ? "Маржа" : "Margin"} {lang === "ru" ? "Дедлайн" : "Deadline"} {lang === "ru" ? "Поз." : "Items"} {lang === "ru" ? "Санкции" : "Sanc."}
{d.id} {lang === "en" ? d.title_en : d.title}
{cust?.short}
{lang === "en" ? d.stage_en : d.stage} {fmtMoney(d.value_usd, "USD")} {d.margin_pct}% {d.deadline} {d.items_done}/{d.items_count}
); } window.HomeScreen = HomeScreen; window.DealsScreen = DealsScreen; window.DealCardScreen = DealCardScreen; window.DealsCompact = DealsCompact;