o2o-negosium-original/frontend/src/features/chat/components/ItemSection.tsx

129 lines
4.3 KiB
TypeScript

import { useState, useCallback, useRef, useEffect } from 'react'
import { createPortal } from 'react-dom'
import { cn } from '@/lib'
import { ItemImage } from '@/features/chat/components/ItemImage'
import { useChatInitStore } from '@/features/chat/stores/useChatInitStore'
export function ItemSection() {
return (
<div className="flex flex-col w-full flex-1 overflow-hidden min-h-0">
<ItemImage />
<ItemInfo />
</div>
)
}
interface TooltipState {
text: string
x: number
y: number
above?: boolean
}
function Tooltip({ text, x, y, above }: TooltipState) {
return createPortal(
<div
className="fixed z-[9999] pointer-events-none"
style={{ left: x, top: y, transform: above ? 'translateY(-100%)' : undefined }}
>
<div className="px-3 py-2 mb-1 rounded-lg bg-neutral-20 text-neutral-90 text-[11px] leading-[16px] whitespace-pre-wrap break-keep max-w-[240px] shadow-md border border-neutral-30">
{text}
</div>
</div>,
document.body,
)
}
function ItemInfo() {
const {
item_code,
item_price,
item_vat_yn,
item_model_name,
item_maker_name,
item_min_order_quantity,
item_lead_time,
item_spec,
item_name,
} = useChatInitStore()
const [tooltip, setTooltip] = useState<TooltipState | null>(null)
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
const showTooltip = useCallback((e: React.MouseEvent, text: string, above = false) => {
if (hideTimer.current) clearTimeout(hideTimer.current)
if (above) {
const rect = e.currentTarget.getBoundingClientRect()
setTooltip({ text, x: rect.left, y: rect.top - 8, above })
} else {
setTooltip({ text, x: e.clientX, y: e.clientY + 16, above })
}
}, [])
const hideTooltip = useCallback(() => {
hideTimer.current = setTimeout(() => setTooltip(null), 100)
}, [])
useEffect(() => () => {
if (hideTimer.current) clearTimeout(hideTimer.current)
}, [])
const formatPrice = (price: number) => price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
const isNewItem = !item_code && !item_price
const priceText = isNewItem ? '신규' : `${formatPrice(item_price)}원`
const formattedPrice = `${priceText}(${item_vat_yn || 'VAT별도'})`
const renderRow = (title: string, data: string, important = false) => {
const textClass = important ? 'title-3 text-neutral-90' : 'body-3 text-neutral-70'
const displayData = data || '-'
return (
<div className="flex items-start self-stretch gap-1 min-w-0">
<div className={cn(textClass, 'w-[100px]')}>{title}</div>
<div
className={cn(textClass, 'w-[150px] break-keep cursor-default')}
onMouseEnter={(e) => displayData !== '-' && showTooltip(e, displayData)}
onMouseLeave={hideTooltip}
>
{displayData}
</div>
</div>
)
}
return (
<div className="flex flex-col w-full flex-1 overflow-hidden min-h-0">
<div
className="text-center headline-3 text-neutral-90 mb-4 truncate px-8 flex-shrink-0 cursor-default"
onMouseEnter={(e) => item_name && showTooltip(e, item_name)}
onMouseLeave={hideTooltip}
>
{item_name}
</div>
<div className="flex flex-1 items-start self-stretch mr-2 overflow-y-auto pr-6 pl-8 min-h-0">
<div className="flex flex-col items-start self-stretch flex-[1_0_auto] gap-2 min-w-0">
{renderRow('상품코드', item_code, true)}
{renderRow('단가', formattedPrice, true)}
{renderRow('모델명', item_model_name, true)}
{renderRow('제조사', item_maker_name)}
{renderRow('최소주문수량', item_min_order_quantity)}
{renderRow('리드타임', item_lead_time)}
<div className="flex items-start self-stretch gap-1 flex-[1_0]">
<div className="body-3 text-neutral-70 w-[100px]">규격</div>
<div
className="body-3 text-neutral-70 self-stretch w-0 flex-[1_0] overflow-hidden whitespace-normal break-words break-keep cursor-default"
onMouseEnter={(e) => item_spec && showTooltip(e, item_spec, true)}
onMouseLeave={hideTooltip}
>
{item_spec || '-'}
</div>
</div>
</div>
</div>
{tooltip && <Tooltip {...tooltip} />}
</div>
)
}