Order card: 'Message provider' and 'Call' buttons, also for a pending offer
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e7de4f5893
commit
a848bb13e2
5 files changed
+49
-7
No files matched your search
@@ -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 ?? '',
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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}%',
|
||||
|
||||
@@ -352,6 +352,12 @@
|
||||
}
|
||||
|
||||
/* подбор исполнителя */
|
||||
.bo-contact {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.bo-pendingbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -419,6 +419,7 @@ function ProviderPanel({ order: d, onChanged, canChat }: { order: OrderData; onC
|
||||
</div>
|
||||
|
||||
{d.provider ? (
|
||||
<>
|
||||
<div className="bo-person">
|
||||
<Avatar name={d.provider.name} id={d.provider.id} size={40} />
|
||||
<a href={`#/providers/${d.provider.id}`} style={{ minWidth: 0, color: 'inherit', textDecoration: 'none' }}>
|
||||
@@ -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(' · ') || '—'}
|
||||
</span>
|
||||
</a>
|
||||
{canChat && (
|
||||
<button type="button" className="bo-iconbtn" title={t('ord.writeProvider')} aria-label={t('ord.writeProvider')} onClick={() => chat.mutate({ providerId: d.provider!.id })}>
|
||||
<I name="chats" size={20} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<ContactRow providerId={d.provider.id} phone={d.provider.phone} canChat={canChat} onChat={(id) => chat.mutate({ providerId: id })} busy={chat.isPending} />
|
||||
</>
|
||||
) : d.pending ? (
|
||||
<>
|
||||
<div className="bo-pendingbox">
|
||||
{d.pending.auto ? <I name="bot" size={20} /> : <I name="send" size={20} />}
|
||||
<span className="bo-grow">
|
||||
@@ -443,6 +442,8 @@ function ProviderPanel({ order: d, onChanged, canChat }: { order: OrderData; onC
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
{d.pending.providerId && <ContactRow providerId={d.pending.providerId} phone={d.pending.phone} canChat={canChat} onChat={(id) => chat.mutate({ providerId: id })} busy={chat.isPending} />}
|
||||
</>
|
||||
) : d.canAssign ? (
|
||||
<>
|
||||
<div className="bo-assignbar">
|
||||
@@ -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 (
|
||||
<div className="bo-contact">
|
||||
{canChat && (
|
||||
<button type="button" className="bo-btn bo-btn--sm" onClick={() => onChat(providerId)} disabled={busy}>
|
||||
<I name="chats" size={16} />
|
||||
{t('ord.writeProvider')}
|
||||
</button>
|
||||
)}
|
||||
{phone ? (
|
||||
<a className="bo-btn bo-btn--ghost bo-btn--sm" href={`tel:${phone.replace(/[^\d+]/g, '')}`}>
|
||||
<I name="phone" size={16} />
|
||||
{t('ord.call')}
|
||||
</a>
|
||||
) : (
|
||||
<span className="bo-btn bo-btn--ghost bo-btn--sm" aria-disabled="true" title={t('ord.noPhone')} style={{ opacity: 0.45, cursor: 'default' }}>
|
||||
<I name="phone" size={16} />
|
||||
{t('ord.call')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ───── диалоги ─────
|
||||
|
||||
function RescheduleDialog({ order: d, onClose, onDone }: { order: OrderData; onClose: () => void; onDone: () => void }) {
|
||||
|
||||
Reference in new issue
Block a user