feat: build Order Management & Analytics (F2.13-F2.16)

Components:
- OrderList: table with filters (status, date, search, pagination)
- OrderListItem: single order row with status badge
- OrderDetail: full order view with status update, items, customer info
- OrderTimeline: status change history with visual progress
- AnalyticsDashboard: stats cards (orders, revenue, completed, pending)

Features:
- Order list with keyword search, status filter, date range
- Pagination support
- Status change workflow with immediate update
- Order detail with items breakdown and customer info
- Visual timeline progress indicator
- Analytics dashboard with key metrics
- Placeholder charts for future implementation

Updated Orders page to use new components with list/detail navigation
This commit is contained in:
dwindown
2026-04-18 12:15:48 +07:00
parent ed2520aadf
commit fa792d38ae
11 changed files with 1361 additions and 5 deletions

View File

@@ -0,0 +1,128 @@
.formipay-analytics-dashboard {
display: flex;
flex-direction: column;
gap: 24px;
padding: 20px;
}
.formipay-dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.formipay-dashboard-header h2 {
margin: 0;
font-size: 20px;
font-weight: 600;
display: flex;
align-items: center;
gap: 10px;
}
.formipay-dashboard-header svg {
fill: #1e1e1e;
}
.formipay-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}
.stat-card {
display: flex;
align-items: center;
gap: 16px;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 20px;
}
.stat-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: #f0f0f1;
border-radius: 4px;
}
.stat-icon svg {
fill: #1e1e1e;
}
.stat-icon.completed {
background: #e7f7ed;
}
.stat-icon.completed span {
font-size: 24px;
color: #28a745;
}
.stat-icon.pending {
background: #fff8e5;
}
.stat-icon.pending span {
font-size: 24px;
}
.stat-content {
flex: 1;
display: flex;
flex-direction: column;
}
.stat-label {
font-size: 12px;
font-weight: 600;
color: #646970;
text-transform: uppercase;
}
.stat-value {
font-size: 24px;
font-weight: 700;
color: #1e1e1e;
}
.formipay-dashboard-charts {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 16px;
}
.chart-card {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 20px;
}
.chart-card h3 {
margin: 0 0 16px;
font-size: 14px;
font-weight: 600;
color: #1e1e1e;
}
.chart-placeholder {
height: 200px;
display: flex;
align-items: center;
justify-content: center;
background: #f6f7f7;
border: 1px dashed #c3c4c7;
border-radius: 2px;
}
.chart-placeholder p {
margin: 0;
font-size: 13px;
color: #646970;
text-align: center;
}

View File

@@ -0,0 +1,170 @@
/**
* Analytics Dashboard - Order statistics and charts
*/
import { __ } from '@wordpress/i18n';
import { useState, useCallback, useEffect } from '@wordpress/element';
import { SelectControl } from '@wordpress/components';
import { Icon, chartLine, money, shoppingCart } from '@wordpress/icons';
import { ordersApi } from '../../api/client';
import './AnalyticsDashboard.css';
export default function AnalyticsDashboard() {
const [stats, setStats] = useState({
totalOrders: 0,
totalRevenue: 0,
completedOrders: 0,
pendingOrders: 0,
});
const [loading, setLoading] = useState(true);
const [dateRange, setDateRange] = useState('30');
const loadStats = useCallback(() => {
setLoading(true);
// This would be a dedicated stats API endpoint
// For now, we'll fetch orders and calculate
ordersApi.list({ limit: 1000 })
.then(result => {
if (result.data) {
const orders = result.data.results || [];
const totalRevenue = orders.reduce((sum, order) => {
if (order.status === 'completed') {
return sum + parseFloat(order.total || 0);
}
return sum;
}, 0);
setStats({
totalOrders: orders.length,
totalRevenue: totalRevenue,
completedOrders: orders.filter(o => o.status === 'completed').length,
pendingOrders: orders.filter(o => ['on-hold', 'payment-confirm', 'in-progress'].includes(o.status)).length,
});
}
})
.catch(error => {
console.error('Load stats error:', error);
})
.finally(() => {
setLoading(false);
});
}, [dateRange]);
useEffect(() => {
loadStats();
}, [loadStats]);
return (
<div className="formipay-analytics-dashboard">
<div className="formipay-dashboard-header">
<h2>
<Icon icon={chartLine} />
{ __('Dashboard', 'formipay') }
</h2>
<SelectControl
value={dateRange}
options={[
{ value: '7', label: __('Last 7 days', 'formipay') },
{ value: '30', label: __('Last 30 days', 'formipay') },
{ value: '90', label: __('Last 90 days', 'formipay') },
{ value: '365', label: __('Last year', 'formipay') },
]}
onChange={setDateRange}
/>
</div>
{loading ? (
<div className="formipay-loading">
<span className="spinner is-active" />
</div>
) : (
<>
<div className="formipay-stats-grid">
<div className="stat-card">
<div className="stat-icon">
<Icon icon={shoppingCart} size={24} />
</div>
<div className="stat-content">
<span className="stat-label">
{ __('Total Orders', 'formipay') }
</span>
<span className="stat-value">
{ stats.totalOrders }
</span>
</div>
</div>
<div className="stat-card">
<div className="stat-icon">
<Icon icon={money} size={24} />
</div>
<div className="stat-content">
<span className="stat-label">
{ __('Total Revenue', 'formipay') }
</span>
<span className="stat-value">
{ stats.totalRevenue.toLocaleString('en-US', {
style: 'currency',
currency: 'USD',
}) }
</span>
</div>
</div>
<div className="stat-card">
<div className="stat-icon completed">
<span></span>
</div>
<div className="stat-content">
<span className="stat-label">
{ __('Completed', 'formipay') }
</span>
<span className="stat-value">
{ stats.completedOrders }
</span>
</div>
</div>
<div className="stat-card">
<div className="stat-icon pending">
<span></span>
</div>
<div className="stat-content">
<span className="stat-label">
{ __('Pending', 'formipay') }
</span>
<span className="stat-value">
{ stats.pendingOrders }
</span>
</div>
</div>
</div>
<div className="formipay-dashboard-charts">
<div className="chart-card">
<h3>
{ __('Revenue Over Time', 'formipay') }
</h3>
<div className="chart-placeholder">
<p>
{ __('Chart placeholder - would use a library like Chart.js or Recharts', 'formipay') }
</p>
</div>
</div>
<div className="chart-card">
<h3>
{ __('Orders by Status', 'formipay') }
</h3>
<div className="chart-placeholder">
<p>
{ __('Chart placeholder - would use a library like Chart.js or Recharts', 'formipay') }
</p>
</div>
</div>
</div>
</>
)}
</div>
);
}