Добавлена логика маршрутизации для поставок в зависимости от типа организации пользователя. Обновлены компоненты боковой панели и страницы создания поставки: реализован поиск оптовиков, улучшена фильтрация товаров и адаптация данных оптовиков. Убраны неиспользуемые поля и улучшен интерфейс отображения информации о товарах и оптовиках.
This commit is contained in:
@ -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>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user