diff --git a/README_v2_2_1.md b/README_v2_2_1.md new file mode 100644 index 0000000..4dc94fb --- /dev/null +++ b/README_v2_2_1.md @@ -0,0 +1,14 @@ +# BotFactory v2.2.1 — Restore Design Hotfix + +Фронтенд возвращён к исходному тёмному дизайну BotFactory из первого архива. + +Сохранены backend-исправления v2.2: +- исправленный запуск platform/admin bot; +- реальные API `/api/admin/*`; +- недельный тестовый тариф; +- индивидуальная постоплата; +- подтверждение/отклонение заказов через API; +- ремонтный скрипт `scripts/repair_server.sh`. + +Важно: демо-данные больше не используются как начальное состояние. Панель грузит данные из API `/api/admin/overview`. +Если база пустая, интерфейс будет пустым, а не с фейковыми пользователями/магазинами. diff --git a/VERSION b/VERSION index ccbccc3..c043eea 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.0 +2.2.1 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 20f8cce..4931899 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "botfactory-frontend", - "version": "2.1.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "botfactory-frontend", - "version": "2.1.0", + "version": "2.0.0", "dependencies": { "axios": "^1.7.2", "react": "^18.3.1", @@ -23,7 +23,6 @@ "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/@babel/code-frame/-/code-frame-7.29.0.tgz", "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", @@ -123,7 +122,6 @@ "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" @@ -185,6 +183,7 @@ "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -194,6 +193,7 @@ "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/@babel/helpers/-/helpers-7.29.2.tgz", "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" @@ -255,7 +255,6 @@ "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/@babel/template/-/template-7.28.6.tgz", "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", @@ -578,7 +577,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -1124,6 +1122,7 @@ "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/@types/babel__generator/-/babel__generator-7.27.0.tgz", "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" } @@ -1192,13 +1191,13 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { "version": "1.16.1", "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/axios/-/axios-1.16.1.tgz", "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", @@ -1238,7 +1237,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -1291,6 +1289,7 @@ "version": "1.0.8", "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1386,6 +1385,7 @@ "version": "2.1.0", "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -1455,7 +1455,6 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -1607,6 +1606,7 @@ "version": "5.0.1", "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", "dependencies": { "agent-base": "6", "debug": "4" @@ -1639,7 +1639,6 @@ "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -1673,7 +1672,6 @@ "version": "1.1.0", "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -1716,7 +1714,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -1832,6 +1829,7 @@ "version": "6.30.3", "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/react-router-dom/-/react-router-dom-6.30.3.tgz", "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", "dependencies": { "@remix-run/router": "1.23.2", "react-router": "6.30.3" @@ -1849,6 +1847,7 @@ "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/rollup/-/rollup-4.60.4.tgz", "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "1.0.8" }, @@ -1953,6 +1952,7 @@ "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/vite/-/vite-5.4.21.tgz", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/frontend/package.json b/frontend/package.json index 1fba9b4..a7e597c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,21 +1,21 @@ { "name": "botfactory-frontend", - "version": "2.1.0", + "version": "2.0.0", "private": true, "type": "module", "scripts": { - "dev": "vite --port 3000", - "build": "vite build", + "dev": "vite --port 3000", + "build": "vite build", "preview": "vite preview --port 3000" }, "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-router-dom": "^6.23.1", - "axios": "^1.7.2" + "axios": "^1.7.2" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.1", - "vite": "^5.2.12" + "vite": "^5.2.12" } } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 557fe5b..d1eb82c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,19 +1,113 @@ -import { useEffect, useMemo, useState } from "react"; -import "./style.css"; +import { useState, useEffect, useCallback } from "react"; -const API = "/api/admin"; -const plans = [ - ["trial_week", "Тест 7 дней"], - ["trial", "Trial"], - ["basic", "Basic"], - ["pro", "Pro"], - ["enterprise", "Enterprise"], - ["postpaid_custom", "Индивидуальная постоплата"], +// ─── HOOKS ──────────────────────────────────────────────────────────────────── +const useMobile = () => { + const [w, setW] = useState(typeof window !== "undefined" ? window.innerWidth : 1200); + useEffect(() => { + const fn = () => setW(window.innerWidth); + window.addEventListener("resize", fn); + return () => window.removeEventListener("resize", fn); + }, []); + return w < 768; +}; + +// ─── CONSTANTS ──────────────────────────────────────────────────────────────── +const R = { + accent:"#4f6ef7", orange:"#f7934f", green:"#4fd98e", + red:"#f75f4f", yellow:"#f7d64f", cyan:"#4fd9f7", + purple:"#b04ff7", + bg:"#07080a", surface:"#0e1014", card:"#13151a", + border:"#1c1f27", border2:"#252830", + text:"#e8eaed", muted:"#6b7280", +}; + +const PLANS = { + trial_week: { label:"Тест 7 дней", color:"#4fd9f7", maxBots:1, rate:10, price:0 }, + trial: { label:"Trial", color:"#888", maxBots:1, rate:10, price:0 }, + basic: { label:"Basic", color:"#4ECDC4", maxBots:3, rate:7, price:990 }, + pro: { label:"Pro", color:"#f7d64f", maxBots:10, rate:5, price:2490 }, + enterprise: { label:"Enterprise", color:"#f7934f", maxBots:999, rate:3, price:7990 }, + postpaid_custom: { label:"Индивидуал", color:"#b04ff7", maxBots:999, rate:0, price:0 }, +}; + +// ─── MOCK DATA ──────────────────────────────────────────────────────────────── +const INIT_TENANT = { + id:1, name:"Алексей Громов", email:"alex@shop.ru", plan:"pro", + status:"active", balance:3450, alertThreshold:500, + totalOrders:142, totalTurnover:284500, totalCommission:14225, +}; + +// Each shop = one "магазин" with: +// controlBot – отдельный бот-оператора (не банится вместе с магазином) +// tokens – список токенов shop-бота (один активный, остальные резерв) +const INIT_SHOPS = [ + { + id:1, name:"ShopBot Alpha", revenue:178000, orders:89, products:12, + controlBot:{ token:"7100000001:CTRL_BOT_AAA", username:"@shopbot_alpha_ctrl", status:"active" }, + tokens:[ + { id:1, token:"7123456789:AAFxxx", username:"@shopbot_alpha", active:true, status:"active", note:"Основной", addedAt:"2026-02-01" }, + { id:2, token:"7987654321:BBByyy", username:"@shopbot_alpha_2", active:false, status:"standby", note:"Резервный", addedAt:"2026-04-10" }, + ], + admins:[{ id:1, name:"Алексей Громов", username:"@alex_gromov", role:"admin" }], + moderators:[ + { id:2, name:"Сергей Романов", username:"@sergey_mod", role:"moderator", addedAt:"2026-03-01" }, + { id:3, name:"Анна Белова", username:"@anna_cashier", role:"moderator", addedAt:"2026-03-15" }, + ], + }, + { + id:2, name:"Keys & Codes", revenue:106500, orders:53, products:28, + controlBot:{ token:"7200000002:CTRL_BOT_BBB", username:"@keys_codes_ctrl", status:"active" }, + tokens:[ + { id:1, token:"7111111111:CCCzzz", username:"@keys_codes_bot", active:true, status:"active", note:"Основной", addedAt:"2026-03-15" }, + ], + admins:[{ id:1, name:"Алексей Громов", username:"@alex_gromov", role:"admin" }], + moderators:[ + { id:4, name:"Дмитрий Петров", username:"@dmitry_mod", role:"moderator", addedAt:"2026-04-01" }, + ], + }, ]; -const fmt = (v) => `${Number(v || 0).toLocaleString("ru-RU")} ₽`; -const date = (v) => (v ? new Date(v).toLocaleString("ru-RU") : "—"); -async function api(path, options = {}) { +const INIT_CARDS = [ + { id:1, shopId:1, bank:"Сбербанк", number:"4276 **** **** 4521", holder:"ALEKSEY GROMOV", phone:"+7 999 123-45-67", active:true, orders:34, received:68000 }, + { id:2, shopId:1, bank:"Тинькофф", number:"5536 **** **** 7832", holder:"ALEKSEY GROMOV", phone:"+7 988 987-65-43", active:true, orders:28, received:56000 }, + { id:3, shopId:2, bank:"ВТБ", number:"4272 **** **** 1203", holder:"ALEKSEY GROMOV", phone:"+7 977 555-11-22", active:true, orders:18, received:36000 }, + { id:4, shopId:1, bank:"Альфа", number:"5486 **** **** 9944", holder:"MARIA GROMOVA", phone:"", active:false, orders:6, received:12000 }, +]; + +const INIT_ORDERS = [ + { id:101, shopId:1, shopName:"ShopBot Alpha", product:"VPN Premium 3 мес", buyer:"@user_alex99", buyerId:9910001, amount:599, commission:30, cardId:1, status:"pending", date:"2026-05-24 14:52", proof:null }, + { id:102, shopId:1, shopName:"ShopBot Alpha", product:"Spotify Premium", buyer:"@music_lover", buyerId:9920002, amount:299, commission:15, cardId:2, status:"confirming", date:"2026-05-24 13:30", proof:"screenshot.jpg" }, + { id:103, shopId:2, shopName:"Keys & Codes", product:"Windows 11 Pro Key", buyer:"@techguy_77", buyerId:9930003, amount:890, commission:45, cardId:3, status:"completed", date:"2026-05-24 12:00", proof:"pay_proof.jpg" }, + { id:104, shopId:1, shopName:"ShopBot Alpha", product:"VPN Premium 3 мес", buyer:"@vpn_user22", buyerId:9940004, amount:599, commission:30, cardId:1, status:"completed", date:"2026-05-23 20:15", proof:"done.jpg" }, + { id:105, shopId:2, shopName:"Keys & Codes", product:"Office 2024 Key", buyer:"@office_pro", buyerId:9950005, amount:1290, commission:65, cardId:3, status:"rejected", date:"2026-05-23 18:00", proof:"fake.jpg" }, +]; + +const INIT_TX = [ + { id:1, type:"deposit", amount:+5000, date:"2026-05-20 10:00", note:"Пополнение баланса" }, + { id:2, type:"commission", amount:-30, date:"2026-05-24 12:00", note:"Комиссия: заказ #101 (599₽ × 5%)" }, + { id:3, type:"commission", amount:-15, date:"2026-05-24 13:30", note:"Комиссия: заказ #102 (299₽ × 5%)" }, + { id:4, type:"commission", amount:-45, date:"2026-05-24 12:00", note:"Комиссия: заказ #103 (890₽ × 5%)" }, + { id:5, type:"alert", amount:0, date:"2026-05-22 08:00", note:"⚠️ Уведомление: баланс ниже 500₽" }, + { id:6, type:"deposit", amount:+3000, date:"2026-05-15 14:00", note:"Пополнение баланса" }, +]; + +const MOCK_PLATFORM_TENANTS = [ + { id:1, name:"Алексей Громов", email:"alex@shop.ru", plan:"pro", status:"active", balance:3450, alertThreshold:500, totalOrders:142, totalTurnover:284500, totalCommission:14225 }, + { id:2, name:"Мария Селезнёва", email:"maria@digital.ru",plan:"basic", status:"active", balance:120, alertThreshold:200, totalOrders:87, totalTurnover:174000, totalCommission:12180 }, + { id:3, name:"Иван Кузнецов", email:"ivan@goods.ru", plan:"pro", status:"blocked", balance:0, alertThreshold:300, totalOrders:44, totalTurnover:98000, totalCommission:4900 }, + { id:4, name:"Дмитрий Попов", email:"dm@newshop.ru", plan:"trial", status:"active", balance:75, alertThreshold:50, totalOrders:5, totalTurnover:4500, totalCommission:450 }, +]; + + +// ─── REAL API MAPPING ───────────────────────────────────────────────────────── +const API = "/api/admin"; + +const EMPTY_TENANT = { + id:null, name:"Нет владельца", email:"", plan:"trial_week", status:"active", + balance:0, alertThreshold:200, totalOrders:0, totalTurnover:0, totalCommission:0, +}; + +async function adminApi(path, options = {}) { const res = await fetch(`${API}${path}`, { headers: { "Content-Type": "application/json", ...(options.headers || {}) }, ...options, @@ -23,168 +117,1269 @@ async function api(path, options = {}) { return body; } -function useOverview() { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - const load = async () => { - setLoading(true); - setError(""); - try { setData(await api("/overview")); } - catch (e) { setError(e.message); } - finally { setLoading(false); } - }; - useEffect(() => { load(); }, []); - return { data, loading, error, load }; +function mapOverview(data, prevTenantId = null) { + const rawTenants = data?.tenants || []; + const rawShops = data?.shops || []; + const rawTokens = data?.tokens || []; + const rawMembers = data?.members || []; + const rawOrders = data?.orders || []; + const rawCards = data?.cards || []; + const rawProducts = data?.products || []; + const rawTx = data?.transactions || []; + + const productById = Object.fromEntries(rawProducts.map(p => [p.id, p])); + const shopById = Object.fromEntries(rawShops.map(s => [s.id, s])); + + const tenants = rawTenants.map(t => { + const tenantShopIds = rawShops.filter(s => s.tenant_id === t.id).map(s => s.id); + const tenantOrders = rawOrders.filter(o => tenantShopIds.includes(o.shop_id)); + return { + id:t.id, name:t.name || `Пользователь #${t.id}`, email:t.email || "", plan:t.plan || "trial_week", + status:t.is_blocked ? "blocked" : "active", + balance:Number(t.balance || 0), alertThreshold:Number(t.alert_threshold || 0), + totalOrders:tenantOrders.length, totalTurnover:Number(t.total_revenue || 0), + totalCommission:Number(t.total_commission || 0), + postpaidDue:Number(t.postpaid_previous_month_due || 0), + postpaidDueDate:t.postpaid_due_date, + postpaidPercent:t.postpaid_commission_percent, + }; + }); + + const selectedTenantId = prevTenantId && tenants.some(t => t.id === prevTenantId) + ? prevTenantId + : tenants[0]?.id; + const tenant = tenants.find(t => t.id === selectedTenantId) || EMPTY_TENANT; + + const tenantShops = rawShops.filter(s => !tenant.id || s.tenant_id === tenant.id).map(s => ({ + id:s.id, name:s.name, revenue:Number(s.revenue || 0), orders:Number(s.orders_count || 0), products:Number(s.products_count || 0), + controlBot:{ token:s.ctrl_bot_token_set ? "set" : "", username:s.ctrl_bot_username || "—", status:s.is_active ? "active" : "disabled" }, + tokens:rawTokens.filter(t => t.shop_id === s.id).map(t => ({ + id:t.id, token:t.token_masked || "", username:t.username || "—", active:!!t.is_active, + status:t.is_active ? "active" : "standby", note:t.note || "Резервный", addedAt:(t.added_at || "").slice(0,10), + })), + admins:[], + moderators:rawMembers.filter(m => m.shop_id === s.id).map(m => ({ + id:m.id, name:m.name, username:m.username || String(m.telegram_id || ""), role:m.role || "moderator", addedAt:(m.added_at || "").slice(0,10), + })), + })); + + const tenantShopIds = tenantShops.map(s => s.id); + const cards = rawCards.filter(c => tenantShopIds.includes(c.shop_id)).map(c => ({ + id:c.id, shopId:c.shop_id, bank:c.bank, number:c.number, holder:c.holder, phone:c.phone || "", + active:!!c.is_active, orders:Number(c.orders_count || 0), received:Number(c.received_total || 0), + })); + + const orders = rawOrders.filter(o => tenantShopIds.includes(o.shop_id) || !tenant.id).map(o => { + const shop = shopById[o.shop_id]; + const product = productById[o.product_id]; + return { + id:o.id, shopId:o.shop_id, shopName:shop?.name || `Магазин #${o.shop_id}`, + product:product?.name || `Товар #${o.product_id || "—"}`, buyer:o.buyer_username || `ID ${o.buyer_telegram_id || "—"}`, + buyerId:o.buyer_telegram_id || "—", amount:Number(o.amount || 0), commission:Number(o.commission || 0), + cardId:o.card_id || "—", status:o.status || "pending", date:(o.created_at || "").replace("T", " ").slice(0,16), + proof:o.proof_file_id ? "чек получен" : null, + }; + }); + + const txs = rawTx.filter(x => !tenant.id || x.tenant_id === tenant.id).map(x => ({ + id:x.id, type:x.type, amount:Number(x.amount || 0), + date:(x.created_at || "").replace("T", " ").slice(0,16), note:x.note || "", + })); + + return { tenant, shops:tenantShops, cards, orders, txs, platformTenants:tenants }; } -function Field({ label, children }) { - return ; -} +// ═══════════════════════════════════════════════════════════════════════════════ +// ICONS +// ═══════════════════════════════════════════════════════════════════════════════ +const P = { + dashboard:"M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z", + shops:"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z", + orders:"M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z", + cards:"M20 4H4c-1.11 0-2 .89-2 2v12c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z", + team:"M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z", + settings:"M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.57 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z", + plus:"M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z", + check:"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z", + close:"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z", + trash:"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z", + eye:"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z", + wallet:"M21 18v1c0 1.1-.9 2-2 2H5c-1.11 0-2-.9-2-2V5c0-1.1.89-2 2-2h14c1.1 0 2 .9 2 2v1h-9c-1.11 0-2 .9-2 2v8c0 1.1.89 2 2 2h9zm-9-2h10V8H12v8zm4-2.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z", + alert:"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z", + lock:"M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z", + shuffle:"M10.59 9.17L5.41 4 4 5.41l5.17 5.17 1.42-1.41zM14.5 4l2.04 2.04L4 18.59 5.41 20 17.96 7.46 20 9.5V4h-5.5zm.33 9.41l-1.41 1.41 3.13 3.13L14.5 20H20v-5.5l-2.04 2.04-3.13-3.13z", + swap:"M6.99 11L3 15l3.99 4v-3H14v-2H6.99v-3zM21 9l-3.99-4v3H10v2h7.01v3L21 9z", + key:"M12.65 10C11.83 7.67 9.61 6 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6c2.61 0 4.83-1.67 5.65-4H17v4h4v-4h2v-4H12.65zM7 14c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z", + ctrl:"M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z", + shield:"M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z", + users:"M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z", + revenue:"M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z", + history:"M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z", + menu:"M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z", + back:"M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z", + bell:"M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z", + edit:"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z", + topup:"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z", + percent:"M7.5 11C9.43 11 11 9.43 11 7.5S9.43 4 7.5 4 4 5.57 4 7.5 5.57 11 7.5 11zm0-5C8.33 6 9 6.67 9 7.5S8.33 9 7.5 9 6 8.33 6 7.5 6.67 6 7.5 6zM4 18.5L18.5 4l1.5 1.5L5.5 20 4 18.5zm12-2.5c-1.93 0-3.5 1.57-3.5 3.5S14.07 23 16 23s3.5-1.57 3.5-3.5S17.93 16 16 16zm0 5c-.83 0-1.5-.67-1.5-1.5S15.17 18 16 18s1.5.67 1.5 1.5S16.83 21 16 21z", +}; -function Modal({ title, onClose, children }) { - return
e.target === e.currentTarget && onClose()}> -
-
{title}
+const Ic = ({ n, s=16, c }) => ( + + + +); + +// ═══════════════════════════════════════════════════════════════════════════════ +// ATOMS +// ═══════════════════════════════════════════════════════════════════════════════ +const css = ` + @import url('https://fonts.googleapis.com/css2?family=Syne:wght@700;800&family=JetBrains+Mono:wght@400;600&display=swap'); + *{box-sizing:border-box;margin:0;padding:0} + body{background:#07080a;color:#e8eaed;-webkit-font-smoothing:antialiased} + ::-webkit-scrollbar{width:4px;height:4px} + ::-webkit-scrollbar-track{background:#111} + ::-webkit-scrollbar-thumb{background:#2a2a2a;border-radius:2px} + select option{background:#161920} + input::placeholder,textarea::placeholder{color:#3a3f4a} + input:focus,textarea:focus,select:focus{outline:none!important;border-color:#4f6ef7!important} + tr:hover>td{background:#0f1118!important} + @media(max-width:767px){ + .hide-mobile{display:none!important} + .mob-full{width:100%!important} + .mob-stack{flex-direction:column!important} + .mob-p{padding:16px!important} + .mob-grid1{grid-template-columns:1fr!important} + .mob-grid2{grid-template-columns:1fr 1fr!important} + .mob-fs13{font-size:13px!important} + .mob-fs22{font-size:22px!important} + } +`; + +const Tag = ({label,color,bg})=>( + {label} +); +const PlanTag = ({plan})=>{const p=PLANS[plan]||PLANS.trial_week;return ;}; + +const Btn = ({children,onClick,v="primary",sm,full,disabled,style:s={}})=>{ + const st={ + primary:{bg:R.accent,color:"#fff",bo:R.accent}, + ghost:{bg:"transparent",color:R.muted,bo:R.border2}, + danger:{bg:`${R.red}15`,color:R.red,bo:`${R.red}40`}, + success:{bg:`${R.green}15`,color:R.green,bo:`${R.green}40`}, + warning:{bg:`${R.yellow}15`,color:R.yellow,bo:`${R.yellow}40`}, + orange:{bg:`${R.orange}15`,color:R.orange,bo:`${R.orange}40`}, + purple:{bg:`${R.purple}15`,color:R.purple,bo:`${R.purple}40`}, + cyan:{bg:`${R.cyan}15`,color:R.cyan,bo:`${R.cyan}40`}, + }[v]||{bg:R.surface,color:R.muted,bo:R.border2}; + return( + + ); +}; + +const F = ({label,children,style:s={}})=>( +
+ {label&&} + {children} +
+); +const baseInput = {width:"100%",background:R.surface,border:`1px solid ${R.border2}`,borderRadius:8,padding:"9px 12px",color:R.text,fontSize:13,fontFamily:"'JetBrains Mono'"}; +const Inp = ({label,...p})=>; +const Sel = ({label,children,...p})=>; +const Txa = ({label,...p})=>