feat: refine CRM workspace and responsive hierarchy
This commit is contained in:
parent
24ef652275
commit
c4e8add07b
4 changed files with 87 additions and 103 deletions
|
|
@ -103,7 +103,9 @@ export const AppShell: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Command Panel (Only on full Desktop, not on communications which has its own full right card) */}
|
{/* Right Command Panel (Only on full Desktop, not on communications which has its own full right card) */}
|
||||||
{!isEffectiveMobile && !isEffectiveTablet && !isCommunicationsPage && <RightCommandPanel />}
|
{!isEffectiveMobile && !isEffectiveTablet && width >= 1536 && !isCommunicationsPage && (
|
||||||
|
<RightCommandPanel />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Bottom Navigation (Always present on mobile) */}
|
{/* Mobile Bottom Navigation (Always present on mobile) */}
|
||||||
|
|
|
||||||
|
|
@ -255,16 +255,12 @@ describe('Configuration Engine & CRM Advanced Interactions', () => {
|
||||||
expect(deal.id).toBeDefined();
|
expect(deal.id).toBeDefined();
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<MemoryRouter initialEntries={['/crm']}>
|
<MemoryRouter initialEntries={['/crm/deals']}>
|
||||||
<CrmModule />
|
<CrmModule />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Switch to Deals Kanban tab
|
// 2. Verify stage columns are rendered from the route-owned workspace.
|
||||||
const dealsTab = screen.getByRole('button', { name: /Воронка сделок/i });
|
|
||||||
fireEvent.click(dealsTab);
|
|
||||||
|
|
||||||
// 3. Verify stage columns are rendered
|
|
||||||
expect(screen.getByText('Квалификация')).toBeInTheDocument();
|
expect(screen.getByText('Квалификация')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Выиграна')).toBeInTheDocument();
|
expect(screen.getByText('Выиграна')).toBeInTheDocument();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { ulid } from 'ulidx';
|
import { ulid } from 'ulidx';
|
||||||
import { useCrm } from '../../services/hooks/useCrm';
|
import { useCrm } from '../../services/hooks/useCrm';
|
||||||
import { useOrders } from '../../services/hooks/useOrders';
|
import { useOrders } from '../../services/hooks/useOrders';
|
||||||
|
|
@ -10,7 +10,7 @@ import { Deal, DealStage } from '../../entities/crm/deal';
|
||||||
import { Lead } from '../../entities/crm/lead';
|
import { Lead } from '../../entities/crm/lead';
|
||||||
import { OrderItem } from '../../entities/commerce/order';
|
import { OrderItem } from '../../entities/commerce/order';
|
||||||
import { MoneyAmount } from '../../entities/types';
|
import { MoneyAmount } from '../../entities/types';
|
||||||
import { MaturityBadge } from '../../shared/ui/MaturityBadge';
|
import { Button } from '../../shared/ui/Button';
|
||||||
import { useToast } from '../../shared/ui/useToast';
|
import { useToast } from '../../shared/ui/useToast';
|
||||||
import { CsvImportWizard } from './CsvImportWizard';
|
import { CsvImportWizard } from './CsvImportWizard';
|
||||||
import { DEAL_STAGES } from './constants';
|
import { DEAL_STAGES } from './constants';
|
||||||
|
|
@ -26,6 +26,7 @@ import { UploadCloud } from 'lucide-react';
|
||||||
|
|
||||||
export const CrmModule: React.FC = () => {
|
export const CrmModule: React.FC = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -50,14 +51,12 @@ export const CrmModule: React.FC = () => {
|
||||||
const dealMeta = useMetadata('deal');
|
const dealMeta = useMetadata('deal');
|
||||||
const leadMeta = useMetadata('lead');
|
const leadMeta = useMetadata('lead');
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<'deals' | 'contacts' | 'companies' | 'leads' | 'import'>(() => {
|
const activeTab: 'deals' | 'contacts' | 'companies' | 'leads' | 'import' =
|
||||||
const p = location.pathname;
|
location.pathname.includes('/crm/deals') ? 'deals'
|
||||||
if (p.includes('/crm/deals')) return 'deals';
|
: location.pathname.includes('/crm/leads') ? 'leads'
|
||||||
if (p.includes('/crm/leads')) return 'leads';
|
: location.pathname.includes('/crm/companies') ? 'companies'
|
||||||
if (p.includes('/crm/companies')) return 'companies';
|
: location.pathname.includes('/crm/import') ? 'import'
|
||||||
if (p.includes('/crm/import')) return 'import';
|
: 'contacts';
|
||||||
return 'contacts';
|
|
||||||
});
|
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
// Modals state
|
// Modals state
|
||||||
|
|
@ -76,16 +75,6 @@ export const CrmModule: React.FC = () => {
|
||||||
const [selectedContact, setSelectedContact] = useState<Contact | null>(null);
|
const [selectedContact, setSelectedContact] = useState<Contact | null>(null);
|
||||||
const [selectedCompany, setSelectedCompany] = useState<Company | null>(null);
|
const [selectedCompany, setSelectedCompany] = useState<Company | null>(null);
|
||||||
|
|
||||||
// Synchronize active tab with URL path if navigated externally
|
|
||||||
const p = location.pathname;
|
|
||||||
if (p !== '/crm' && p !== '/crm/') {
|
|
||||||
if (p.includes('/crm/deals') && activeTab !== 'deals') setActiveTab('deals');
|
|
||||||
else if (p.includes('/crm/leads') && activeTab !== 'leads') setActiveTab('leads');
|
|
||||||
else if (p.includes('/crm/companies') && activeTab !== 'companies') setActiveTab('companies');
|
|
||||||
else if (p.includes('/crm/import') && activeTab !== 'import') setActiveTab('import');
|
|
||||||
else if (p.includes('/crm/contacts') || p === '/crm') setActiveTab('contacts');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal stage transition handler
|
// Deal stage transition handler
|
||||||
const handleStageChange = async (dealId: string, nextStage: DealStage) => {
|
const handleStageChange = async (dealId: string, nextStage: DealStage) => {
|
||||||
const deal = deals.find((d) => d.id === dealId);
|
const deal = deals.find((d) => d.id === dealId);
|
||||||
|
|
@ -210,54 +199,51 @@ export const CrmModule: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const navTabs = [
|
const pageMeta = {
|
||||||
{ id: 'deals', label: `Воронка сделок (${deals.length})` },
|
deals: {
|
||||||
{ id: 'contacts', label: `Контакты (${contacts.length})` },
|
title: 'Воронка сделок',
|
||||||
{ id: 'companies', label: `Компании (${companies.length})` },
|
description: `${deals.length} сделок — от первого контакта до заказа`,
|
||||||
{ id: 'leads', label: `Лиды (${leads.length})` },
|
},
|
||||||
] as const;
|
contacts: {
|
||||||
|
title: 'Клиенты и контакты',
|
||||||
|
description: `${contacts.length} контактов в единой клиентской базе`,
|
||||||
|
},
|
||||||
|
companies: {
|
||||||
|
title: 'Компании',
|
||||||
|
description: `${companies.length} организаций, поставщиков и партнёров`,
|
||||||
|
},
|
||||||
|
leads: {
|
||||||
|
title: 'Лиды',
|
||||||
|
description: `${leads.length} обращений ожидают обработки`,
|
||||||
|
},
|
||||||
|
import: {
|
||||||
|
title: 'Импорт данных',
|
||||||
|
description: 'Загрузка клиентской базы из CSV с предварительной проверкой',
|
||||||
|
},
|
||||||
|
}[activeTab];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 animate-fade-in">
|
<div className="space-y-5 animate-fade-in max-w-[1600px] mx-auto">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 bg-krona-surface p-3.5 rounded-xl border border-krona-border shadow-krona-soft">
|
<header className="flex items-start sm:items-center justify-between gap-4 border-b border-krona-border pb-4">
|
||||||
<div>
|
<div className="min-w-0">
|
||||||
<div className="flex items-center gap-2">
|
<h1 className="text-xl sm:text-2xl font-semibold tracking-[-0.02em] text-krona-primary">
|
||||||
<h1 className="text-sm font-bold text-krona-primary">
|
{pageMeta.title}
|
||||||
CRM: Клиенты, Воронка продаж & Сделки
|
</h1>
|
||||||
</h1>
|
<p className="text-sm text-krona-secondary mt-1">{pageMeta.description}</p>
|
||||||
<MaturityBadge level="pilot1" size="sm" />
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-krona-secondary mt-0.5">
|
|
||||||
Сквозной процесс: Лид → Контакт/Компания → Сделка (Kanban) → Выиграна → Заказ в Коммерции
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
{activeTab !== 'import' && (
|
||||||
<div className="flex items-center gap-1.5 overflow-x-auto text-xs">
|
<Button
|
||||||
{navTabs.map((t) => (
|
variant="secondary"
|
||||||
<button
|
size="sm"
|
||||||
key={t.id}
|
aria-label="Импорт CSV"
|
||||||
onClick={() => setActiveTab(t.id)}
|
onClick={() => navigate('/crm/import')}
|
||||||
className={`px-3 py-1.5 rounded-lg font-medium transition-all ${
|
|
||||||
activeTab === t.id
|
|
||||||
? 'bg-krona-green text-white shadow-sm'
|
|
||||||
: 'bg-krona-workspace text-krona-secondary hover:text-krona-primary border border-krona-border'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{t.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveTab('import')}
|
|
||||||
className={`px-3 py-1.5 rounded-lg font-medium transition-all flex items-center gap-1 ${
|
|
||||||
activeTab === 'import'
|
|
||||||
? 'bg-krona-green text-white shadow-sm'
|
|
||||||
: 'bg-krona-workspace text-krona-secondary hover:text-krona-primary border border-krona-border'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<UploadCloud className="w-3.5 h-3.5" /> Импорт CSV
|
<UploadCloud className="w-4 h-4 mr-1.5" />
|
||||||
</button>
|
<span className="hidden sm:inline">Импорт CSV</span>
|
||||||
</div>
|
<span className="sm:hidden">Импорт</span>
|
||||||
</div>
|
</Button>
|
||||||
|
)}
|
||||||
|
</header>
|
||||||
|
|
||||||
{activeTab === 'deals' && (
|
{activeTab === 'deals' && (
|
||||||
<CrmKanbanView
|
<CrmKanbanView
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
if (activeTab === 'contacts') {
|
if (activeTab === 'contacts') {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||||
<div className="relative w-64">
|
<div className="relative w-full sm:w-80">
|
||||||
<Search className="w-3.5 h-3.5 absolute left-2.5 top-2.5 text-krona-secondary" />
|
<Search className="w-3.5 h-3.5 absolute left-2.5 top-2.5 text-krona-secondary" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -55,15 +55,15 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-krona-surface border border-krona-border rounded-xl shadow-krona-soft overflow-hidden">
|
<div className="bg-krona-surface border border-krona-border rounded-xl shadow-krona-soft overflow-x-auto">
|
||||||
<table className="w-full text-xs text-left">
|
<table className="w-full text-xs text-left table-fixed md:table-auto">
|
||||||
<thead className="bg-krona-workspace text-krona-secondary border-b border-krona-border">
|
<thead className="bg-krona-workspace text-krona-secondary border-b border-krona-border">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="p-3 font-semibold">ФИО / Контакт</th>
|
<th className="p-3 font-semibold">ФИО / Контакт</th>
|
||||||
<th className="p-3 font-semibold">Компания</th>
|
<th className="p-3 font-semibold hidden lg:table-cell">Компания</th>
|
||||||
<th className="p-3 font-semibold">Телефон</th>
|
<th className="p-3 font-semibold">Телефон</th>
|
||||||
<th className="p-3 font-semibold">Email</th>
|
<th className="p-3 font-semibold hidden xl:table-cell">Email</th>
|
||||||
<th className="p-3 font-semibold">Роли</th>
|
<th className="p-3 font-semibold hidden xl:table-cell">Роли</th>
|
||||||
<th className="p-3 font-semibold text-right">Действие</th>
|
<th className="p-3 font-semibold text-right">Действие</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -97,10 +97,10 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-3 text-krona-secondary">{comp?.name || '—'}</td>
|
<td className="p-3 text-krona-secondary hidden lg:table-cell">{comp?.name || '—'}</td>
|
||||||
<td className="p-3 font-mono text-krona-secondary">{contact.phone || '—'}</td>
|
<td className="p-3 font-mono text-krona-secondary break-words">{contact.phone || '—'}</td>
|
||||||
<td className="p-3 text-krona-secondary">{contact.email || '—'}</td>
|
<td className="p-3 text-krona-secondary hidden xl:table-cell">{contact.email || '—'}</td>
|
||||||
<td className="p-3">
|
<td className="p-3 hidden xl:table-cell">
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
{contact.roles?.map((r) => (
|
{contact.roles?.map((r) => (
|
||||||
<Badge key={r} variant="gold" size="sm">
|
<Badge key={r} variant="gold" size="sm">
|
||||||
|
|
@ -118,7 +118,7 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
onSelectContact(contact);
|
onSelectContact(contact);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Карточка 360 <ChevronRight className="w-3.5 h-3.5 ml-1" />
|
<span className="hidden md:inline">Карточка</span><ChevronRight className="w-3.5 h-3.5 md:ml-1" />
|
||||||
</Button>
|
</Button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -134,8 +134,8 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
if (activeTab === 'companies') {
|
if (activeTab === 'companies') {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||||
<div className="relative w-64">
|
<div className="relative w-full sm:w-80">
|
||||||
<Search className="w-3.5 h-3.5 absolute left-2.5 top-2.5 text-krona-secondary" />
|
<Search className="w-3.5 h-3.5 absolute left-2.5 top-2.5 text-krona-secondary" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -150,15 +150,15 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-krona-surface border border-krona-border rounded-xl shadow-krona-soft overflow-hidden">
|
<div className="bg-krona-surface border border-krona-border rounded-xl shadow-krona-soft overflow-x-auto">
|
||||||
<table className="w-full text-xs text-left">
|
<table className="w-full text-xs text-left table-fixed md:table-auto">
|
||||||
<thead className="bg-krona-workspace text-krona-secondary border-b border-krona-border">
|
<thead className="bg-krona-workspace text-krona-secondary border-b border-krona-border">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="p-3 font-semibold">Название компании</th>
|
<th className="p-3 font-semibold">Название компании</th>
|
||||||
<th className="p-3 font-semibold">ИНН</th>
|
<th className="p-3 font-semibold hidden md:table-cell">ИНН</th>
|
||||||
<th className="p-3 font-semibold">Город</th>
|
<th className="p-3 font-semibold hidden xl:table-cell">Город</th>
|
||||||
<th className="p-3 font-semibold">Телефон</th>
|
<th className="p-3 font-semibold">Телефон</th>
|
||||||
<th className="p-3 font-semibold">Роли</th>
|
<th className="p-3 font-semibold hidden xl:table-cell">Роли</th>
|
||||||
<th className="p-3 font-semibold text-right">Действие</th>
|
<th className="p-3 font-semibold text-right">Действие</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -187,10 +187,10 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-3 font-mono text-krona-secondary">{company.inn || '—'}</td>
|
<td className="p-3 font-mono text-krona-secondary hidden md:table-cell">{company.inn || '—'}</td>
|
||||||
<td className="p-3 text-krona-secondary">{company.address || '—'}</td>
|
<td className="p-3 text-krona-secondary hidden xl:table-cell">{company.address || '—'}</td>
|
||||||
<td className="p-3 font-mono text-krona-secondary">{company.phone || '—'}</td>
|
<td className="p-3 font-mono text-krona-secondary">{company.phone || '—'}</td>
|
||||||
<td className="p-3">
|
<td className="p-3 hidden xl:table-cell">
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
{company.roles?.map((r) => (
|
{company.roles?.map((r) => (
|
||||||
<Badge key={r} variant="gold" size="sm">
|
<Badge key={r} variant="gold" size="sm">
|
||||||
|
|
@ -208,7 +208,7 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
onSelectCompany(company);
|
onSelectCompany(company);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Карточка 360 <ChevronRight className="w-3.5 h-3.5 ml-1" />
|
<span className="hidden md:inline">Карточка</span><ChevronRight className="w-3.5 h-3.5 md:ml-1" />
|
||||||
</Button>
|
</Button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -223,8 +223,8 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
if (activeTab === 'leads') {
|
if (activeTab === 'leads') {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||||
<div className="relative w-64">
|
<div className="relative w-full sm:w-80">
|
||||||
<Search className="w-3.5 h-3.5 absolute left-2.5 top-2.5 text-krona-secondary" />
|
<Search className="w-3.5 h-3.5 absolute left-2.5 top-2.5 text-krona-secondary" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -244,13 +244,13 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-krona-surface border border-krona-border rounded-xl shadow-krona-soft overflow-hidden">
|
<div className="bg-krona-surface border border-krona-border rounded-xl shadow-krona-soft overflow-x-auto">
|
||||||
<table className="w-full text-xs text-left">
|
<table className="w-full text-xs text-left table-fixed md:table-auto">
|
||||||
<thead className="bg-krona-workspace text-krona-secondary border-b border-krona-border">
|
<thead className="bg-krona-workspace text-krona-secondary border-b border-krona-border">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="p-3 font-semibold">Название лида</th>
|
<th className="p-3 font-semibold">Название лида</th>
|
||||||
<th className="p-3 font-semibold">Контактное лицо</th>
|
<th className="p-3 font-semibold hidden md:table-cell">Контактное лицо</th>
|
||||||
<th className="p-3 font-semibold">Контакты</th>
|
<th className="p-3 font-semibold hidden lg:table-cell">Контакты</th>
|
||||||
<th className="p-3 font-semibold">Оценка бюджета</th>
|
<th className="p-3 font-semibold">Оценка бюджета</th>
|
||||||
<th className="p-3 font-semibold">Статус</th>
|
<th className="p-3 font-semibold">Статус</th>
|
||||||
<th className="p-3 font-semibold text-right">Конвертация</th>
|
<th className="p-3 font-semibold text-right">Конвертация</th>
|
||||||
|
|
@ -268,8 +268,8 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
.map((lead) => (
|
.map((lead) => (
|
||||||
<tr key={lead.id} className="hover:bg-krona-workspace/50 transition-colors">
|
<tr key={lead.id} className="hover:bg-krona-workspace/50 transition-colors">
|
||||||
<td className="p-3 font-bold text-krona-primary">{lead.title}</td>
|
<td className="p-3 font-bold text-krona-primary">{lead.title}</td>
|
||||||
<td className="p-3 font-medium text-krona-primary">{lead.contactName}</td>
|
<td className="p-3 font-medium text-krona-primary hidden md:table-cell">{lead.contactName}</td>
|
||||||
<td className="p-3 text-krona-secondary">
|
<td className="p-3 text-krona-secondary hidden lg:table-cell">
|
||||||
<div>{lead.phone}</div>
|
<div>{lead.phone}</div>
|
||||||
<div className="text-[11px]">{lead.email}</div>
|
<div className="text-[11px]">{lead.email}</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -302,7 +302,7 @@ export const CrmTableView: React.FC<CrmTableViewProps> = ({
|
||||||
data-testid="convert-lead-btn"
|
data-testid="convert-lead-btn"
|
||||||
onClick={() => onConvertLead(lead)}
|
onClick={() => onConvertLead(lead)}
|
||||||
>
|
>
|
||||||
<Sparkles className="w-3.5 h-3.5 mr-1" /> Конвертировать в сделку
|
<Sparkles className="w-3.5 h-3.5 md:mr-1" /> <span className="hidden md:inline">В сделку</span>
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-emerald-600 dark:text-emerald-400 text-xs font-semibold flex items-center justify-end gap-1">
|
<span className="text-emerald-600 dark:text-emerald-400 text-xs font-semibold flex items-center justify-end gap-1">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue