2026-03-18 09:38:09 +08:00
import React , { useState , useEffect } from 'react' ;
import {
Card ,
Descriptions ,
Button ,
Space ,
Tag ,
Image ,
Divider ,
Row ,
Col ,
Tabs ,
Table ,
Modal ,
message ,
Spin ,
Typography ,
Tooltip ,
Badge ,
} from 'antd' ;
import {
ShoppingCartOutlined ,
EditOutlined ,
DeleteOutlined ,
CopyOutlined ,
HistoryOutlined ,
ShopOutlined ,
TagOutlined ,
DollarOutlined ,
BoxPlotOutlined ,
FileImageOutlined ,
CheckCircleOutlined ,
CloseCircleOutlined ,
SyncOutlined ,
} from '@ant-design/icons' ;
import type { ColumnsType } from 'antd/es/table' ;
2026-03-19 14:19:01 +08:00
import { productDataSource , Product } from '@/services/productDataSource' ;
2026-03-18 09:38:09 +08:00
const { Title , Text } = Typography ;
const { TabPane } = Tabs ;
interface ProductDetail {
id : string ;
productId : string ;
platform : string ;
name : string ;
description : string ;
category : string ;
brand : string ;
status : 'DRAFT' | 'PENDING' | 'ACTIVE' | 'INACTIVE' | 'SUSPENDED' ;
price : number ;
costPrice : number ;
currency : string ;
stock : number ;
sku : string ;
images : string [ ] ;
attributes : Record < string , string > ;
variants : ProductVariant [ ] ;
complianceStatus : 'COMPLIANT' | 'NON_COMPLIANT' | 'PENDING_REVIEW' ;
certificates : string [ ] ;
createdAt : string ;
updatedAt : string ;
publishedAt? : string ;
}
interface ProductVariant {
id : string ;
sku : string ;
name : string ;
price : number ;
stock : number ;
attributes : Record < string , string > ;
}
interface PriceHistory {
id : string ;
price : number ;
currency : string ;
changedAt : string ;
changedBy : string ;
reason : string ;
}
interface StockHistory {
id : string ;
quantity : number ;
type : 'IN' | 'OUT' ;
reason : string ;
createdAt : string ;
}
const MOCK_PRODUCT : ProductDetail = {
id : '1' ,
productId : 'P-2026-001' ,
platform : 'Amazon' ,
name : 'Industrial Temperature Sensor Pro' ,
description : 'High-precision industrial temperature sensor with digital output. Suitable for manufacturing environments with temperature range -40°C to +125°C.' ,
category : 'Industrial Automation > Sensors' ,
brand : 'TechPro' ,
status : 'ACTIVE' ,
price : 89.99 ,
costPrice : 45.00 ,
currency : 'USD' ,
stock : 256 ,
sku : 'TP-TEMP-001' ,
images : [
'https://via.placeholder.com/400x400?text=Product+1' ,
'https://via.placeholder.com/400x400?text=Product+2' ,
'https://via.placeholder.com/400x400?text=Product+3' ,
] ,
attributes : {
'Temperature Range' : '-40°C to +125°C' ,
'Accuracy' : '±0.5°C' ,
'Output Type' : 'Digital (I2C)' ,
'Power Supply' : '3.3V - 5V DC' ,
'Housing Material' : 'Stainless Steel 316' ,
'IP Rating' : 'IP67' ,
} ,
variants : [
{ id : 'v1' , sku : 'TP-TEMP-001-A' , name : 'Standard Cable 1m' , price : 89.99 , stock : 150 , attributes : { 'Cable Length' : '1m' } } ,
{ id : 'v2' , sku : 'TP-TEMP-001-B' , name : 'Extended Cable 3m' , price : 99.99 , stock : 80 , attributes : { 'Cable Length' : '3m' } } ,
{ id : 'v3' , sku : 'TP-TEMP-001-C' , name : 'Extended Cable 5m' , price : 109.99 , stock : 26 , attributes : { 'Cable Length' : '5m' } } ,
] ,
complianceStatus : 'COMPLIANT' ,
certificates : [ 'CE' , 'FCC' , 'RoHS' ] ,
createdAt : '2025-12-15 10:00:00' ,
updatedAt : '2026-03-18 14:30:00' ,
publishedAt : '2025-12-20 09:00:00' ,
} ;
const MOCK_PRICE_HISTORY : PriceHistory [ ] = [
{ id : '1' , price : 89.99 , currency : 'USD' , changedAt : '2026-03-18 14:30:00' , changedBy : 'System' , reason : 'Market price adjustment' } ,
{ id : '2' , price : 84.99 , currency : 'USD' , changedAt : '2026-02-01 10:00:00' , changedBy : 'Admin' , reason : 'Promotional pricing' } ,
{ id : '3' , price : 94.99 , currency : 'USD' , changedAt : '2025-12-20 09:00:00' , changedBy : 'Admin' , reason : 'Initial listing price' } ,
] ;
const MOCK_STOCK_HISTORY : StockHistory [ ] = [
{ id : '1' , quantity : 50 , type : 'IN' , reason : 'Restock from supplier' , createdAt : '2026-03-15 08:00:00' } ,
{ id : '2' , quantity : 25 , type : 'OUT' , reason : 'Order #ORD-2026-1234' , createdAt : '2026-03-14 16:30:00' } ,
{ id : '3' , quantity : 100 , type : 'IN' , reason : 'Initial stock' , createdAt : '2025-12-20 09:00:00' } ,
] ;
const STATUS_CONFIG : Record < string , { color : string ; text : string } > = {
DRAFT : { color : 'default' , text : 'Draft' } ,
PENDING : { color : 'processing' , text : 'Pending Review' } ,
ACTIVE : { color : 'success' , text : 'Active' } ,
INACTIVE : { color : 'warning' , text : 'Inactive' } ,
SUSPENDED : { color : 'error' , text : 'Suspended' } ,
} ;
const COMPLIANCE_CONFIG : Record < string , { color : string ; icon : React.ReactNode } > = {
COMPLIANT : { color : 'success' , icon : < CheckCircleOutlined / > } ,
NON_COMPLIANT : { color : 'error' , icon : < CloseCircleOutlined / > } ,
PENDING_REVIEW : { color : 'warning' , icon : < SyncOutlined spin / > } ,
} ;
export const ProductDetail : React.FC = ( ) = > {
const [ loading , setLoading ] = useState ( true ) ;
const [ product , setProduct ] = useState < ProductDetail | null > ( null ) ;
const [ priceHistory , setPriceHistory ] = useState < PriceHistory [ ] > ( [ ] ) ;
const [ stockHistory , setStockHistory ] = useState < StockHistory [ ] > ( [ ] ) ;
const [ editModalVisible , setEditModalVisible ] = useState ( false ) ;
useEffect ( ( ) = > {
fetchProductDetail ( ) ;
} , [ ] ) ;
const fetchProductDetail = async ( ) = > {
setLoading ( true ) ;
await new Promise ( resolve = > setTimeout ( resolve , 800 ) ) ;
setProduct ( MOCK_PRODUCT ) ;
setPriceHistory ( MOCK_PRICE_HISTORY ) ;
setStockHistory ( MOCK_STOCK_HISTORY ) ;
setLoading ( false ) ;
} ;
const handleEdit = ( ) = > {
setEditModalVisible ( true ) ;
} ;
const handleDelete = ( ) = > {
Modal . confirm ( {
title : 'Delete Product' ,
content : 'Are you sure you want to delete this product? This action cannot be undone.' ,
okText : 'Delete' ,
okType : 'danger' ,
onOk : ( ) = > {
message . success ( 'Product deleted successfully' ) ;
} ,
} ) ;
} ;
const handleDuplicate = ( ) = > {
message . success ( 'Product duplicated. Redirecting to edit page...' ) ;
} ;
const handlePublish = ( ) = > {
Modal . confirm ( {
title : 'Publish Product' ,
content : 'Publish this product to all connected platforms?' ,
onOk : ( ) = > {
message . success ( 'Product published successfully' ) ;
} ,
} ) ;
} ;
const priceColumns : ColumnsType < PriceHistory > = [
{ title : 'Price' , dataIndex : 'price' , key : 'price' , render : ( v , r ) = > ` ${ r . currency } ${ v . toFixed ( 2 ) } ` } ,
{ title : 'Changed At' , dataIndex : 'changedAt' , key : 'changedAt' } ,
{ title : 'Changed By' , dataIndex : 'changedBy' , key : 'changedBy' } ,
{ title : 'Reason' , dataIndex : 'reason' , key : 'reason' } ,
] ;
const stockColumns : ColumnsType < StockHistory > = [
{
title : 'Type' ,
dataIndex : 'type' ,
key : 'type' ,
render : ( type : string ) = > (
< Tag color = { type === 'IN' ? 'green' : 'red' } >
{ type === 'IN' ? 'Stock In' : 'Stock Out' }
< / Tag >
)
} ,
{ title : 'Quantity' , dataIndex : 'quantity' , key : 'quantity' } ,
{ title : 'Reason' , dataIndex : 'reason' , key : 'reason' } ,
{ title : 'Date' , dataIndex : 'createdAt' , key : 'createdAt' } ,
] ;
const variantColumns : ColumnsType < ProductVariant > = [
{ title : 'SKU' , dataIndex : 'sku' , key : 'sku' } ,
{ title : 'Name' , dataIndex : 'name' , key : 'name' } ,
{ title : 'Price' , dataIndex : 'price' , key : 'price' , render : ( v ) = > ` USD ${ v . toFixed ( 2 ) } ` } ,
{
title : 'Stock' ,
dataIndex : 'stock' ,
key : 'stock' ,
render : ( stock : number ) = > (
< Badge
count = { stock }
showZero
style = { { backgroundColor : stock > 50 ? '#52c41a' : stock > 10 ? '#faad14' : '#ff4d4f' } }
/ >
)
} ,
] ;
if ( loading ) {
return (
< div style = { { textAlign : 'center' , padding : '100px 0' } } >
< Spin size = "large" / >
< / div >
) ;
}
if ( ! product ) {
return < div > Product not found < / div > ;
}
const profitMargin = ( ( product . price - product . costPrice ) / product . price * 100 ) . toFixed ( 1 ) ;
const statusConfig = STATUS_CONFIG [ product . status ] ;
const complianceConfig = COMPLIANCE_CONFIG [ product . complianceStatus ] ;
return (
< div className = "product-detail-page" >
< Card >
< Row gutter = { 24 } >
< Col span = { 8 } >
< Image.PreviewGroup >
< Image
src = { product . images [ 0 ] }
alt = { product . name }
style = { { width : '100%' , borderRadius : 8 } }
/ >
< div style = { { display : 'none' } } >
{ product . images . slice ( 1 ) . map ( ( img , idx ) = > (
< Image key = { idx } src = { img } / >
) ) }
< / div >
< / Image.PreviewGroup >
< Space style = { { marginTop : 16 , width : '100%' } } wrap >
{ product . images . map ( ( img , idx ) = > (
< Image
key = { idx }
src = { img }
width = { 60 }
height = { 60 }
style = { { borderRadius : 4 , objectFit : 'cover' , cursor : 'pointer' } }
/ >
) ) }
< / Space >
< / Col >
< Col span = { 16 } >
< Space direction = "vertical" style = { { width : '100%' } } size = "large" >
< div >
< Space >
< Tag color = "blue" > { product . platform } < / Tag >
< Tag color = { statusConfig . color } > { statusConfig . text } < / Tag >
< Tag color = { complianceConfig . color } icon = { complianceConfig . icon } >
{ product . complianceStatus . replace ( '_' , ' ' ) }
< / Tag >
< / Space >
< Title level = { 3 } style = { { marginTop : 8 , marginBottom : 0 } } > { product . name } < / Title >
< Text type = "secondary" > SKU : { product . sku } | ID : { product . productId } < / Text >
< / div >
< Descriptions column = { 2 } size = "small" >
< Descriptions.Item label = "Category" > { product . category } < / Descriptions.Item >
< Descriptions.Item label = "Brand" > { product . brand } < / Descriptions.Item >
< Descriptions.Item label = { < > < DollarOutlined / > Price < / > } >
< Text strong style = { { fontSize : 18 } } > { product . currency } { product . price . toFixed ( 2 ) } < / Text >
< / Descriptions.Item >
< Descriptions.Item label = "Cost Price" >
{ product . currency } { product . costPrice . toFixed ( 2 ) }
< / Descriptions.Item >
< Descriptions.Item label = "Profit Margin" >
< Tag color = { parseFloat ( profitMargin ) >= 30 ? 'green' : parseFloat ( profitMargin ) >= 15 ? 'orange' : 'red' } >
{ profitMargin } %
< / Tag >
< / Descriptions.Item >
< Descriptions.Item label = { < > < BoxPlotOutlined / > Stock < / > } >
< Badge
count = { product . stock }
showZero
style = { { backgroundColor : product.stock > 50 ? '#52c41a' : product . stock > 10 ? '#faad14' : '#ff4d4f' } }
/ >
< / Descriptions.Item >
< / Descriptions >
< div >
< Text strong > Certificates : < / Text >
{ product . certificates . map ( cert = > (
< Tag key = { cert } icon = { < CheckCircleOutlined / > } color = "green" > { cert } < / Tag >
) ) }
< / div >
< Divider style = { { margin : '12px 0' } } / >
< Space >
< Button type = "primary" icon = { < EditOutlined / > } onClick = { handleEdit } > Edit < / Button >
< Button icon = { < CopyOutlined / > } onClick = { handleDuplicate } > Duplicate < / Button >
< Button icon = { < ShopOutlined / > } onClick = { handlePublish } > Publish < / Button >
< Button danger icon = { < DeleteOutlined / > } onClick = { handleDelete } > Delete < / Button >
< / Space >
< / Space >
< / Col >
< / Row >
< / Card >
< Card style = { { marginTop : 16 } } >
< Tabs defaultActiveKey = "description" >
< TabPane tab = "Description" key = "description" >
< Text style = { { whiteSpace : 'pre-wrap' } } > { product . description } < / Text >
< / TabPane >
< TabPane tab = "Attributes" key = "attributes" >
< Descriptions column = { 2 } bordered size = "small" >
{ Object . entries ( product . attributes ) . map ( ( [ key , value ] ) = > (
< Descriptions.Item key = { key } label = { key } > { value } < / Descriptions.Item >
) ) }
< / Descriptions >
< / TabPane >
< TabPane tab = "Variants" key = "variants" >
< Table
columns = { variantColumns }
dataSource = { product . variants }
rowKey = "id"
pagination = { false }
size = "small"
/ >
< / TabPane >
< TabPane tab = { < > < HistoryOutlined / > Price History < / > } key = "priceHistory" >
< Table
columns = { priceColumns }
dataSource = { priceHistory }
rowKey = "id"
pagination = { { pageSize : 5 } }
size = "small"
/ >
< / TabPane >
< TabPane tab = { < > < BoxPlotOutlined / > Stock History < / > } key = "stockHistory" >
< Table
columns = { stockColumns }
dataSource = { stockHistory }
rowKey = "id"
pagination = { { pageSize : 5 } }
size = "small"
/ >
< / TabPane >
< / Tabs >
< / Card >
< Modal
title = "Edit Product"
open = { editModalVisible }
onCancel = { ( ) = > setEditModalVisible ( false ) }
footer = { null }
width = { 800 }
>
< p > Edit form will be implemented here . . . < / p >
< / Modal >
< / div >
) ;
} ;
export default ProductDetail ;