gbmake-payload/src/components/fields/PreorderOrdersField.tsx

269 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useField, useFormFields } from '@payloadcms/ui'
import React, { useEffect, useState } from 'react'
interface PreorderItem {
id: string
title: string
variant_id: string
variant_sku?: string
variant_title?: string
quantity: number
unit_price: number
total: number
}
interface Order {
id: string
display_id: number
status: string
payment_status: string
fulfillment_status: string
email: string
total: number
preorder_amount: number
preorder_quantity: number
currency_code: string
created_at: string
preorder_items: PreorderItem[]
}
interface Statistics {
total_orders: number
total_quantity: number
total_amount: number
status_breakdown: Record<string, number>
}
export const PreorderOrdersField: React.FC = () => {
const { value: medusaId } = useField<string>({ path: 'medusaId' })
const { value: seedId } = useField<string>({ path: 'seedId' })
const [orders, setOrders] = useState<Order[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [stats, setStats] = useState<Statistics | null>(null)
useEffect(() => {
if (medusaId || seedId) {
fetchOrders()
}
}, [medusaId, seedId])
const fetchOrders = async () => {
const productId = seedId || medusaId
if (!productId) return
try {
setLoading(true)
setError(null)
const response = await fetch(`/api/preorders/${productId}/orders`)
if (!response.ok) {
throw new Error('Failed to fetch orders')
}
const data = await response.json()
setOrders(data.orders || [])
setStats(data.statistics ?? null)
} catch (err: any) {
console.error('Failed to fetch orders:', err)
setError(err.message || 'Failed to load orders')
} finally {
setLoading(false)
}
}
const formatCurrency = (amount: number, currency: string) => {
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: currency.toUpperCase(),
}).format(amount / 100)
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
}
if (!medusaId && !seedId) {
return (
<div style={{ padding: '1rem', background: '#f5f5f5', borderRadius: '4px', marginBottom: '1rem' }}>
<p style={{ margin: 0, color: '#666' }}>
Medusa
</p>
</div>
)
}
if (loading) {
return (
<div style={{ padding: '1rem', background: '#f5f5f5', borderRadius: '4px', marginBottom: '1rem' }}>
<p style={{ margin: 0 }}>...</p>
</div>
)
}
if (error) {
return (
<div style={{ padding: '1rem', background: '#fee', borderRadius: '4px', marginBottom: '1rem', border: '1px solid #fcc' }}>
<p style={{ margin: 0, color: '#c00' }}>: {error}</p>
<button
onClick={fetchOrders}
style={{
marginTop: '0.5rem',
padding: '0.25rem 0.5rem',
background: '#fff',
border: '1px solid #ccc',
borderRadius: '4px',
cursor: 'pointer',
}}
>
</button>
</div>
)
}
if (orders.length === 0) {
return (
<div style={{ padding: '1rem', background: '#f5f5f5', borderRadius: '4px', marginBottom: '1rem' }}>
<p style={{ margin: 0, color: '#666' }}></p>
<button
onClick={fetchOrders}
style={{
marginTop: '0.5rem',
padding: '0.25rem 0.5rem',
background: '#fff',
border: '1px solid #ccc',
borderRadius: '4px',
cursor: 'pointer',
}}
>
</button>
</div>
)
}
return (
<div style={{ marginBottom: '1rem' }}>
{/* 统计信息 */}
{stats && (
<div style={{
padding: '1rem',
background: '#e3f2fd',
borderRadius: '4px',
marginBottom: '1rem',
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))',
gap: '1rem',
}}>
<div>
<div style={{ fontSize: '0.875rem', color: '#666', marginBottom: '0.25rem' }}></div>
<div style={{ fontSize: '1.5rem', fontWeight: 'bold', color: '#1976d2' }}>{stats.total_orders}</div>
</div>
<div>
<div style={{ fontSize: '0.875rem', color: '#666', marginBottom: '0.25rem' }}></div>
<div style={{ fontSize: '1.5rem', fontWeight: 'bold', color: '#1976d2' }}>{stats.total_quantity}</div>
</div>
<div>
<div style={{ fontSize: '0.875rem', color: '#666', marginBottom: '0.25rem' }}></div>
<div style={{ fontSize: '1.5rem', fontWeight: 'bold', color: '#1976d2' }}>
{formatCurrency(stats.total_amount, orders[0]?.currency_code || 'CNY')}
</div>
</div>
<div style={{ display: 'flex', alignItems: 'flex-end' }}>
<button
onClick={fetchOrders}
style={{
padding: '0.5rem 1rem',
background: '#fff',
border: '1px solid #1976d2',
borderRadius: '4px',
cursor: 'pointer',
color: '#1976d2',
fontSize: '0.875rem',
}}
>
🔄
</button>
</div>
</div>
)}
{/* 订单列表 */}
<div style={{
border: '1px solid #e0e0e0',
borderRadius: '4px',
overflow: 'hidden',
}}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ background: '#f5f5f5', borderBottom: '2px solid #e0e0e0' }}>
<th style={{ padding: '0.75rem', textAlign: 'left', fontSize: '0.875rem', fontWeight: 600 }}></th>
<th style={{ padding: '0.75rem', textAlign: 'left', fontSize: '0.875rem', fontWeight: 600 }}></th>
<th style={{ padding: '0.75rem', textAlign: 'left', fontSize: '0.875rem', fontWeight: 600 }}></th>
<th style={{ padding: '0.75rem', textAlign: 'right', fontSize: '0.875rem', fontWeight: 600 }}></th>
<th style={{ padding: '0.75rem', textAlign: 'center', fontSize: '0.875rem', fontWeight: 600 }}></th>
<th style={{ padding: '0.75rem', textAlign: 'left', fontSize: '0.875rem', fontWeight: 600 }}></th>
</tr>
</thead>
<tbody>
{orders.map((order, index) => (
<tr
key={order.id}
style={{
borderBottom: index < orders.length - 1 ? '1px solid #e0e0e0' : 'none',
background: index % 2 === 0 ? '#fff' : '#fafafa',
}}
>
<td style={{ padding: '0.75rem', fontSize: '0.875rem' }}>
<div style={{ fontWeight: 600 }}>#{order.display_id}</div>
<div style={{ fontSize: '0.75rem', color: '#999' }}>{order.id.slice(0, 8)}</div>
</td>
<td style={{ padding: '0.75rem', fontSize: '0.875rem' }}>
{order.email}
</td>
<td style={{ padding: '0.75rem', fontSize: '0.875rem' }}>
{(order.preorder_items || []).map((item, i) => (
<div key={i} style={{ marginBottom: i < order.preorder_items.length - 1 ? '0.25rem' : 0 }}>
{item.variant_title ? `${item.title} · ${item.variant_title}` : item.title} × {item.quantity}
</div>
))}
</td>
<td style={{ padding: '0.75rem', fontSize: '0.875rem', textAlign: 'right', fontWeight: 600 }}>
{formatCurrency(order.total, order.currency_code)}
</td>
<td style={{ padding: '0.75rem', textAlign: 'center' }}>
<span style={{
display: 'inline-block',
padding: '0.25rem 0.5rem',
borderRadius: '4px',
fontSize: '0.75rem',
fontWeight: 600,
background: order.status === 'completed' ? '#e8f5e9' : '#fff3e0',
color: order.status === 'completed' ? '#2e7d32' : '#f57c00',
}}>
{order.status}
</span>
</td>
<td style={{ padding: '0.75rem', fontSize: '0.875rem' }}>
{formatDate(order.created_at)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}