Добавлены новые зависимости для работы с графиками и статистикой: интегрирован пакет recharts для визуализации данных. Обновлены компоненты бизнес-демо и сайдбара, добавлены новые функции для отображения информации о поставках и статистике. Улучшена структура кода и взаимодействие с пользователем. Обновлены GraphQL резолверы для получения статистики Wildberries.
This commit is contained in:
@ -7,6 +7,13 @@ import { Button } from '@/components/ui/button'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import {
|
||||
Calendar,
|
||||
Check,
|
||||
@ -29,12 +36,20 @@ import {
|
||||
Briefcase,
|
||||
Eye,
|
||||
Plus,
|
||||
Minus
|
||||
Minus,
|
||||
Store,
|
||||
Boxes,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Hash,
|
||||
Package2,
|
||||
Truck
|
||||
} from 'lucide-react'
|
||||
|
||||
export function BusinessDemo() {
|
||||
const [selectedProduct] = useState(null)
|
||||
const [cartQuantity, setCartQuantity] = useState(1)
|
||||
const [expandedSeller, setExpandedSeller] = useState(false)
|
||||
|
||||
// Данные для демонстрации
|
||||
const scheduleData = Array.from({ length: 30 }, (_, i) => ({
|
||||
@ -92,6 +107,39 @@ export function BusinessDemo() {
|
||||
}
|
||||
]
|
||||
|
||||
// Данные для поставки фулфилмента
|
||||
const fulfillmentSupply = {
|
||||
id: "1",
|
||||
supplyNumber: "ФФ-2024-001",
|
||||
supplyDate: "2024-01-15",
|
||||
seller: {
|
||||
id: "seller1",
|
||||
name: "TechStore LLC",
|
||||
storeName: "ТехноМагазин",
|
||||
managerName: "Иванов Иван Иванович",
|
||||
phone: "+7 (495) 123-45-67",
|
||||
email: "contact@techstore.ru",
|
||||
inn: "7701234567",
|
||||
},
|
||||
itemsQuantity: 150,
|
||||
cargoPlaces: 5,
|
||||
volume: 12.5,
|
||||
responsibleEmployeeId: "emp1",
|
||||
logisticsPartnerId: "log1",
|
||||
status: "planned",
|
||||
totalValue: 2500000,
|
||||
}
|
||||
|
||||
const employees = [
|
||||
{ id: "emp1", firstName: "Иван", lastName: "Петров", position: "Менеджер склада" },
|
||||
{ id: "emp2", firstName: "Мария", lastName: "Сидорова", position: "Логист" }
|
||||
]
|
||||
|
||||
const logisticsPartners = [
|
||||
{ id: "log1", name: "ТК Энергия", fullName: "ООО ТК Энергия" },
|
||||
{ id: "log2", name: "СДЭК", fullName: "ООО СДЭК" }
|
||||
]
|
||||
|
||||
const wholesalers = [
|
||||
{
|
||||
id: '1',
|
||||
@ -165,6 +213,52 @@ export function BusinessDemo() {
|
||||
}).format(price)
|
||||
}
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat("ru-RU", {
|
||||
style: "currency",
|
||||
currency: "RUB",
|
||||
minimumFractionDigits: 0,
|
||||
}).format(amount)
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
})
|
||||
}
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
const statusConfig = {
|
||||
planned: {
|
||||
color: "text-blue-300 border-blue-400/30",
|
||||
label: "Запланировано",
|
||||
},
|
||||
"in-transit": {
|
||||
color: "text-yellow-300 border-yellow-400/30",
|
||||
label: "В пути",
|
||||
},
|
||||
delivered: {
|
||||
color: "text-green-300 border-green-400/30",
|
||||
label: "Доставлено",
|
||||
},
|
||||
"in-processing": {
|
||||
color: "text-purple-300 border-purple-400/30",
|
||||
label: "Обрабатывается",
|
||||
},
|
||||
}
|
||||
|
||||
const config =
|
||||
statusConfig[status as keyof typeof statusConfig] || statusConfig.planned
|
||||
|
||||
return (
|
||||
<Badge variant="outline" className={`glass-secondary ${config.color}`}>
|
||||
{config.label}
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Табель рабочего времени */}
|
||||
@ -385,6 +479,238 @@ export function BusinessDemo() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Карточка поставки фулфилмента */}
|
||||
<Card className="glass-card border-white/10">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white">Карточка поставки фулфилмента</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Card className="glass-card p-4 hover:bg-white/10 transition-colors">
|
||||
<div className="space-y-3">
|
||||
{/* Компактный блок с названием магазина */}
|
||||
<div className="flex items-center justify-between bg-white/5 rounded-lg p-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-1.5 bg-green-500/20 rounded">
|
||||
<Store className="h-3 w-3 text-green-400" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-white font-medium text-sm">
|
||||
{fulfillmentSupply.seller.storeName}
|
||||
</span>
|
||||
<span className="text-white/60 text-xs"> • </span>
|
||||
<span className="text-white/80 text-xs">
|
||||
{fulfillmentSupply.seller.managerName}
|
||||
</span>
|
||||
<span className="text-white/60 text-xs"> • </span>
|
||||
<span className="text-white/80 text-xs">
|
||||
{fulfillmentSupply.seller.phone}
|
||||
</span>
|
||||
<span className="text-white/60 text-xs"> • </span>
|
||||
<div className="flex items-center gap-1">
|
||||
<a
|
||||
href={`https://t.me/${fulfillmentSupply.seller.phone.replace(
|
||||
/[^\d]/g,
|
||||
""
|
||||
)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-1 bg-blue-500/20 rounded hover:bg-blue-500/30 transition-colors"
|
||||
title="Написать в Telegram"
|
||||
>
|
||||
<svg
|
||||
className="h-3 w-3 text-blue-400"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href={`https://wa.me/${fulfillmentSupply.seller.phone.replace(
|
||||
/[^\d]/g,
|
||||
""
|
||||
)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-1 bg-green-500/20 rounded hover:bg-green-500/30 transition-colors"
|
||||
title="Написать в WhatsApp"
|
||||
>
|
||||
<svg
|
||||
className="h-3 w-3 text-green-400"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893A11.821 11.821 0 0020.885 3.488" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setExpandedSeller(!expandedSeller)}
|
||||
className="h-6 w-6 p-0 text-white/60 hover:text-white hover:bg-white/10"
|
||||
>
|
||||
{expandedSeller ? (
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
) : (
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Скрытая детальная информация о магазине */}
|
||||
{expandedSeller && (
|
||||
<div className="bg-white/5 rounded-lg p-3 space-y-2">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">
|
||||
Юридическое название
|
||||
</p>
|
||||
<p className="text-white text-sm">
|
||||
{fulfillmentSupply.seller.name}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">ИНН</p>
|
||||
<p className="text-white text-sm font-mono">
|
||||
{fulfillmentSupply.seller.inn}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">Email</p>
|
||||
<p className="text-white text-sm">
|
||||
{fulfillmentSupply.seller.email}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Единый блок со всеми параметрами в одной строке */}
|
||||
<div className="bg-white/5 rounded-lg p-4">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-9 gap-4 items-center">
|
||||
{/* Номер поставки */}
|
||||
<div className="text-center">
|
||||
<p className="text-white/60 text-xs mb-1">Номер</p>
|
||||
<p className="text-white font-semibold text-sm">
|
||||
{fulfillmentSupply.supplyNumber}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Дата поставки */}
|
||||
<div className="text-center">
|
||||
<p className="text-white/60 text-xs mb-1">Дата</p>
|
||||
<p className="text-white font-semibold text-sm">
|
||||
{formatDate(fulfillmentSupply.supplyDate)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Количество товаров */}
|
||||
<div className="text-center">
|
||||
<p className="text-white/60 text-xs mb-1">Товаров</p>
|
||||
<p className="text-white font-semibold text-sm">
|
||||
{fulfillmentSupply.itemsQuantity}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Количество мест */}
|
||||
<div className="text-center">
|
||||
<p className="text-white/60 text-xs mb-1">Мест</p>
|
||||
<p className="text-white font-semibold text-sm">
|
||||
{fulfillmentSupply.cargoPlaces}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Объём */}
|
||||
<div className="text-center">
|
||||
<p className="text-white/60 text-xs mb-1">Объём</p>
|
||||
<p className="text-white font-semibold text-sm">
|
||||
{fulfillmentSupply.volume} м³
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Стоимость */}
|
||||
<div className="text-center">
|
||||
<p className="text-white/60 text-xs mb-1">Стоимость</p>
|
||||
<p className="text-green-400 font-semibold text-sm">
|
||||
{formatCurrency(fulfillmentSupply.totalValue)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Ответственный сотрудник */}
|
||||
<div className="col-span-1">
|
||||
<p className="text-white/60 text-xs mb-1">
|
||||
Ответственный
|
||||
</p>
|
||||
<Select defaultValue={fulfillmentSupply.responsibleEmployeeId}>
|
||||
<SelectTrigger className="h-8 glass-input bg-white/10 border-white/20 text-white hover:bg-white/15 focus:bg-white/15 focus:ring-1 focus:ring-purple-400/50 text-xs">
|
||||
<SelectValue placeholder="Выберите" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-gray-900/95 backdrop-blur border-white/20 text-white">
|
||||
{employees.map((employee) => (
|
||||
<SelectItem
|
||||
key={employee.id}
|
||||
value={employee.id}
|
||||
className="text-white hover:bg-white/10 focus:bg-white/10 text-xs"
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">
|
||||
{employee.firstName} {employee.lastName}
|
||||
</span>
|
||||
<span className="text-xs text-white/60">
|
||||
{employee.position}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Логистический партнер */}
|
||||
<div className="col-span-1">
|
||||
<p className="text-white/60 text-xs mb-1">Логистика</p>
|
||||
<Select defaultValue={fulfillmentSupply.logisticsPartnerId}>
|
||||
<SelectTrigger className="h-8 glass-input bg-white/10 border-white/20 text-white hover:bg-white/15 focus:bg-white/15 focus:ring-1 focus:ring-orange-400/50 text-xs">
|
||||
<SelectValue placeholder="Выберите" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-gray-900/95 backdrop-blur border-white/20 text-white">
|
||||
{logisticsPartners.map((partner) => (
|
||||
<SelectItem
|
||||
key={partner.id}
|
||||
value={partner.id}
|
||||
className="text-white hover:bg-white/10 focus:bg-white/10 text-xs"
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">
|
||||
{partner.name || partner.fullName || "Без названия"}
|
||||
</span>
|
||||
<span className="text-xs text-white/60">
|
||||
Логистический партнер
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Статус */}
|
||||
<div className="text-center">
|
||||
<p className="text-white/60 text-xs mb-1">Статус</p>
|
||||
<div className="flex justify-center">
|
||||
{getStatusBadge(fulfillmentSupply.status)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Карточки оптовиков */}
|
||||
<Card className="glass-card border-white/10">
|
||||
<CardHeader>
|
||||
|
Reference in New Issue
Block a user