Добавлена логика маршрутизации для поставок в зависимости от типа организации пользователя. Обновлены компоненты боковой панели и страницы создания поставки: реализован поиск оптовиков, улучшена фильтрация товаров и адаптация данных оптовиков. Убраны неиспользуемые поля и улучшен интерфейс отображения информации о товарах и оптовиках.

This commit is contained in:
Bivekich
2025-07-21 11:52:26 +03:00
parent cc1f9d8473
commit 2afbe6cac0
8 changed files with 1225 additions and 80 deletions

View File

@ -0,0 +1,60 @@
"use client"
import { useState } from 'react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Card } from '@/components/ui/card'
import { Sidebar } from '@/components/dashboard/sidebar'
import { useSidebar } from '@/hooks/useSidebar'
import { Package, Truck, Wrench, ArrowLeftRight } from 'lucide-react'
// Импорты компонентов подразделов
import { GoodsSuppliesTab } from './goods-supplies/goods-supplies-tab'
import { MaterialsSuppliesTab } from './materials-supplies/materials-supplies-tab'
export function FulfillmentSuppliesDashboard() {
const { getSidebarMargin } = useSidebar()
const [activeTab, setActiveTab] = useState('goods')
return (
<div className="h-screen flex overflow-hidden">
<Sidebar />
<main className={`flex-1 ${getSidebarMargin()} px-4 py-3 overflow-hidden transition-all duration-300`}>
<div className="h-full w-full flex flex-col">
{/* Основной контент с табами */}
<div className="flex-1 overflow-hidden">
<Tabs value={activeTab} onValueChange={setActiveTab} className="h-full flex flex-col">
<TabsList className="grid w-full grid-cols-2 bg-white/5 backdrop-blur border-white/10 flex-shrink-0 h-10">
<TabsTrigger
value="goods"
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/70 flex items-center gap-1 text-sm"
>
<Package className="h-3 w-3" />
Товары
</TabsTrigger>
<TabsTrigger
value="materials"
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/70 flex items-center gap-1 text-sm"
>
<Wrench className="h-3 w-3" />
Расходники
</TabsTrigger>
</TabsList>
<TabsContent value="goods" className="flex-1 overflow-hidden mt-3">
<Card className="glass-card h-full overflow-hidden p-0">
<GoodsSuppliesTab />
</Card>
</TabsContent>
<TabsContent value="materials" className="flex-1 overflow-hidden mt-3">
<Card className="glass-card h-full overflow-hidden p-0">
<MaterialsSuppliesTab />
</Card>
</TabsContent>
</Tabs>
</div>
</div>
</main>
</div>
)
}

View File

