722 lines
30 KiB
TypeScript
722 lines
30 KiB
TypeScript
import React, { useState } from 'react';
|
||
import { useDemoStore } from '../../mock/store';
|
||
import { StatCard, Button, Badge, MaturityBadge } from '../../shared/ui';
|
||
import { formatCurrency } from '../../shared/utils/formatters';
|
||
import {
|
||
TrendingUp,
|
||
AlertTriangle,
|
||
AlertCircle,
|
||
Clock,
|
||
CheckCircle2,
|
||
ArrowRight,
|
||
Utensils,
|
||
Car,
|
||
ShoppingBag,
|
||
Users,
|
||
Truck,
|
||
DollarSign,
|
||
Package,
|
||
Layers,
|
||
} from 'lucide-react';
|
||
import {
|
||
AreaChart,
|
||
Area,
|
||
XAxis,
|
||
YAxis,
|
||
CartesianGrid,
|
||
Tooltip,
|
||
ResponsiveContainer,
|
||
} from 'recharts';
|
||
import { useNavigate } from 'react-router-dom';
|
||
|
||
const REVENUE_DATA_TODAY = [
|
||
{ time: '10:00', revenue: 14200, orders: 4 },
|
||
{ time: '12:00', revenue: 38400, orders: 12 },
|
||
{ time: '14:00', revenue: 76000, orders: 24 },
|
||
{ time: '16:00', revenue: 104500, orders: 32 },
|
||
{ time: '18:00', revenue: 149000, orders: 45 },
|
||
{ time: '20:00', revenue: 184500, orders: 58 },
|
||
];
|
||
|
||
const REVENUE_DATA_WEEK = [
|
||
{ time: 'Пн', revenue: 142000, orders: 48 },
|
||
{ time: 'Вт', revenue: 156000, orders: 52 },
|
||
{ time: 'Ср', revenue: 168000, orders: 56 },
|
||
{ time: 'Чт', revenue: 174000, orders: 59 },
|
||
{ time: 'Пт', revenue: 245000, orders: 84 },
|
||
{ time: 'Сб', revenue: 290000, orders: 98 },
|
||
{ time: 'Вс', revenue: 260000, orders: 89 },
|
||
];
|
||
|
||
export const DynamicDashboard: React.FC = () => {
|
||
const {
|
||
getActiveProfile,
|
||
getActiveCapabilities,
|
||
activeLocation,
|
||
setIsQuickActionsOpen,
|
||
kdsTickets,
|
||
deliveries,
|
||
workOrders,
|
||
} = useDemoStore();
|
||
|
||
const profile = getActiveProfile();
|
||
const capabilities = getActiveCapabilities();
|
||
const navigate = useNavigate();
|
||
|
||
const [chartPeriod, setChartPeriod] = useState<'today' | 'week'>('today');
|
||
|
||
// Dynamic KPIs based on Profile
|
||
const renderKpis = () => {
|
||
switch (profile.id) {
|
||
case 'car_wash':
|
||
return (
|
||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3.5">
|
||
<StatCard
|
||
title="Машин в работе"
|
||
value={`${workOrders.filter((w) => w.status === 'in_progress').length || 4} авто`}
|
||
change={{ value: '+2', trend: 'up' }}
|
||
subtitle="2 бокса на въезде"
|
||
icon={<Car className="w-4 h-4 text-krona-info" />}
|
||
/>
|
||
<StatCard
|
||
title="Загрузка боксов"
|
||
value="83%"
|
||
change={{ value: '+5%', trend: 'up' }}
|
||
subtitle="5 из 6 боксов заняты"
|
||
icon={<Layers className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Средний чек мойки"
|
||
value={formatCurrency(1850)}
|
||
change={{ value: '+12%', trend: 'up' }}
|
||
subtitle="Комплекс + воск"
|
||
icon={<TrendingUp className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Время на пост"
|
||
value="28 минут"
|
||
change={{ value: '-3 мин', trend: 'up' }}
|
||
subtitle="Норматив: 30 мин"
|
||
icon={<Clock className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
case 'auto_service':
|
||
return (
|
||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3.5">
|
||
<StatCard
|
||
title="Авто на постах"
|
||
value={`${workOrders.length} авто`}
|
||
change={{ value: '+3', trend: 'up' }}
|
||
subtitle="В работе: 3 • Ожидают: 1"
|
||
icon={<Car className="w-4 h-4 text-krona-info" />}
|
||
/>
|
||
<StatCard
|
||
title="Подъемники"
|
||
value="4 из 4 заняты"
|
||
change={{ value: '100%', trend: 'up' }}
|
||
subtitle="Очередь: 2 авто"
|
||
icon={<Layers className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Нормо-часы"
|
||
value="42.5 н-ч"
|
||
change={{ value: '+6.5', trend: 'up' }}
|
||
subtitle="Ставка: 2 100 ₽/н-ч"
|
||
icon={<Clock className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Запчасти в нарядах"
|
||
value={formatCurrency(186400)}
|
||
change={{ value: '+18%', trend: 'up' }}
|
||
subtitle="На складе: 94% позиций"
|
||
icon={<DollarSign className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
case 'restaurant_delivery_only':
|
||
return (
|
||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3.5">
|
||
<StatCard
|
||
title="Выручка доставки"
|
||
value={formatCurrency(142800)}
|
||
change={{ value: '+22%', trend: 'up' }}
|
||
subtitle="План: 130 000 ₽"
|
||
icon={<DollarSign className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Курьеров на линии"
|
||
value={`${deliveries.filter((d) => d.status === 'in_transit').length + 3} курьера`}
|
||
change={{ value: '+2', trend: 'up' }}
|
||
subtitle="3 на авто, 2 самокат"
|
||
icon={<Truck className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Среднее время доставки"
|
||
value="34 мин"
|
||
change={{ value: '-4 мин', trend: 'up' }}
|
||
subtitle="Готовка 14 мин + путь 20 мин"
|
||
icon={<Clock className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Онлайн-чеков"
|
||
value={`${deliveries.length + 38} заказов`}
|
||
change={{ value: '+14', trend: 'up' }}
|
||
subtitle="Сайт: 60% • Бот: 40%"
|
||
icon={<ShoppingBag className="w-4 h-4 text-krona-info" />}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
case 'fitness':
|
||
return (
|
||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3.5">
|
||
<StatCard
|
||
title="Активных абонементов"
|
||
value="840 карт"
|
||
change={{ value: '+32', trend: 'up' }}
|
||
subtitle="Продлений в мес: 78%"
|
||
icon={<Users className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Загрузка залов"
|
||
value="72%"
|
||
change={{ value: '+8%', trend: 'up' }}
|
||
subtitle="Пик: 18:00 - 21:00"
|
||
icon={<Layers className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Тренировок сегодня"
|
||
value="18 занятий"
|
||
subtitle="Персональных: 12"
|
||
icon={<Clock className="w-4 h-4 text-krona-info" />}
|
||
/>
|
||
<StatCard
|
||
title="Выручка сегодня"
|
||
value={formatCurrency(92000)}
|
||
change={{ value: '+15%', trend: 'up' }}
|
||
subtitle="Карты + спортбар"
|
||
icon={<DollarSign className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
case 'clinic':
|
||
return (
|
||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3.5">
|
||
<StatCard
|
||
title="Приёмов сегодня"
|
||
value="36 пациентов"
|
||
change={{ value: '+4', trend: 'up' }}
|
||
subtitle="Подтверждено: 34"
|
||
icon={<Users className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Занятость кабинетов"
|
||
value="90%"
|
||
change={{ value: '+6%', trend: 'up' }}
|
||
subtitle="9 из 10 кабинетов"
|
||
icon={<Layers className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Средний чек приема"
|
||
value={formatCurrency(3400)}
|
||
change={{ value: '+300 ₽', trend: 'up' }}
|
||
subtitle="Первичный + анализы"
|
||
icon={<TrendingUp className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Выручка сегодня"
|
||
value={formatCurrency(122400)}
|
||
change={{ value: '+11%', trend: 'up' }}
|
||
subtitle="Оплата картами: 82%"
|
||
icon={<DollarSign className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
case 'coworking':
|
||
return (
|
||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3.5">
|
||
<StatCard
|
||
title="Занятость рабочих мест"
|
||
value="88% (44 из 50)"
|
||
change={{ value: '+6%', trend: 'up' }}
|
||
subtitle="Свободно 6 мест"
|
||
icon={<Layers className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Брони переговорных"
|
||
value="9 броней"
|
||
change={{ value: '+2', trend: 'up' }}
|
||
subtitle="Зал А, Б и Скайп-румы"
|
||
icon={<Clock className="w-4 h-4 text-krona-info" />}
|
||
/>
|
||
<StatCard
|
||
title="Активных резидентов"
|
||
value="64 чел"
|
||
subtitle="Командных офисов: 6"
|
||
icon={<Users className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Выручка сегодня"
|
||
value={formatCurrency(45000)}
|
||
change={{ value: '+8%', trend: 'up' }}
|
||
subtitle="Дневные пропуски + кофе"
|
||
icon={<DollarSign className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
case 'logistics':
|
||
return (
|
||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3.5">
|
||
<StatCard
|
||
title="Заказов в пути"
|
||
value="18 отправлений"
|
||
change={{ value: '+5', trend: 'up' }}
|
||
subtitle="В доставке прямо сейчас"
|
||
icon={<Truck className="w-4 h-4 text-krona-info" />}
|
||
/>
|
||
<StatCard
|
||
title="Доставлено вовремя %"
|
||
value="97.8%"
|
||
change={{ value: '+0.5%', trend: 'up' }}
|
||
subtitle="SLA: 95%"
|
||
icon={<CheckCircle2 className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Курьеров на маршрутах"
|
||
value="8 курьеров"
|
||
subtitle="Авто: 5, Мото: 3"
|
||
icon={<Users className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Среднее время рейса"
|
||
value="26 минут"
|
||
change={{ value: '-2 мин', trend: 'up' }}
|
||
subtitle="Городской экспресс"
|
||
icon={<Clock className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
case 'retail':
|
||
return (
|
||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3.5">
|
||
<StatCard
|
||
title="Выручка магазина"
|
||
value={formatCurrency(248000)}
|
||
change={{ value: '+19%', trend: 'up' }}
|
||
subtitle="Чеков сегодня: 142"
|
||
icon={<ShoppingBag className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Средний чек"
|
||
value={formatCurrency(1746)}
|
||
change={{ value: '+4%', trend: 'up' }}
|
||
subtitle="Товаров в чеке: 3.2"
|
||
icon={<TrendingUp className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Товарных позиций на складе"
|
||
value="1 420 SKU"
|
||
subtitle="Критический остаток: 4 SKU"
|
||
icon={<Package className="w-4 h-4 text-krona-info" />}
|
||
/>
|
||
<StatCard
|
||
title="Оборачиваемость склада"
|
||
value="14.2 дня"
|
||
change={{ value: '-1.5 дн', trend: 'up' }}
|
||
subtitle="Маржинальность: 42.4%"
|
||
icon={<DollarSign className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
case 'beauty_services':
|
||
return (
|
||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3.5">
|
||
<StatCard
|
||
title="Выручка салона"
|
||
value={formatCurrency(124000)}
|
||
change={{ value: '+12%', trend: 'up' }}
|
||
subtitle="18 визитов сегодня"
|
||
icon={<DollarSign className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Загрузка мастеров"
|
||
value="86%"
|
||
change={{ value: '+5%', trend: 'up' }}
|
||
subtitle="5 мастеров на смене"
|
||
icon={<Users className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Записей на сегодня"
|
||
value="22 клиента"
|
||
subtitle="Подтверждено: 19"
|
||
icon={<Clock className="w-4 h-4 text-krona-info" />}
|
||
/>
|
||
<StatCard
|
||
title="Средний чек визита"
|
||
value={formatCurrency(4850)}
|
||
change={{ value: '+350 ₽', trend: 'up' }}
|
||
subtitle="Услуги + уход дома"
|
||
icon={<TrendingUp className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
case 'wholesale_b2b':
|
||
return (
|
||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3.5">
|
||
<StatCard
|
||
title="Объем отгрузок сегодня"
|
||
value={formatCurrency(1450000)}
|
||
change={{ value: '+24%', trend: 'up' }}
|
||
subtitle="9 партий сформировано"
|
||
icon={<DollarSign className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Активные B2B контракты"
|
||
value="42 контрагента"
|
||
subtitle="В согласовании: 3"
|
||
icon={<Users className="w-4 h-4 text-krona-info" />}
|
||
/>
|
||
<StatCard
|
||
title="Дебиторская задолженность"
|
||
value={formatCurrency(840000)}
|
||
change={{ value: '-8%', trend: 'up' }}
|
||
subtitle="Просрочено: 95 000 ₽"
|
||
icon={<TrendingUp className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Загрузка распредцентра"
|
||
value="84%"
|
||
subtitle="Свободно 320 паллетомест"
|
||
icon={<Package className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
case 'restaurant_hall_delivery':
|
||
default:
|
||
return (
|
||
<div className="grid grid-cols-2 lg:grid-cols-5 gap-3.5">
|
||
<StatCard
|
||
title="Выручка сегодня"
|
||
value={formatCurrency(184500)}
|
||
change={{ value: '+14%', trend: 'up' }}
|
||
subtitle="План: 170 000 ₽ (108%)"
|
||
icon={<DollarSign className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Заказы"
|
||
value="58 чеков"
|
||
change={{ value: '+8', trend: 'up' }}
|
||
subtitle="Зал: 34 • Доставка: 24"
|
||
icon={<Utensils className="w-4 h-4 text-krona-gold" />}
|
||
/>
|
||
<StatCard
|
||
title="Средний чек"
|
||
value={formatCurrency(3180)}
|
||
change={{ value: '+6.2%', trend: 'up' }}
|
||
subtitle="Зал: 3 650 ₽ • Онлайн: 2 450 ₽"
|
||
icon={<TrendingUp className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
<StatCard
|
||
title="Загрузка зала"
|
||
value="82%"
|
||
change={{ value: '+12%', trend: 'up' }}
|
||
subtitle="11 из 14 столов заняты"
|
||
icon={<Users className="w-4 h-4 text-krona-info" />}
|
||
/>
|
||
<StatCard
|
||
title="Food Cost / Маржа"
|
||
value="29.4%"
|
||
change={{ value: '-0.8%', trend: 'up' }}
|
||
subtitle="Маржинальность: 70.6%"
|
||
icon={<Package className="w-4 h-4 text-krona-green" />}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-5 animate-fade-in">
|
||
{/* Top Welcome Bar */}
|
||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 bg-krona-surface border border-krona-border rounded-xl p-4 shadow-krona-soft">
|
||
<div>
|
||
<div className="flex items-center gap-2">
|
||
<h1 className="text-lg font-bold text-krona-primary tracking-tight">
|
||
Операционный центр • {profile.companyName}
|
||
</h1>
|
||
<Badge variant="gold" size="sm">{profile.category}</Badge>
|
||
<MaturityBadge level="pilot1" size="sm" />
|
||
</div>
|
||
<p className="text-xs text-krona-secondary mt-1">
|
||
Локация: <span className="font-semibold text-krona-primary">{activeLocation}</span> • Контур реального времени синхронизирован
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-2">
|
||
<Button variant="outline" size="sm" onClick={() => navigate('/operations')}>
|
||
Открыть рабочий стол
|
||
</Button>
|
||
<Button variant="primary" size="sm" onClick={() => setIsQuickActionsOpen(true)}>
|
||
+ Быстрое действие
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 1. ЧТО ПРОИСХОДИТ? — KPI МЕТРИКИ */}
|
||
{renderKpis()}
|
||
|
||
{/* 2. БОЛЬШОЙ ГРАФИК ВЫРУЧКИ И ЧЕКОВ */}
|
||
<div className="bg-krona-surface border border-krona-border rounded-xl p-4 shadow-krona-soft">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<div>
|
||
<h3 className="text-sm font-semibold text-krona-primary">
|
||
Динамика выручки и среднего чека
|
||
</h3>
|
||
<p className="text-xs text-krona-secondary">
|
||
Почасовая разбивка с кассовых узлов и онлайн-заказов
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-1 bg-krona-workspace p-1 rounded-lg border border-krona-border text-xs">
|
||
<button
|
||
onClick={() => setChartPeriod('today')}
|
||
className={`px-3 py-1 rounded font-medium transition-colors ${
|
||
chartPeriod === 'today' ? 'bg-[#2F684C] text-white' : 'text-krona-secondary hover:text-krona-primary'
|
||
}`}
|
||
>
|
||
Сегодня
|
||
</button>
|
||
<button
|
||
onClick={() => setChartPeriod('week')}
|
||
className={`px-3 py-1 rounded font-medium transition-colors ${
|
||
chartPeriod === 'week' ? 'bg-[#2F684C] text-white' : 'text-krona-secondary hover:text-krona-primary'
|
||
}`}
|
||
>
|
||
Неделя
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="h-64 w-full">
|
||
<ResponsiveContainer width="100%" height="100%">
|
||
<AreaChart data={chartPeriod === 'today' ? REVENUE_DATA_TODAY : REVENUE_DATA_WEEK}>
|
||
<defs>
|
||
<linearGradient id="colorRev" x1="0" y1="0" x2="0" y2="1">
|
||
<stop offset="5%" stopColor="#2F684C" stopOpacity={0.4} />
|
||
<stop offset="95%" stopColor="#2F684C" stopOpacity={0.0} />
|
||
</linearGradient>
|
||
</defs>
|
||
<CartesianGrid strokeDasharray="3 3" stroke="#D8D5CB" opacity={0.4} />
|
||
<XAxis dataKey="time" stroke="#686C65" fontSize={11} />
|
||
<YAxis stroke="#686C65" fontSize={11} tickFormatter={(val) => `${val / 1000}k`} />
|
||
<Tooltip
|
||
formatter={(value: any) => [formatCurrency(Number(value)), 'Выручка']}
|
||
contentStyle={{
|
||
backgroundColor: '#F8F6F0',
|
||
borderColor: '#D8D5CB',
|
||
borderRadius: 8,
|
||
fontSize: 12,
|
||
}}
|
||
/>
|
||
<Area
|
||
type="monotone"
|
||
dataKey="revenue"
|
||
stroke="#2F684C"
|
||
strokeWidth={2.5}
|
||
fillOpacity={1}
|
||
fill="url(#colorRev)"
|
||
/>
|
||
</AreaChart>
|
||
</ResponsiveContainer>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 3. ЧТО ТРЕБУЕТ ВНИМАНИЯ? (Alerts with direct actions) */}
|
||
<div className="bg-krona-surface border border-krona-border rounded-xl p-4 shadow-krona-soft">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<div className="flex items-center gap-2">
|
||
<AlertTriangle className="w-4 h-4 text-krona-warning" />
|
||
<h3 className="text-sm font-semibold text-krona-primary">Требует внимания управляющего</h3>
|
||
</div>
|
||
<span className="text-xs text-krona-secondary">3 критических инцидента</span>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||
<div className="p-3 rounded-lg border border-krona-danger/40 bg-krona-danger/10 flex flex-col justify-between">
|
||
<div>
|
||
<div className="flex items-center gap-1.5 text-xs font-semibold text-krona-danger">
|
||
<AlertCircle className="w-4 h-4" />
|
||
<span>Критический остаток</span>
|
||
</div>
|
||
<p className="text-xs text-krona-primary mt-1.5 leading-relaxed">
|
||
Лосось охлажденный: осталось <strong>2.4 кг</strong> (порог 6 кг). Хватит на ~2.5 часа работы кухни.
|
||
</p>
|
||
</div>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="mt-3 bg-krona-surface text-xs justify-between"
|
||
onClick={() => navigate('/operations/inventory')}
|
||
>
|
||
<span>Заявка поставщику</span>
|
||
<ArrowRight className="w-3.5 h-3.5" />
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="p-3 rounded-lg border border-krona-warning/40 bg-krona-warning/10 flex flex-col justify-between">
|
||
<div>
|
||
<div className="flex items-center gap-1.5 text-xs font-semibold text-krona-warning">
|
||
<Clock className="w-4 h-4" />
|
||
<span>Задержка на кухне (KDS)</span>
|
||
</div>
|
||
<p className="text-xs text-krona-primary mt-1.5 leading-relaxed">
|
||
Заказ #104 на <strong>Стол 4</strong> находится в работе уже 18 минут при норме 12 минут.
|
||
</p>
|
||
</div>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="mt-3 bg-krona-surface text-xs justify-between"
|
||
onClick={() => navigate('/operations/kds')}
|
||
>
|
||
<span>Открыть KDS кухни</span>
|
||
<ArrowRight className="w-3.5 h-3.5" />
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="p-3 rounded-lg border border-krona-info/40 bg-krona-info/10 flex flex-col justify-between">
|
||
<div>
|
||
<div className="flex items-center gap-1.5 text-xs font-semibold text-krona-info">
|
||
<Truck className="w-4 h-4" />
|
||
<span>Наличные у курьера</span>
|
||
</div>
|
||
<p className="text-xs text-krona-primary mt-1.5 leading-relaxed">
|
||
Курьер Алексей Смирнов держит <strong>24 500 ₽</strong> наличными после 7 доставок.
|
||
</p>
|
||
</div>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="mt-3 bg-krona-surface text-xs justify-between"
|
||
onClick={() => navigate('/operations/delivery')}
|
||
>
|
||
<span>Принять инкассацию</span>
|
||
<ArrowRight className="w-3.5 h-3.5" />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 4. СОСТОЯНИЕ БИЗНЕСА СЕЙЧАС (Live Pulse Grid) */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3.5">
|
||
{capabilities.dining && (
|
||
<div className="bg-krona-surface border border-krona-border rounded-lg p-3.5 shadow-krona-soft">
|
||
<div className="flex items-center justify-between text-xs mb-2">
|
||
<span className="font-semibold text-krona-primary">Зал и столы</span>
|
||
<Badge variant="green" size="sm">82% занято</Badge>
|
||
</div>
|
||
<div className="text-xs text-krona-secondary space-y-1">
|
||
<div className="flex justify-between">
|
||
<span>Открытых чеков:</span>
|
||
<span className="font-medium text-krona-primary">11 столов</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span>Среднее время посадки:</span>
|
||
<span className="font-medium text-krona-primary">48 минут</span>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => navigate('/operations/dining')}
|
||
className="mt-2.5 text-xs text-krona-green font-medium hover:underline block"
|
||
>
|
||
Схема зала →
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{capabilities.kds && (
|
||
<div className="bg-krona-surface border border-krona-border rounded-lg p-3.5 shadow-krona-soft">
|
||
<div className="flex items-center justify-between text-xs mb-2">
|
||
<span className="font-semibold text-krona-primary">Кухня (KDS)</span>
|
||
<Badge variant="gold" size="sm">{kdsTickets.length} в работе</Badge>
|
||
</div>
|
||
<div className="text-xs text-krona-secondary space-y-1">
|
||
<div className="flex justify-between">
|
||
<span>Суши-цех:</span>
|
||
<span className="font-medium text-krona-primary">4 блюда</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span>Горячий цех:</span>
|
||
<span className="font-medium text-krona-primary">2 блюда</span>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => navigate('/operations/kds')}
|
||
className="mt-2.5 text-xs text-krona-green font-medium hover:underline block"
|
||
>
|
||
Кухонный экран →
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{capabilities.delivery && (
|
||
<div className="bg-krona-surface border border-krona-border rounded-lg p-3.5 shadow-krona-soft">
|
||
<div className="flex items-center justify-between text-xs mb-2">
|
||
<span className="font-semibold text-krona-primary">Доставка & Курьеры</span>
|
||
<Badge variant="info" size="sm">3 в пути</Badge>
|
||
</div>
|
||
<div className="text-xs text-krona-secondary space-y-1">
|
||
<div className="flex justify-between">
|
||
<span>Среднее время доставки:</span>
|
||
<span className="font-medium text-krona-primary">34 мин</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span>Курьеров на линии:</span>
|
||
<span className="font-medium text-krona-primary">4 сотрудника</span>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => navigate('/operations/delivery')}
|
||
className="mt-2.5 text-xs text-krona-green font-medium hover:underline block"
|
||
>
|
||
Управление доставкой →
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
<div className="bg-krona-surface border border-krona-border rounded-lg p-3.5 shadow-krona-soft">
|
||
<div className="flex items-center justify-between text-xs mb-2">
|
||
<span className="font-semibold text-krona-primary">Финансы & Кассы</span>
|
||
<Badge variant="green" size="sm">Смены открыты</Badge>
|
||
</div>
|
||
<div className="text-xs text-krona-secondary space-y-1">
|
||
<div className="flex justify-between">
|
||
<span>Наличные в кассе:</span>
|
||
<span className="font-medium text-krona-primary">42 800 ₽</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span>Безнал / Эквайринг:</span>
|
||
<span className="font-medium text-krona-primary">141 700 ₽</span>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => navigate('/finance')}
|
||
className="mt-2.5 text-xs text-krona-green font-medium hover:underline block"
|
||
>
|
||
ДДС & Сальдо →
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|