From a848bb13e28123ca7199f1f6781e28fd34d0e341 Mon Sep 17 00:00:00 2001 From: zabudko-ru Date: Wed, 21 Oct 2026 02:32:36 +0400 Subject: [PATCH] Order card: 'Message provider' and 'Call' buttons, also for a pending offer Co-Authored-By: Claude Opus 5.5 (1M context) --- apps/api/src/routers/backoffice/orders.ts | 6 ++-- apps/backoffice/src/components/kit.tsx | 1 + apps/backoffice/src/i18n.tsx | 6 ++++ apps/backoffice/src/orders.css | 6 ++++ apps/backoffice/src/pages/OrderCard.tsx | 37 ++++++++++++++++++++--- 5 files changed, 49 insertions(+), 7 deletions(-) diff --git a/apps/api/src/routers/backoffice/orders.ts b/apps/api/src/routers/backoffice/orders.ts index 9881bb1..f0cbb35 100644 --- a/apps/api/src/routers/backoffice/orders.ts +++ b/apps/api/src/routers/backoffice/orders.ts @@ -196,7 +196,7 @@ export const boOrdersRouter = router({ client: { include: { user: true, subscriptions: { where: { status: 'ACTIVE' } } } }, history: { orderBy: { createdAt: 'asc' } }, checklist: { orderBy: { sortOrder: 'asc' } }, - offers: { orderBy: { createdAt: 'desc' }, include: { provider: { select: { name: true } }, company: { select: { name: true } } } }, + offers: { orderBy: { createdAt: 'desc' }, include: { provider: { select: { id: true, name: true, phone: true } }, company: { select: { name: true } } } }, files: { where: { deletedAt: null }, select: { id: true, kind: true, mime: true, createdAt: true } }, paymentMethod: true, rating: true, @@ -233,7 +233,9 @@ export const boOrdersRouter = router({ client: { id: o.clientId, name: name(o.client.user), username: o.client.user.username, phone: o.client.user.phone, plan: sub?.plan ?? null }, provider: o.provider ? { id: o.provider.id, name: o.provider.name, phone: o.provider.phone, username: o.provider.user?.username ?? null } : null, company: o.company ? { id: o.company.id, name: o.company.name } : null, - pending: pending ? { id: pending.id, name: pending.provider?.name ?? pending.company?.name ?? '', expiresAt: pending.expiresAt, auto: !pending.createdById, company: !pending.providerId } : null, + pending: pending + ? { id: pending.id, name: pending.provider?.name ?? pending.company?.name ?? '', providerId: pending.provider?.id ?? null, phone: pending.provider?.phone ?? null, expiresAt: pending.expiresAt, auto: !pending.createdById, company: !pending.providerId } + : null, offers: o.offers.map((x) => ({ id: x.id, name: x.provider?.name ?? x.company?.name ?? '', diff --git a/apps/backoffice/src/components/kit.tsx b/apps/backoffice/src/components/kit.tsx index 7171190..d88ef9d 100644 --- a/apps/backoffice/src/components/kit.tsx +++ b/apps/backoffice/src/components/kit.tsx @@ -37,6 +37,7 @@ const PATHS = { archive: 'M4 5h16v4H4zM5 9v10h14V9M10 13h4', bot: 'M6 9h12v10H6zM12 5v4M9.5 13.5v.01M14.5 13.5v.01M9 17h6M3 13v2M21 13v2', warn: 'M12 3l10 18H2zM12 10v5M12 18v.01', + phone: 'M5 4h4l2 5-2.5 1.5a11 11 0 0 0 5 5L15 13l5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 6a2 2 0 0 1 2-2z', } as const; export type IconName = keyof typeof PATHS; diff --git a/apps/backoffice/src/i18n.tsx b/apps/backoffice/src/i18n.tsx index 8902a25..ebede46 100644 --- a/apps/backoffice/src/i18n.tsx +++ b/apps/backoffice/src/i18n.tsx @@ -433,6 +433,8 @@ const ru = { 'ord.client': 'Клиент', 'ord.writeClient': 'Написать клиенту', 'ord.writeProvider': 'Написать исполнителю', + 'ord.call': 'Позвонить', + 'ord.noPhone': 'Телефон исполнителя не указан — добавьте его в профиле', 'ord.money': 'Деньги', 'ord.base': 'Без скидки', 'ord.discount': 'Скидка {pct}%', @@ -1075,6 +1077,8 @@ const en: Dict = { 'ord.client': 'Client', 'ord.writeClient': 'Message the client', 'ord.writeProvider': 'Message the provider', + 'ord.call': 'Call', + 'ord.noPhone': 'No phone number — add it to the profile', 'ord.money': 'Money', 'ord.base': 'Before discount', 'ord.discount': 'Discount {pct}%', @@ -1714,6 +1718,8 @@ const hy: Dict = { 'ord.client': 'Հաճախորդ', 'ord.writeClient': 'Գրել հաճախորդին', 'ord.writeProvider': 'Գրել կատարողին', + 'ord.call': 'Զանգահարել', + 'ord.noPhone': 'Հեռախոսահամարը նշված չէ', 'ord.money': 'Գումար', 'ord.base': 'Առանց զեղչի', 'ord.discount': 'Զեղչ {pct}%', diff --git a/apps/backoffice/src/orders.css b/apps/backoffice/src/orders.css index b482c31..0792153 100644 --- a/apps/backoffice/src/orders.css +++ b/apps/backoffice/src/orders.css @@ -352,6 +352,12 @@ } /* подбор исполнителя */ +.bo-contact { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; +} .bo-pendingbox { display: flex; align-items: center; diff --git a/apps/backoffice/src/pages/OrderCard.tsx b/apps/backoffice/src/pages/OrderCard.tsx index 276d7ce..bf5d6f3 100644 --- a/apps/backoffice/src/pages/OrderCard.tsx +++ b/apps/backoffice/src/pages/OrderCard.tsx @@ -419,6 +419,7 @@ function ProviderPanel({ order: d, onChanged, canChat }: { order: OrderData; onC {d.provider ? ( + <>
@@ -427,13 +428,11 @@ function ProviderPanel({ order: d, onChanged, canChat }: { order: OrderData; onC {[d.company?.name, d.provider.phone, d.provider.username && `@${d.provider.username}`].filter(Boolean).join(' · ') || '—'} - {canChat && ( - - )}
+ chat.mutate({ providerId: id })} busy={chat.isPending} /> + ) : d.pending ? ( + <>
{d.pending.auto ? : } @@ -443,6 +442,8 @@ function ProviderPanel({ order: d, onChanged, canChat }: { order: OrderData; onC
+ {d.pending.providerId && chat.mutate({ providerId: id })} busy={chat.isPending} />} + ) : d.canAssign ? ( <>
@@ -496,6 +497,32 @@ function ProviderPanel({ order: d, onChanged, canChat }: { order: OrderData; onC ); } +/** Связаться с исполнителем: чат в рабочем боте и звонок */ +function ContactRow({ providerId, phone, canChat, onChat, busy }: { providerId: string; phone: string | null; canChat: boolean; onChat: (id: string) => void; busy: boolean }) { + const { t } = useT(); + return ( +
+ {canChat && ( + + )} + {phone ? ( + + + {t('ord.call')} + + ) : ( + + + {t('ord.call')} + + )} +
+ ); +} + // ───── диалоги ───── function RescheduleDialog({ order: d, onClose, onDone }: { order: OrderData; onClose: () => void; onDone: () => void }) {