@ -0,0 +1,297 @@
"use client"
import { useState } from 'react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Card } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Package, Wrench, RotateCcw, Plus, Calendar, TrendingUp, AlertCircle, Building2 } from 'lucide-react'
interface SupplyItem {
id: string
name: string
type: 'product' | 'materials' | 'return'
category: string
quantity: number
status: 'planned' | 'in-transit' | 'delivered' | 'processing'
date: string
supplier: string
amount: number
}
const mockSupplies: SupplyItem[] = [
{
id: '1',
name: 'Смартфоны iPhone 15',
type: 'product',
category: 'Электроника',
quantity: 50,
status: 'delivered',
date: '2024-01-15',
supplier: 'ООО "ТехноСнаб"',
amount: 3750000
},
{
id: '2',
name: 'Упаковочные коробки',
type: 'materials',
category: 'Упаковка',
quantity: 1000,
status: 'in-transit',
date: '2024-01-18',
supplier: 'ИП Селлер Один',
amount: 25000
},
{
id: '3',
name: 'Товары с брагом (ПВЗ №5)',
type: 'return',
category: 'Возврат',
quantity: 15,
status: 'processing',
date: '2024-01-20',
supplier: 'ПВЗ Москва-5',
amount: 185000
}
]
export function FulfillmentSuppliesTab() {
const [activeFilter, setActiveFilter] = useState<'all' | 'product' | 'materials' | 'return'>('all')
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: { variant: 'outline' as const, color: 'text-blue-300 border-blue-400/30', label: 'Запланировано' },
'in-transit': { variant: 'outline' as const, color: 'text-yellow-300 border-yellow-400/30', label: 'В пути' },
delivered: { variant: 'outline' as const, color: 'text-green-300 border-green-400/30', label: 'Доставлено' },
processing: { variant: 'outline' as const, color: 'text-orange-300 border-orange-400/30', label: 'Обработка' }
}
const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.planned
return (
<Badge variant={config.variant} className={`glass-secondary ${config.color}`}>
{config.label}
</Badge>
)
}
const getTypeIcon = (type: string) => {
switch (type) {
case 'product':
return <Package className="h-4 w-4 text-blue-400" />
case 'materials':
return <Wrench className="h-4 w-4 text-purple-400" />
case 'return':
return <RotateCcw className="h-4 w-4 text-orange-400" />
default:
return <Package className="h-4 w-4 text-gray-400" />
}
}
const filteredSupplies = activeFilter === 'all'
? mockSupplies
: mockSupplies.filter(supply => supply.type === activeFilter)
const getTotalAmount = () => {
return filteredSupplies.reduce((sum, supply) => sum + supply.amount, 0)
}
const getTotalQuantity = () => {
return filteredSupplies.reduce((sum, supply) => sum + supply.quantity, 0)
}
return (
<div className="h-full flex flex-col space-y-4 p-4">
{/* Компактный заголовок и кнопка */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Building2 className="h-4 w-4 text-blue-400" />
<span className="text-white font-medium text-sm">Фулфилмент</span>
</div>
<Button
size="sm"
className="bg-gradient-to-r from-blue-500 to-cyan-500 hover:from-blue-600 hover:to-cyan-600 text-white text-xs"
>
<Plus className="h-3 w-3 mr-1" />
Создать
</Button>
</div>
{/* Компактная статистика */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
<Card className="glass-card p-3">
<div className="flex items-center space-x-2">
<div className="p-1.5 bg-blue-500/20 rounded">
<Package className="h-3 w-3 text-blue-400" />
</div>
<div>
<p className="text-white/60 text-xs">Поставок</p>
<p className="text-lg font-bold text-white">{filteredSupplies.length}</p>
</div>
</div>
</Card>
<Card className="glass-card p-3">
<div className="flex items-center space-x-2">
<div className="p-1.5 bg-green-500/20 rounded">
<TrendingUp className="h-3 w-3 text-green-400" />
</div>
<div>
<p className="text-white/60 text-xs">Сумма</p>
<p className="text-lg font-bold text-white">{formatCurrency(getTotalAmount())}</p>
</div>
</div>
</Card>
<Card className="glass-card p-3">
<div className="flex items-center space-x-2">
<div className="p-1.5 bg-purple-500/20 rounded">
<AlertCircle className="h-3 w-3 text-purple-400" />
</div>
<div>
<p className="text-white/60 text-xs">Единиц</p>
<p className="text-lg font-bold text-white">{getTotalQuantity()}</p>
</div>
</div>
</Card>
<Card className="glass-card p-3">
<div className="flex items-center space-x-2">
<div className="p-1.5 bg-yellow-500/20 rounded">
<Calendar className="h-3 w-3 text-yellow-400" />
</div>
<div>
<p className="text-white/60 text-xs">В пути</p>
<p className="text-lg font-bold text-white">
{filteredSupplies.filter(s => s.status === 'in-transit').length}
</p>
</div>
</div>
</Card>
</div>
{/* Компактные фильтры */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-1">
<Button
variant={activeFilter === 'all' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setActiveFilter('all')}
className={`h-7 px-2 text-xs ${activeFilter === 'all' ? 'bg-white/20 text-white' : 'text-white/70 hover:bg-white/10'}`}
>
Все
</Button>
<Button
variant={activeFilter === 'product' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setActiveFilter('product')}
className={`h-7 px-2 text-xs ${activeFilter === 'product' ? 'bg-white/20 text-white' : 'text-white/70 hover:bg-white/10'} flex items-center gap-1`}
>
<Package className="h-3 w-3" />
Товары
</Button>
<Button
variant={activeFilter === 'materials' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setActiveFilter('materials')}
className={`h-7 px-2 text-xs ${activeFilter === 'materials' ? 'bg-white/20 text-white' : 'text-white/70 hover:bg-white/10'} flex items-center gap-1`}
>
<Wrench className="h-3 w-3" />
Расходники
</Button>
<Button
variant={activeFilter === 'return' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setActiveFilter('return')}
className={`h-7 px-2 text-xs ${activeFilter === 'return' ? 'bg-white/20 text-white' : 'text-white/70 hover:bg-white/10'} flex items-center gap-1`}
>
<RotateCcw className="h-3 w-3" />
Возвраты
</Button>
</div>
</div>
{/* Компактная таблица */}
<Card className="glass-card flex-1 overflow-hidden">
<div className="p-3 h-full flex flex-col">
<div className="overflow-x-auto flex-1">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-white/20">
<th className="text-left p-2 text-white font-medium text-xs">Тип</th>
<th className="text-left p-2 text-white font-medium text-xs">Наименование</th>
<th className="text-left p-2 text-white font-medium text-xs">Категория</th>
<th className="text-left p-2 text-white font-medium text-xs">Кол-во</th>
<th className="text-left p-2 text-white font-medium text-xs">Поставщик</th>
<th className="text-left p-2 text-white font-medium text-xs">Дата</th>
<th className="text-left p-2 text-white font-medium text-xs">Сумма</th>
<th className="text-left p-2 text-white font-medium text-xs">Статус</th>
</tr>
</thead>
<tbody>
{filteredSupplies.map((supply) => (
<tr key={supply.id} className="border-b border-white/10 hover:bg-white/5 transition-colors">
<td className="p-2">
<div className="flex items-center justify-center">
{getTypeIcon(supply.type)}
</div>
</td>
<td className="p-2">
<span className="text-white font-medium text-sm">{supply.name}</span>
</td>
<td className="p-2">
<span className="text-white/80 text-sm">{supply.category}</span>
</td>
<td className="p-2">
<span className="text-white font-semibold text-sm">{supply.quantity}</span>
</td>
<td className="p-2">
<span className="text-white/80 text-sm">{supply.supplier}</span>
</td>
<td className="p-2">
<span className="text-white/80 text-sm">{formatDate(supply.date)}</span>
</td>
<td className="p-2">
<span className="text-white font-semibold text-sm">{formatCurrency(supply.amount)}</span>
</td>
<td className="p-2">
{getStatusBadge(supply.status)}
</td>
</tr>
))}
</tbody>
</table>
{filteredSupplies.length === 0 && (
<div className="flex items-center justify-center p-8">
<div className="text-center">
<Package className="h-12 w-12 text-white/20 mx-auto mb-4" />
<p className="text-white/60">Поставки не найдены</p>
<p className="text-white/40 text-sm mt-2">
Измените фильтр или создайте новую поставку
</p>
</div>
</div>
)}
</div>
</div>
</Card>
</div>
)
}

View File

@ -0,0 +1,47 @@
"use client"
import { useState } from 'react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Building2, ShoppingCart } from 'lucide-react'
// Импорты компонентов подразделов
import { FulfillmentSuppliesTab } from './fulfillment-supplies-tab'
import { MarketplaceSuppliesTab } from './marketplace-supplies-tab'
export function GoodsSuppliesTab() {
const [activeSubTab, setActiveSubTab] = useState('fulfillment')
return (
<div className="h-full flex flex-col p-4">
{/* Подвкладки */}
<div className="flex-1 overflow-hidden">
<Tabs value={activeSubTab} onValueChange={setActiveSubTab} className="h-full flex flex-col">
<TabsList className="grid w-full grid-cols-2 bg-white/5 backdrop-blur border-white/10 flex-shrink-0 mb-3 h-9">
<TabsTrigger
value="fulfillment"
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/70 flex items-center gap-1 text-sm"
>
<Building2 className="h-3 w-3" />
Фулфилмент
</TabsTrigger>
<TabsTrigger
value="marketplace"
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/70 flex items-center gap-1 text-sm"
>
<ShoppingCart className="h-3 w-3" />
Маркетплейсы
</TabsTrigger>
</TabsList>
<TabsContent value="fulfillment" className="flex-1 overflow-hidden">
<FulfillmentSuppliesTab />
</TabsContent>
<TabsContent value="marketplace" className="flex-1 overflow-hidden">
<MarketplaceSuppliesTab />
</TabsContent>
</Tabs>
</div>
</div>
)
}

View File

@ -0,0 +1,299 @@
"use client"
import { useState } from 'react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Card } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { ShoppingCart, Package, Plus, Calendar, TrendingUp, AlertCircle, Building2 } from 'lucide-react'
interface MarketplaceSupply {
id: string
name: string
marketplace: 'wildberries' | 'ozon'
category: string
quantity: number
status: 'planned' | 'in-transit' | 'delivered' | 'accepted'
date: string
warehouse: string
amount: number
sku: string
}
const mockMarketplaceSupplies: MarketplaceSupply[] = [
{
id: '1',
name: 'Наушники AirPods Pro',
marketplace: 'wildberries',
category: 'Аудио',
quantity: 30,
status: 'delivered',
date: '2024-01-20',
warehouse: 'WB Подольск',
amount: 750000,
sku: 'APL-AP-PRO2'
},
{
id: '2',
name: 'Смарт часы Apple Watch',
marketplace: 'ozon',
category: 'Электроника',
quantity: 25,
status: 'in-transit',
date: '2024-01-22',
warehouse: 'Ozon Тверь',
amount: 1250000,
sku: 'APL-AW-S9'
},
{
id: '3',
name: 'Зарядные устройства',
marketplace: 'wildberries',
category: 'Аксессуары',
quantity: 100,
status: 'accepted',
date: '2024-01-18',
warehouse: 'WB Электросталь',
amount: 350000,
sku: 'ACC-CHG-20W'
}
]
export function MarketplaceSuppliesTab() {
const [activeMarketplace, setActiveMarketplace] = useState<'all' | 'wildberries' | 'ozon'>('all')
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: { variant: 'outline' as const, color: 'text-blue-300 border-blue-400/30', label: 'Запланировано' },
'in-transit': { variant: 'outline' as const, color: 'text-yellow-300 border-yellow-400/30', label: 'В пути' },
delivered: { variant: 'outline' as const, color: 'text-green-300 border-green-400/30', label: 'Доставлено' },
accepted: { variant: 'outline' as const, color: 'text-purple-300 border-purple-400/30', label: 'Принято' }
}
const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.planned
return (
<Badge variant={config.variant} className={`glass-secondary ${config.color}`}>
{config.label}
</Badge>
)
}
const getMarketplaceBadge = (marketplace: string) => {
if (marketplace === 'wildberries') {
return (
<Badge variant="outline" className="glass-secondary text-purple-300 border-purple-400/30">
Wildberries
</Badge>
)
} else if (marketplace === 'ozon') {
return (
<Badge variant="outline" className="glass-secondary text-blue-300 border-blue-400/30">
Ozon
</Badge>
)
}
return null
}
const filteredSupplies = activeMarketplace === 'all'
? mockMarketplaceSupplies
: mockMarketplaceSupplies.filter(supply => supply.marketplace === activeMarketplace)
const getTotalAmount = () => {
return filteredSupplies.reduce((sum, supply) => sum + supply.amount, 0)
}
const getTotalQuantity = () => {
return filteredSupplies.reduce((sum, supply) => sum + supply.quantity, 0)
}
return (
<div className="h-full flex flex-col space-y-4 p-4">
{/* Компактный заголовок */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<ShoppingCart className="h-4 w-4 text-purple-400" />
<span className="text-white font-medium text-sm">Маркетплейсы</span>
</div>
<Button
size="sm"
className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white text-xs"
>
<Plus className="h-3 w-3 mr-1" />
Создать
</Button>
</div>
{/* Компактная статистика */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
<Card className="glass-card p-3">
<div className="flex items-center space-x-2">
<div className="p-1.5 bg-purple-500/20 rounded">
<Package className="h-3 w-3 text-purple-400" />
</div>
<div>
<p className="text-white/60 text-xs">Поставок</p>
<p className="text-lg font-bold text-white">{filteredSupplies.length}</p>
</div>
</div>
</Card>
<Card className="glass-card p-4">
<div className="flex items-center space-x-3">
<div className="p-2 bg-green-500/20 rounded-lg">
<TrendingUp className="h-5 w-5 text-green-400" />
</div>
<div>
<p className="text-white/60 text-sm">Общая сумма</p>
<p className="text-xl font-bold text-white">{formatCurrency(getTotalAmount())}</p>
</div>
</div>
</Card>
<Card className="glass-card p-4">
<div className="flex items-center space-x-3">
<div className="p-2 bg-blue-500/20 rounded-lg">
<AlertCircle className="h-5 w-5 text-blue-400" />
</div>
<div>
<p className="text-white/60 text-sm">Единиц товара</p>
<p className="text-xl font-bold text-white">{getTotalQuantity()}</p>
</div>
</div>
</Card>
<Card className="glass-card p-4">
<div className="flex items-center space-x-3">
<div className="p-2 bg-yellow-500/20 rounded-lg">
<Calendar className="h-5 w-5 text-yellow-400" />
</div>
<div>
<p className="text-white/60 text-sm">В пути</p>
<p className="text-xl font-bold text-white">
{filteredSupplies.filter(s => s.status === 'in-transit').length}
</p>
</div>
</div>
</Card>
</div>
{/* Фильтры по маркетплейсу */}
<div className="flex items-center space-x-2">
<span className="text-white/60 text-sm">Маркетплейс:</span>
<div className="flex space-x-2">
<Button
variant={activeMarketplace === 'all' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setActiveMarketplace('all')}
className={`${activeMarketplace === 'all' ? 'bg-white/20 text-white' : 'text-white/70 hover:bg-white/10'}`}
>
Все
</Button>
<Button
variant={activeMarketplace === 'wildberries' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setActiveMarketplace('wildberries')}
className={`${activeMarketplace === 'wildberries' ? 'bg-white/20 text-white' : 'text-white/70 hover:bg-white/10'} flex items-center gap-1`}
>
<div className="w-3 h-3 bg-purple-400 rounded-full"></div>
Wildberries
</Button>
<Button
variant={activeMarketplace === 'ozon' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setActiveMarketplace('ozon')}
className={`${activeMarketplace === 'ozon' ? 'bg-white/20 text-white' : 'text-white/70 hover:bg-white/10'} flex items-center gap-1`}
>
<div className="w-3 h-3 bg-blue-400 rounded-full"></div>
Ozon
</Button>
</div>
</div>
{/* Таблица поставок */}
<Card className="glass-card flex-1 overflow-hidden">
<div className="p-6 h-full flex flex-col">
<div className="overflow-x-auto flex-1">
<table className="w-full">
<thead>
<tr className="border-b border-white/20">
<th className="text-left p-3 text-white font-semibold">Маркетплейс</th>
<th className="text-left p-3 text-white font-semibold">Наименование</th>
<th className="text-left p-3 text-white font-semibold">SKU</th>
<th className="text-left p-3 text-white font-semibold">Категория</th>
<th className="text-left p-3 text-white font-semibold">Количество</th>
<th className="text-left p-3 text-white font-semibold">Склад</th>
<th className="text-left p-3 text-white font-semibold">Дата</th>
<th className="text-left p-3 text-white font-semibold">Сумма</th>
<th className="text-left p-3 text-white font-semibold">Статус</th>
</tr>
</thead>
<tbody>
{filteredSupplies.map((supply) => (
<tr key={supply.id} className="border-b border-white/10 hover:bg-white/5 transition-colors">
<td className="p-3">
{getMarketplaceBadge(supply.marketplace)}
</td>
<td className="p-3">
<span className="text-white font-medium">{supply.name}</span>
</td>
<td className="p-3">
<span className="text-white/80 font-mono text-sm">{supply.sku}</span>
</td>
<td className="p-3">
<span className="text-white/80">{supply.category}</span>
</td>
<td className="p-3">
<span className="text-white font-semibold">{supply.quantity}</span>
</td>
<td className="p-3">
<span className="text-white/80">{supply.warehouse}</span>
</td>
<td className="p-3">
<span className="text-white/80">{formatDate(supply.date)}</span>
</td>
<td className="p-3">
<span className="text-white font-semibold">{formatCurrency(supply.amount)}</span>
</td>
<td className="p-3">
{getStatusBadge(supply.status)}
</td>
</tr>
))}
</tbody>
</table>
{filteredSupplies.length === 0 && (
<div className="flex items-center justify-center p-8">
<div className="text-center">
<ShoppingCart className="h-12 w-12 text-white/20 mx-auto mb-4" />
<p className="text-white/60">Поставки не найдены</p>
<p className="text-white/40 text-sm mt-2">
Измените фильтр или создайте новую поставку
</p>
</div>
</div>
)}
</div>
</div>
</Card>
</div>
)
}

View File

@ -0,0 +1,347 @@
"use client"
import { useState } from 'react'
import { Card } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Wrench, Plus, Calendar, TrendingUp, AlertCircle, Search, Filter } from 'lucide-react'
interface MaterialSupply {
id: string
name: string
category: string
quantity: number
unit: string
status: 'planned' | 'in-transit' | 'delivered' | 'in-stock'
date: string
supplier: string
amount: number
description?: string
minStock: number
currentStock: number
}
const mockMaterialSupplies: MaterialSupply[] = [
{
id: '1',
name: 'Упаковочные коробки 30x20x10',
category: 'Упаковка',
quantity: 1000,
unit: 'шт',
status: 'delivered',
date: '2024-01-15',
supplier: 'ООО "УпакСервис"',
amount: 50000,
description: 'Картонные коробки для мелких товаров',
minStock: 200,
currentStock: 350
},
{
id: '2',
name: 'Пузырчатая пленка',
category: 'Защитная упаковка',
quantity: 500,
unit: 'м²',
status: 'in-transit',
date: '2024-01-20',
supplier: 'ИП Петров А.В.',
amount: 25000,
description: 'Пленка для защиты хрупких товаров',
minStock: 100,
currentStock: 80
},
{
id: '3',
name: 'Скотч упаковочный прозрачный',
category: 'Клейкая лента',
quantity: 200,
unit: 'рул',
status: 'planned',
date: '2024-01-25',
supplier: 'ООО "КлейТех"',
amount: 15000,
description: 'Прозрачный скотч 48мм x 66м',
minStock: 50,
currentStock: 25
},
{
id: '4',
name: 'Этикетки самоклеющиеся',
category: 'Маркировка',
quantity: 10000,
unit: 'шт',
status: 'in-stock',
date: '2024-01-10',
supplier: 'ООО "ЛейблПринт"',
amount: 30000,
description: 'Белые этикетки 100x70мм',
minStock: 2000,
currentStock: 3500
}
]
export function MaterialsSuppliesTab() {
const [searchTerm, setSearchTerm] = useState('')
const [categoryFilter, setCategoryFilter] = useState<string>('all')
const [statusFilter, setStatusFilter] = useState<string>('all')
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: { variant: 'outline' as const, color: 'text-blue-300 border-blue-400/30', label: 'Запланировано' },
'in-transit': { variant: 'outline' as const, color: 'text-yellow-300 border-yellow-400/30', label: 'В пути' },
delivered: { variant: 'outline' as const, color: 'text-green-300 border-green-400/30', label: 'Доставлено' },
'in-stock': { variant: 'outline' as const, color: 'text-purple-300 border-purple-400/30', label: 'На складе' }
}
const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.planned
return (
<Badge variant={config.variant} className={`glass-secondary ${config.color}`}>
{config.label}
</Badge>
)
}
const getStockStatusBadge = (currentStock: number, minStock: number) => {
if (currentStock <= minStock) {
return (
<Badge variant="outline" className="glass-secondary text-red-300 border-red-400/30">
Низкий остаток
</Badge>
)
} else if (currentStock <= minStock * 1.5) {
return (
<Badge variant="outline" className="glass-secondary text-yellow-300 border-yellow-400/30">
Требует заказа
</Badge>
)
}
return (
<Badge variant="outline" className="glass-secondary text-green-300 border-green-400/30">
В норме
</Badge>
)
}
const filteredSupplies = mockMaterialSupplies.filter(supply => {
const matchesSearch = supply.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
supply.category.toLowerCase().includes(searchTerm.toLowerCase()) ||
supply.supplier.toLowerCase().includes(searchTerm.toLowerCase())
const matchesCategory = categoryFilter === 'all' || supply.category === categoryFilter
const matchesStatus = statusFilter === 'all' || supply.status === statusFilter
return matchesSearch && matchesCategory && matchesStatus
})
const getTotalAmount = () => {
return filteredSupplies.reduce((sum, supply) => sum + supply.amount, 0)
}
const getTotalQuantity = () => {
return filteredSupplies.reduce((sum, supply) => sum + supply.quantity, 0)
}
const getLowStockCount = () => {
return mockMaterialSupplies.filter(supply => supply.currentStock <= supply.minStock).length
}
const categories = Array.from(new Set(mockMaterialSupplies.map(supply => supply.category)))
return (
<div className="h-full flex flex-col space-y-4 p-4">
{/* Компактный заголовок */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Wrench className="h-4 w-4 text-purple-400" />
<span className="text-white font-medium text-sm">Расходники</span>
</div>
<Button
size="sm"
className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white text-xs"
>
<Plus className="h-3 w-3 mr-1" />
Заказать
</Button>
</div>
{/* Компактная статистика */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
<Card className="glass-card p-3">
<div className="flex items-center space-x-2">
<div className="p-1.5 bg-purple-500/20 rounded">
<Wrench className="h-3 w-3 text-purple-400" />
</div>
<div>
<p className="text-white/60 text-xs">Поставок</p>
<p className="text-lg font-bold text-white">{filteredSupplies.length}</p>
</div>
</div>
</Card>
<Card className="glass-card p-3">
<div className="flex items-center space-x-2">
<div className="p-1.5 bg-green-500/20 rounded">
<TrendingUp className="h-3 w-3 text-green-400" />
</div>
<div>
<p className="text-white/60 text-xs">Сумма</p>
<p className="text-lg font-bold text-white">{formatCurrency(getTotalAmount())}</p>
</div>
</div>
</Card>
<Card className="glass-card p-3">
<div className="flex items-center space-x-2">
<div className="p-1.5 bg-blue-500/20 rounded">
<AlertCircle className="h-3 w-3 text-blue-400" />
</div>
<div>
<p className="text-white/60 text-xs">Единиц</p>
<p className="text-lg font-bold text-white">{getTotalQuantity()}</p>
</div>
</div>
</Card>
<Card className="glass-card p-3">
<div className="flex items-center space-x-2">
<div className="p-1.5 bg-red-500/20 rounded">
<Calendar className="h-3 w-3 text-red-400" />
</div>
<div>
<p className="text-white/60 text-xs">Низкий остаток</p>
<p className="text-lg font-bold text-white">{getLowStockCount()}</p>
</div>
</div>
</Card>
</div>
{/* Компактный поиск и фильтры */}
<div className="flex flex-col sm:flex-row gap-2">
<div className="relative flex-1">
<Search className="absolute left-2 top-2 h-3 w-3 text-white/40" />
<Input
placeholder="Поиск..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-7 h-8 text-sm glass-input text-white placeholder:text-white/40"
/>
</div>
<div className="flex gap-1">
<select
value={categoryFilter}
onChange={(e) => setCategoryFilter(e.target.value)}
className="px-2 py-1 h-8 bg-white/5 border border-white/20 rounded text-white text-xs focus:outline-none focus:ring-1 focus:ring-purple-500"
>
<option value="all">Все категории</option>
{categories.map(category => (
<option key={category} value={category}>{category}</option>
))}
</select>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="px-2 py-1 h-8 bg-white/5 border border-white/20 rounded text-white text-xs focus:outline-none focus:ring-1 focus:ring-purple-500"
>
<option value="all">Все статусы</option>
<option value="planned">Запланировано</option>
<option value="in-transit">В пути</option>
<option value="delivered">Доставлено</option>
<option value="in-stock">На складе</option>
</select>
</div>
</div>
{/* Компактная таблица расходников */}
<Card className="glass-card flex-1 overflow-hidden">
<div className="p-3 h-full flex flex-col">
<div className="overflow-x-auto flex-1">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-white/20">
<th className="text-left p-2 text-white font-medium text-xs">Наименование</th>
<th className="text-left p-2 text-white font-medium text-xs">Категория</th>
<th className="text-left p-2 text-white font-medium text-xs">Кол-во</th>
<th className="text-left p-2 text-white font-medium text-xs">Остаток</th>
<th className="text-left p-2 text-white font-medium text-xs">Поставщик</th>
<th className="text-left p-2 text-white font-medium text-xs">Дата</th>
<th className="text-left p-2 text-white font-medium text-xs">Сумма</th>
<th className="text-left p-2 text-white font-medium text-xs">Статус</th>
</tr>
</thead>
<tbody>
{filteredSupplies.map((supply) => (
<tr key={supply.id} className="border-b border-white/10 hover:bg-white/5 transition-colors">
<td className="p-2">
<div>
<span className="text-white font-medium text-sm">{supply.name}</span>
{supply.description && (
<p className="text-white/60 text-xs mt-0.5">{supply.description}</p>
)}
</div>
</td>
<td className="p-2">
<span className="text-white/80 text-sm">{supply.category}</span>
</td>
<td className="p-2">
<span className="text-white font-semibold text-sm">{supply.quantity} {supply.unit}</span>
</td>
<td className="p-2">
<div className="flex flex-col gap-0.5">
<span className="text-white font-semibold text-sm">{supply.currentStock} {supply.unit}</span>
{getStockStatusBadge(supply.currentStock, supply.minStock)}
</div>
</td>
<td className="p-2">
<span className="text-white/80 text-sm">{supply.supplier}</span>
</td>
<td className="p-2">
<span className="text-white/80 text-sm">{formatDate(supply.date)}</span>
</td>
<td className="p-2">
<span className="text-white font-semibold text-sm">{formatCurrency(supply.amount)}</span>
</td>
<td className="p-2">
{getStatusBadge(supply.status)}
</td>
</tr>
))}
</tbody>
</table>
{filteredSupplies.length === 0 && (
<div className="flex items-center justify-center p-8">
<div className="text-center">
<Wrench className="h-12 w-12 text-white/20 mx-auto mb-4" />
<p className="text-white/60">Расходники не найдены</p>
<p className="text-white/40 text-sm mt-2">
Попробуйте изменить параметры поиска или создать новый заказ
</p>
</div>
</div>
)}
</div>
</div>
</Card>
</div>
)
}