
## Структурные изменения: ### 📁 Организация архивных файлов: - Перенос всех устаревших правил в legacy-rules/ - Создание структуры docs-and-reports/ для отчетов - Архивация backup файлов в legacy-rules/backups/ ### 🔧 Критические компоненты: - src/components/supplies/multilevel-supplies-table.tsx - многоуровневая таблица поставок - src/components/supplies/components/recipe-display.tsx - отображение рецептур - src/components/fulfillment-supplies/fulfillment-goods-orders-tab.tsx - вкладка товарных заказов ### 🎯 GraphQL обновления: - Обновление mutations.ts, queries.ts, resolvers.ts, typedefs.ts - Синхронизация с Prisma schema.prisma - Backup файлы для истории изменений ### 🛠️ Утилитарные скрипты: - 12 новых скриптов в scripts/ для анализа данных - Скрипты проверки фулфилмент-пользователей - Утилиты очистки и фиксации данных поставок ### 📊 Тестирование: - test-fulfillment-filtering.js - тестирование фильтрации фулфилмента - test-full-workflow.js - полный workflow тестирование ### 📝 Документация: - logistics-statistics-warehouse-rules.md - объединенные правила модулей - Обновление журналов модуляризации и разработки ### ✅ Исправления ESLint: - Исправлены критические ошибки в sidebar.tsx - Исправлены ошибки типизации в multilevel-supplies-table.tsx - Исправлены неиспользуемые переменные в goods-supplies-table.tsx - Заменены типы any на строгую типизацию - Исправлены console.log на console.warn ## Результат: - Завершена полная модуляризация системы - Организована архитектура legacy файлов - Добавлены критически важные компоненты таблиц - Создана полная инфраструктура тестирования - Исправлены все критические ESLint ошибки - Сохранены 103 незакоммиченных изменения 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
545 lines
22 KiB
TypeScript
545 lines
22 KiB
TypeScript
/**
|
||
* БЛОК ДЕТАЛЬНОГО КАТАЛОГА С РЕЦЕПТУРОЙ
|
||
*
|
||
* Выделен из create-suppliers-supply-page.tsx
|
||
* Детальный просмотр товаров с настройкой рецептуры и панелью управления
|
||
*/
|
||
|
||
'use client'
|
||
|
||
import { useQuery } from '@apollo/client'
|
||
import { Package, Building2, Sparkles, Zap, Star, Orbit, X } from 'lucide-react'
|
||
import Image from 'next/image'
|
||
import React, { useState } from 'react'
|
||
|
||
import { Badge } from '@/components/ui/badge'
|
||
import { DatePicker } from '@/components/ui/date-picker'
|
||
import { Input } from '@/components/ui/input'
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||
import { GET_WB_WAREHOUSE_DATA } from '@/graphql/queries'
|
||
|
||
import type {
|
||
DetailedCatalogBlockProps,
|
||
GoodsProduct,
|
||
ProductRecipe,
|
||
FulfillmentService,
|
||
FulfillmentConsumable,
|
||
SellerConsumable,
|
||
} from '../types/supply-creation.types'
|
||
|
||
export const DetailedCatalogBlock = React.memo(function DetailedCatalogBlock({
|
||
allSelectedProducts,
|
||
productRecipes,
|
||
fulfillmentServices,
|
||
fulfillmentConsumables,
|
||
sellerConsumables,
|
||
deliveryDate,
|
||
selectedFulfillment,
|
||
allCounterparties,
|
||
onQuantityChange,
|
||
onRecipeChange,
|
||
onDeliveryDateChange,
|
||
onFulfillmentChange,
|
||
onProductRemove,
|
||
}: DetailedCatalogBlockProps) {
|
||
const fulfillmentCenters = allCounterparties?.filter((partner) => partner.type === 'FULFILLMENT') || []
|
||
|
||
return (
|
||
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl h-full flex flex-col">
|
||
{/* Панель управления */}
|
||
<div className="p-6 border-b border-white/10">
|
||
{/* ЗАГОЛОВОК УДАЛЕН ДЛЯ МИНИМАЛИЗМА */}
|
||
|
||
<div className="flex items-center gap-4">
|
||
{/* Дата поставки */}
|
||
<div className="w-[180px]">
|
||
<DatePicker
|
||
value={deliveryDate}
|
||
onChange={(date) => {
|
||
console.log('DatePicker onChange вызван:', date)
|
||
onDeliveryDateChange(date)
|
||
}}
|
||
className="glass-input text-white text-sm h-9"
|
||
/>
|
||
</div>
|
||
|
||
{/* Фулфилмент-центр */}
|
||
<div className="flex-1 max-w-[300px]">
|
||
<Select value={selectedFulfillment} onValueChange={onFulfillmentChange}>
|
||
<SelectTrigger className="glass-input text-white text-sm h-9">
|
||
<SelectValue placeholder="Выберите фулфилмент-центр" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{fulfillmentCenters.map((center) => (
|
||
<SelectItem key={center.id} value={center.id}>
|
||
<div className="flex items-center gap-2">
|
||
<Building2 className="h-4 w-4" />
|
||
{center.name || center.fullName}
|
||
</div>
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Каталог товаров с рецептурой - Новый стиль таблицы */}
|
||
<div className="flex-1 overflow-y-auto">
|
||
<div className="p-6 space-y-3">
|
||
|
||
{/* Строки товаров */}
|
||
{allSelectedProducts.length === 0 ? (
|
||
<div className="flex flex-col items-center justify-center h-64">
|
||
<Package className="h-12 w-12 text-white/20 mb-2" />
|
||
<p className="text-white/60">Товары не добавлены</p>
|
||
<p className="text-white/40 text-sm mt-1">Выберите товары из каталога выше для настройки рецептуры</p>
|
||
</div>
|
||
) : (
|
||
allSelectedProducts.map((product) => {
|
||
const recipe = productRecipes[product.id]
|
||
const selectedServicesIds = recipe?.selectedServices || []
|
||
const selectedFFConsumablesIds = recipe?.selectedFFConsumables || []
|
||
const selectedSellerConsumablesIds = recipe?.selectedSellerConsumables || []
|
||
|
||
return (
|
||
<ProductTableRow
|
||
key={product.id}
|
||
product={product}
|
||
recipe={recipe}
|
||
fulfillmentServices={fulfillmentServices}
|
||
fulfillmentConsumables={fulfillmentConsumables}
|
||
sellerConsumables={sellerConsumables}
|
||
selectedServicesIds={selectedServicesIds}
|
||
selectedFFConsumablesIds={selectedFFConsumablesIds}
|
||
selectedSellerConsumablesIds={selectedSellerConsumablesIds}
|
||
selectedFulfillment={selectedFulfillment}
|
||
onQuantityChange={onQuantityChange}
|
||
onRecipeChange={onRecipeChange}
|
||
onRemove={onProductRemove}
|
||
/>
|
||
)
|
||
})
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
})
|
||
|
||
// Компонент строки товара в табличном стиле
|
||
interface ProductTableRowProps {
|
||
product: GoodsProduct & { selectedQuantity: number }
|
||
recipe?: ProductRecipe
|
||
fulfillmentServices: FulfillmentService[]
|
||
fulfillmentConsumables: FulfillmentConsumable[]
|
||
sellerConsumables: SellerConsumable[]
|
||
selectedServicesIds: string[]
|
||
selectedFFConsumablesIds: string[]
|
||
selectedSellerConsumablesIds: string[]
|
||
selectedFulfillment?: string
|
||
onQuantityChange: (productId: string, quantity: number) => void
|
||
onRecipeChange: (productId: string, recipe: ProductRecipe) => void
|
||
onRemove: (productId: string) => void
|
||
}
|
||
|
||
function ProductTableRow({
|
||
product,
|
||
recipe,
|
||
selectedServicesIds,
|
||
selectedFFConsumablesIds,
|
||
selectedSellerConsumablesIds,
|
||
fulfillmentServices,
|
||
fulfillmentConsumables,
|
||
sellerConsumables,
|
||
selectedFulfillment,
|
||
onQuantityChange,
|
||
onRecipeChange,
|
||
onRemove,
|
||
}: ProductTableRowProps) {
|
||
// Расчет стоимости услуг и расходников
|
||
const servicesCost = selectedServicesIds.reduce((sum, serviceId) => {
|
||
const service = fulfillmentServices.find(s => s.id === serviceId)
|
||
return sum + (service ? service.price * product.selectedQuantity : 0)
|
||
}, 0)
|
||
|
||
const ffConsumablesCost = selectedFFConsumablesIds.reduce((sum, consumableId) => {
|
||
const consumable = fulfillmentConsumables.find(c => c.id === consumableId)
|
||
return sum + (consumable ? consumable.price * product.selectedQuantity : 0)
|
||
}, 0)
|
||
|
||
const sellerConsumablesCost = selectedSellerConsumablesIds.reduce((sum, consumableId) => {
|
||
const consumable = sellerConsumables.find(c => c.id === consumableId)
|
||
return sum + (consumable ? (consumable.pricePerUnit || 0) * product.selectedQuantity : 0)
|
||
}, 0)
|
||
|
||
const productCost = product.price * product.selectedQuantity
|
||
const totalCost = productCost + servicesCost + ffConsumablesCost + sellerConsumablesCost
|
||
|
||
return (
|
||
<div className="p-4 rounded-xl bg-white/5 hover:bg-white/10 transition-all duration-200 border border-white/10 relative group">
|
||
{/* КНОПКА УДАЛЕНИЯ */}
|
||
<button
|
||
onClick={() => onRemove(product.id)}
|
||
className="absolute top-2 right-2 text-white/40 hover:text-red-400 transition-colors opacity-0 group-hover:opacity-100 p-1 rounded-lg hover:bg-red-500/10 z-10"
|
||
>
|
||
<X className="h-4 w-4" />
|
||
</button>
|
||
|
||
<div className="grid grid-cols-12 gap-4 items-start">
|
||
{/* ТОВАР (3 колонки) */}
|
||
<div className="col-span-3">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Package className="h-4 w-4 text-cyan-400" />
|
||
<span className="text-sm font-medium text-white/80">Товар</span>
|
||
</div>
|
||
<div className="flex items-center gap-3">
|
||
<div className="relative w-12 h-12 rounded-lg overflow-hidden bg-white/5 flex-shrink-0">
|
||
{product.mainImage || (product.images && product.images[0]) ? (
|
||
<Image src={product.mainImage || product.images[0]} alt={product.name} fill className="object-cover" />
|
||
) : (
|
||
<div className="flex items-center justify-center h-full">
|
||
<Package className="h-5 w-5 text-white/30" />
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div className="min-w-0">
|
||
<h5 className="text-white font-medium text-sm leading-tight line-clamp-2">{product.name}</h5>
|
||
{product.article && <p className="text-white/50 text-xs mt-1">Арт: {product.article}</p>}
|
||
<div className="text-white/80 font-semibold text-xs">
|
||
{product.price.toLocaleString('ru-RU')} ₽/{product.unit || 'шт'}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* КОЛИЧЕСТВО (1 колонка) */}
|
||
<div className="col-span-1">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Sparkles className="h-4 w-4 text-green-400" />
|
||
<span className="text-sm font-medium text-white/80">Кол-во</span>
|
||
</div>
|
||
<div className="space-y-1">
|
||
{product.quantity !== undefined && (
|
||
<div className="flex items-center justify-center gap-1">
|
||
<div className={`w-1.5 h-1.5 rounded-full ${product.quantity > 0 ? 'bg-green-400' : 'bg-red-400'}`} />
|
||
<span className={`text-xs ${product.quantity > 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||
{product.quantity > 0 ? product.quantity : 0}
|
||
</span>
|
||
</div>
|
||
)}
|
||
<div className="flex items-center justify-center gap-1">
|
||
<Input
|
||
type="number"
|
||
min="0"
|
||
max={product.quantity}
|
||
value={product.selectedQuantity || ''}
|
||
onChange={(e) => {
|
||
const inputValue = e.target.value
|
||
const newQuantity = inputValue === '' ? 0 : Math.max(0, parseInt(inputValue) || 0)
|
||
|
||
if (newQuantity > product.quantity) {
|
||
onQuantityChange(product.id, product.quantity)
|
||
return
|
||
}
|
||
|
||
onQuantityChange(product.id, newQuantity)
|
||
}}
|
||
className="glass-input w-14 h-7 text-xs text-center text-white placeholder:text-white/50"
|
||
placeholder="0"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* УСЛУГИ ФФ (2 колонки) */}
|
||
<div className="col-span-2">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Zap className="h-4 w-4 text-purple-400" />
|
||
<span className="text-sm font-medium text-white/80">Услуги ФФ</span>
|
||
</div>
|
||
<div className="flex flex-wrap gap-1">
|
||
{(() => {
|
||
console.log('🎯 Услуги ФФ:', {
|
||
fulfillmentServicesCount: fulfillmentServices.length,
|
||
fulfillmentServices: fulfillmentServices,
|
||
selectedFulfillment: selectedFulfillment
|
||
})
|
||
return null
|
||
})()}
|
||
{fulfillmentServices.length > 0 ? (
|
||
fulfillmentServices.map((service) => {
|
||
const isSelected = selectedServicesIds.includes(service.id)
|
||
return (
|
||
<button
|
||
key={service.id}
|
||
onClick={() => {
|
||
const newSelectedServices = isSelected
|
||
? selectedServicesIds.filter(id => id !== service.id)
|
||
: [...selectedServicesIds, service.id]
|
||
|
||
const newRecipe = {
|
||
selectedServices: newSelectedServices,
|
||
selectedFFConsumables: selectedFFConsumablesIds,
|
||
selectedSellerConsumables: selectedSellerConsumablesIds,
|
||
}
|
||
|
||
console.log('🔧 Услуга ФФ клик:', {
|
||
productId: product.id,
|
||
serviceName: service.name,
|
||
isSelected: isSelected,
|
||
newRecipe: newRecipe
|
||
})
|
||
|
||
onRecipeChange(product.id, newRecipe)
|
||
}}
|
||
className={`px-2 py-1 rounded-lg text-xs font-medium transition-all duration-200 ${
|
||
isSelected
|
||
? 'bg-purple-500/30 border border-purple-400/60 text-purple-200'
|
||
: 'bg-white/10 border border-white/20 text-white/70 hover:bg-purple-500/10'
|
||
}`}
|
||
>
|
||
{service.name} {service.price}₽
|
||
</button>
|
||
)
|
||
})
|
||
) : (
|
||
<div className="text-xs text-white/50">
|
||
{!selectedFulfillment ? 'Выберите ФФ-центр' : 'Нет услуг'}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* РАСХОДНИКИ ФФ (2 колонки) */}
|
||
<div className="col-span-2">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Star className="h-4 w-4 text-orange-400" />
|
||
<span className="text-sm font-medium text-white/80">Расходники ФФ</span>
|
||
</div>
|
||
<div className="flex flex-wrap gap-1">
|
||
{fulfillmentConsumables.length > 0 ? (
|
||
fulfillmentConsumables.map((consumable) => {
|
||
const isSelected = selectedFFConsumablesIds.includes(consumable.id)
|
||
return (
|
||
<button
|
||
key={consumable.id}
|
||
onClick={() => {
|
||
const newSelectedFFConsumables = isSelected
|
||
? selectedFFConsumablesIds.filter(id => id !== consumable.id)
|
||
: [...selectedFFConsumablesIds, consumable.id]
|
||
|
||
onRecipeChange(product.id, {
|
||
selectedServices: selectedServicesIds,
|
||
selectedFFConsumables: newSelectedFFConsumables,
|
||
selectedSellerConsumables: selectedSellerConsumablesIds,
|
||
})
|
||
}}
|
||
className={`px-2 py-1 rounded-lg text-xs font-medium transition-all duration-200 ${
|
||
isSelected
|
||
? 'bg-orange-500/30 border border-orange-400/60 text-orange-200'
|
||
: 'bg-white/10 border border-white/20 text-white/70 hover:bg-orange-500/10'
|
||
}`}
|
||
>
|
||
{consumable.name} {consumable.price}₽
|
||
</button>
|
||
)
|
||
})
|
||
) : (
|
||
<div className="text-xs text-white/50">
|
||
{!selectedFulfillment ? 'Выберите ФФ-центр' : 'Нет расходников'}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* РАСХОДНИКИ СЕЛЛЕРА (2 колонки) */}
|
||
<div className="col-span-2">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Orbit className="h-4 w-4 text-blue-400" />
|
||
<span className="text-sm font-medium text-white/80">Расходники сел.</span>
|
||
</div>
|
||
<div className="flex flex-wrap gap-1">
|
||
{sellerConsumables.length > 0 ? (
|
||
sellerConsumables.map((consumable) => {
|
||
const isSelected = selectedSellerConsumablesIds.includes(consumable.id)
|
||
return (
|
||
<button
|
||
key={consumable.id}
|
||
onClick={() => {
|
||
const newSelectedSellerConsumables = isSelected
|
||
? selectedSellerConsumablesIds.filter(id => id !== consumable.id)
|
||
: [...selectedSellerConsumablesIds, consumable.id]
|
||
|
||
onRecipeChange(product.id, {
|
||
selectedServices: selectedServicesIds,
|
||
selectedFFConsumables: selectedFFConsumablesIds,
|
||
selectedSellerConsumables: newSelectedSellerConsumables,
|
||
})
|
||
}}
|
||
className={`px-2 py-1 rounded-lg text-xs font-medium transition-all duration-200 ${
|
||
isSelected
|
||
? 'bg-blue-500/30 border border-blue-400/60 text-blue-200'
|
||
: 'bg-white/10 border border-white/20 text-white/70 hover:bg-blue-500/10'
|
||
}`}
|
||
>
|
||
{consumable.name} {consumable.pricePerUnit}₽
|
||
</button>
|
||
)
|
||
})
|
||
) : (
|
||
<div className="text-xs text-white/50">Нет расходников</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* МП КАРТОЧКА (1 колонка) */}
|
||
<div className="col-span-1">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Sparkles className="h-4 w-4 text-yellow-400" />
|
||
<span className="text-sm font-medium text-white/80">МП</span>
|
||
</div>
|
||
<MarketplaceCardSelector
|
||
productId={product.id}
|
||
onCardSelect={(productId, cardId) => {
|
||
onRecipeChange(productId, {
|
||
selectedServices: selectedServicesIds,
|
||
selectedFFConsumables: selectedFFConsumablesIds,
|
||
selectedSellerConsumables: selectedSellerConsumablesIds,
|
||
selectedWBCard: cardId === 'none' ? undefined : cardId,
|
||
})
|
||
}}
|
||
selectedCardId={recipe?.selectedWBCard}
|
||
/>
|
||
</div>
|
||
|
||
{/* СТОИМОСТЬ (1 колонка) */}
|
||
<div className="col-span-1">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Star className="h-4 w-4 text-green-400" />
|
||
<span className="text-sm font-medium text-white/80">Сумма</span>
|
||
</div>
|
||
<div className="text-green-400 font-bold text-sm">
|
||
{totalCost.toLocaleString('ru-RU')} ₽
|
||
</div>
|
||
{totalCost > productCost && (
|
||
<div className="text-xs text-white/60 mt-1">
|
||
+{(totalCost - productCost).toLocaleString('ru-RU')} ₽
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
|
||
// КОМПОНЕНТ ВЫБОРА КАРТОЧКИ МАРКЕТПЛЕЙСА
|
||
interface MarketplaceCardSelectorProps {
|
||
productId: string
|
||
onCardSelect?: (productId: string, cardId: string) => void
|
||
selectedCardId?: string
|
||
}
|
||
|
||
function MarketplaceCardSelector({ productId, onCardSelect, selectedCardId }: MarketplaceCardSelectorProps) {
|
||
const { data, loading, error } = useQuery(GET_WB_WAREHOUSE_DATA, {
|
||
fetchPolicy: 'cache-first',
|
||
errorPolicy: 'all',
|
||
})
|
||
|
||
console.log('📦 GET_WB_WAREHOUSE_DATA результат:', {
|
||
loading,
|
||
error: error?.message,
|
||
dataExists: !!data,
|
||
warehouseDataExists: !!data?.getWBWarehouseData,
|
||
cacheExists: !!data?.getWBWarehouseData?.cache,
|
||
rawData: data
|
||
})
|
||
|
||
// Извлекаем карточки из кеша склада WB, как на странице склада
|
||
const wbCards = (() => {
|
||
try {
|
||
console.log('🔍 Структура данных WB:', {
|
||
hasData: !!data,
|
||
hasWBData: !!data?.getWBWarehouseData,
|
||
hasCache: !!data?.getWBWarehouseData?.cache,
|
||
cache: data?.getWBWarehouseData?.cache,
|
||
cacheData: data?.getWBWarehouseData?.cache?.data
|
||
})
|
||
|
||
const cacheData = data?.getWBWarehouseData?.cache?.data
|
||
if (!cacheData) {
|
||
console.log('❌ Нет данных кеша WB')
|
||
return []
|
||
}
|
||
|
||
const parsedData = typeof cacheData === 'string' ? JSON.parse(cacheData) : cacheData
|
||
const stocks = parsedData?.stocks || []
|
||
|
||
console.log('📦 Найдено карточек WB:', stocks.length)
|
||
|
||
return stocks.map((stock: any) => ({
|
||
id: stock.nmId.toString(),
|
||
nmId: stock.nmId,
|
||
vendorCode: stock.vendorCode || '',
|
||
title: stock.title || 'Без названия',
|
||
brand: stock.brand || '',
|
||
}))
|
||
} catch (error) {
|
||
console.error('Ошибка парсинга данных WB склада:', error)
|
||
return []
|
||
}
|
||
})()
|
||
|
||
// Временная отладка
|
||
console.warn('📊 MarketplaceCardSelector WB Warehouse:', {
|
||
loading,
|
||
error: error?.message,
|
||
hasCache: !!data?.getWBWarehouseData?.cache,
|
||
cardsCount: wbCards.length,
|
||
firstCard: wbCards[0],
|
||
})
|
||
|
||
|
||
return (
|
||
<div className="w-20">
|
||
<Select
|
||
value={selectedCardId || 'none'}
|
||
onValueChange={(value) => {
|
||
if (onCardSelect) {
|
||
onCardSelect(productId, value)
|
||
}
|
||
}}
|
||
>
|
||
<SelectTrigger className="glass-input h-7 text-xs border-white/20">
|
||
<SelectValue placeholder={loading ? "..." : "WB"} />
|
||
</SelectTrigger>
|
||
<SelectContent className="glass-card border-white/20 max-h-[200px] overflow-y-auto">
|
||
<SelectItem value="none">Не выбрано</SelectItem>
|
||
{wbCards.length === 0 && !loading && (
|
||
<SelectItem value="no-cards" disabled>
|
||
Карточки WB не найдены
|
||
</SelectItem>
|
||
)}
|
||
{loading && (
|
||
<SelectItem value="loading" disabled>
|
||
Загрузка...
|
||
</SelectItem>
|
||
)}
|
||
{wbCards.map((card: any) => (
|
||
<SelectItem key={card.id} value={card.id}>
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-xs truncate max-w-[150px]">{card.vendorCode || card.nmId}</span>
|
||
{card.title && (
|
||
<span className="text-xs text-white/60 truncate max-w-[100px]">- {card.title}</span>
|
||
)}
|
||
</div>
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
)
|
||
}
|
||
|