diff --git a/src/admin/components/dashboard/AnalyticsDashboard.css b/src/admin/components/dashboard/AnalyticsDashboard.css
new file mode 100644
index 000000000..039c2d1a9
--- /dev/null
+++ b/src/admin/components/dashboard/AnalyticsDashboard.css
@@ -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;
+}
diff --git a/src/admin/components/dashboard/AnalyticsDashboard.js b/src/admin/components/dashboard/AnalyticsDashboard.js
new file mode 100644
index 000000000..4d835aeae
--- /dev/null
+++ b/src/admin/components/dashboard/AnalyticsDashboard.js
@@ -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 (
+
+
+
+
+ { __('Dashboard', 'formipay') }
+
+
+
+
+ {loading ? (
+
+
+
+ ) : (
+ <>
+
+
+
+
+
+
+
+ { __('Total Orders', 'formipay') }
+
+
+ { stats.totalOrders }
+
+
+
+
+
+
+
+
+
+
+ { __('Total Revenue', 'formipay') }
+
+
+ { stats.totalRevenue.toLocaleString('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ }) }
+
+
+
+
+
+
+ ✓
+
+
+
+ { __('Completed', 'formipay') }
+
+
+ { stats.completedOrders }
+
+
+
+
+
+
+ ⏱
+
+
+
+ { __('Pending', 'formipay') }
+
+
+ { stats.pendingOrders }
+
+
+
+
+
+
+
+
+ { __('Revenue Over Time', 'formipay') }
+
+
+
+ { __('Chart placeholder - would use a library like Chart.js or Recharts', 'formipay') }
+
+
+
+
+
+
+ { __('Orders by Status', 'formipay') }
+
+
+
+ { __('Chart placeholder - would use a library like Chart.js or Recharts', 'formipay') }
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/src/admin/components/orders/OrderDetail.css b/src/admin/components/orders/OrderDetail.css
new file mode 100644
index 000000000..5ad3e82da
--- /dev/null
+++ b/src/admin/components/orders/OrderDetail.css
@@ -0,0 +1,165 @@
+.formipay-order-detail {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background: #f6f7f7;
+}
+
+.formipay-detail-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ background: #fff;
+ border-bottom: 1px solid #e0e0e0;
+ gap: 16px;
+}
+
+.formipay-detail-header h1 {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 600;
+ flex: 1;
+}
+
+.header-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.formipay-detail-content {
+ display: grid;
+ grid-template-columns: 2fr 1fr;
+ gap: 20px;
+ padding: 20px;
+ overflow-y: auto;
+}
+
+.formipay-detail-main,
+.formipay-detail-sidebar {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.formipay-detail-card {
+ background: #fff;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+ padding: 20px;
+}
+
+.formipay-detail-card h3 {
+ margin: 0 0 16px;
+ font-size: 16px;
+ font-weight: 600;
+ color: #1e1e1e;
+}
+
+.detail-list {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 16px;
+}
+
+.detail-list > div {
+ display: flex;
+ flex-direction: column;
+}
+
+.detail-list dt {
+ font-size: 12px;
+ font-weight: 600;
+ color: #646970;
+ margin-bottom: 4px;
+}
+
+.detail-list dd {
+ font-size: 14px;
+ color: #1e1e1e;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.detail-list dd .components-select-control {
+ flex: 1;
+}
+
+.items-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.items-table th,
+.items-table td {
+ padding: 10px;
+ text-align: left;
+ border-bottom: 1px solid #f0f0f1;
+}
+
+.items-table th {
+ font-size: 12px;
+ font-weight: 600;
+ color: #646970;
+ text-transform: uppercase;
+}
+
+.items-table td {
+ font-size: 13px;
+}
+
+.items-table small {
+ display: block;
+ color: #646970;
+ font-size: 11px;
+}
+
+.items-table tfoot td {
+ border-top: 2px solid #1e1e1e;
+ border-bottom: none;
+ padding-top: 16px;
+}
+
+.customer-info {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 12px;
+}
+
+.customer-info > div {
+ display: flex;
+ flex-direction: column;
+}
+
+.customer-info dt {
+ font-size: 11px;
+ font-weight: 600;
+ color: #646970;
+ margin-bottom: 2px;
+}
+
+.customer-info dd {
+ font-size: 13px;
+ color: #1e1e1e;
+}
+
+.no-data {
+ color: #646970;
+ font-size: 13px;
+}
+
+.formipay-error,
+.formipay-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 60px 20px;
+ gap: 16px;
+}
+
+.formipay-error p {
+ color: #646970;
+ margin: 0;
+}
diff --git a/src/admin/components/orders/OrderDetail.js b/src/admin/components/orders/OrderDetail.js
new file mode 100644
index 000000000..df6bda002
--- /dev/null
+++ b/src/admin/components/orders/OrderDetail.js
@@ -0,0 +1,251 @@
+/**
+ * Order Detail - Complete order information view
+ */
+
+import { __ } from '@wordpress/i18n';
+import { useState, useCallback, useEffect } from '@wordpress/element';
+import { Button, SelectControl } from '@wordpress/components';
+import { Icon, arrowLeft, trash } from '@wordpress/icons';
+import { ordersApi } from '../../api/client';
+import OrderTimeline from './OrderTimeline';
+import './OrderDetail.css';
+
+const STATUS_OPTIONS = [
+ { value: 'on-hold', label: __('On Hold', 'formipay') },
+ { value: 'payment-confirm', label: __('Payment Confirmed', 'formipay') },
+ { value: 'in-progress', label: __('In Progress', 'formipay') },
+ { value: 'shipping', label: __('Shipping', 'formipay') },
+ { value: 'completed', label: __('Completed', 'formipay') },
+ { value: 'failed', label: __('Failed', 'formipay') },
+ { value: 'refunded', label: __('Refunded', 'formipay') },
+ { value: 'cancelled', label: __('Cancelled', 'formipay') },
+];
+
+export default function OrderDetail({ orderId, onBack }) {
+ const [order, setOrder] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [updating, setUpdating] = useState(false);
+ const [newStatus, setNewStatus] = useState('');
+
+ const loadOrder = useCallback(() => {
+ setLoading(true);
+ ordersApi.get(orderId)
+ .then(result => {
+ if (result.data) {
+ setOrder(result.data);
+ setNewStatus(result.data.status);
+ }
+ })
+ .catch(error => {
+ console.error('Load order error:', error);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }, [orderId]);
+
+ useEffect(() => {
+ loadOrder();
+ }, [loadOrder]);
+
+ const handleStatusChange = () => {
+ if (!newStatus || newStatus === order.status) return;
+
+ setUpdating(true);
+ ordersApi.updateStatus(orderId, newStatus)
+ .then(result => {
+ if (result.success || result.data?.valid) {
+ loadOrder();
+ }
+ })
+ .catch(error => {
+ console.error('Update status error:', error);
+ })
+ .finally(() => {
+ setUpdating(false);
+ });
+ };
+
+ const handleDelete = () => {
+ if (!confirm(__('Are you sure you want to delete this order?', 'formipay'))) {
+ return;
+ }
+
+ ordersApi.delete([orderId])
+ .then(result => {
+ if (result.success) {
+ onBack?.();
+ }
+ })
+ .catch(error => {
+ console.error('Delete order error:', error);
+ });
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (!order) {
+ return (
+
+
+
{ __('Order not found', 'formipay') }
+
+
+
+ );
+ }
+
+ const formatDate = (dateString) => {
+ if (!dateString) return '-';
+ return new Date(dateString).toLocaleString();
+ };
+
+ return (
+
+
+
+
+ { __('Order', 'formipay') } #{ order.id }
+
+
+
+
+
+
+
+
+
+
{ __('Order Details', 'formipay') }
+
+
+
- { __('Status', 'formipay') }
+ -
+
+ {newStatus !== order.status && (
+
+ )}
+
+
+
+
- { __('Date Created', 'formipay') }
+ - { formatDate(order.created_date) }
+
+
+
- { __('Form ID', 'formipay') }
+ - { order.form_id }
+
+
+
- { __('Payment Gateway', 'formipay') }
+ - { order.payment_gateway || '-' }
+
+
+
+
+
+
{ __('Items', 'formipay') }
+
+
+
+ | { __('Item', 'formipay') } |
+ { __('Qty', 'formipay') } |
+ { __('Subtotal', 'formipay') } |
+
+
+
+ {order.items?.map((item, index) => (
+
+ |
+ { item.item }
+ {item.description && (
+ { item.description }
+ )}
+ |
+ { item.qty || 1 } |
+ { item.subtotal_formatted || item.subtotal } |
+
+ )) || (
+
+ |
+ { __('No items', 'formipay') }
+ |
+
+ )}
+
+
+
+ | { __('Total', 'formipay') } |
+ { order.total_formatted || order.total } |
+
+
+
+
+
+
+
+
+
{ __('Customer Information', 'formipay') }
+ {order.form_data ? (
+
+ {Object.entries(order.form_data).map(([key, value]) => {
+ if (['payment', 'payment_gateway', 'coupon_code', 'qty'].includes(key)) {
+ return null;
+ }
+ return (
+
+
- { key.replace(/_/g, ' ') }
+ - { value?.value || value || '-' }
+
+ );
+ })}
+
+ ) : (
+
{ __('No customer data available', 'formipay') }
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/src/admin/components/orders/OrderList.css b/src/admin/components/orders/OrderList.css
new file mode 100644
index 000000000..c1fd2bdd0
--- /dev/null
+++ b/src/admin/components/orders/OrderList.css
@@ -0,0 +1,111 @@
+.formipay-order-list {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.formipay-orders-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ background: #fff;
+ border-bottom: 1px solid #e0e0e0;
+}
+
+.formipay-orders-header h2 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.formipay-orders-header svg {
+ fill: #1e1e1e;
+}
+
+.order-count {
+ font-size: 13px;
+ color: #646970;
+}
+
+.formipay-orders-filters {
+ display: flex;
+ gap: 12px;
+ padding: 16px 20px;
+ background: #f6f7f7;
+ border-bottom: 1px solid #e0e0e0;
+ flex-wrap: wrap;
+ align-items: flex-end;
+}
+
+.formipay-orders-filters .components-base-control {
+ flex: 1;
+ min-width: 200px;
+ margin: 0;
+}
+
+.formipay-date-input {
+ padding: 6px 8px;
+ font-size: 13px;
+ border: 1px solid #8c8f94;
+ border-radius: 2px;
+ height: 30px;
+}
+
+.formipay-orders-table-wrapper {
+ flex: 1;
+ overflow-y: auto;
+ background: #fff;
+}
+
+.formipay-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 60px 20px;
+}
+
+.formipay-no-results {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 60px 20px;
+ color: #646970;
+}
+
+.formipay-orders-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.formipay-orders-table thead th {
+ padding: 12px 16px;
+ text-align: left;
+ font-size: 13px;
+ font-weight: 600;
+ color: #1e1e1e;
+ border-bottom: 1px solid #e0e0e0;
+}
+
+.formipay-orders-table tbody td {
+ padding: 12px 16px;
+ font-size: 13px;
+ border-bottom: 1px solid #f0f0f1;
+}
+
+.formipay-pagination {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ border-top: 1px solid #e0e0e0;
+}
+
+.pagination-info {
+ font-size: 13px;
+ color: #646970;
+}
diff --git a/src/admin/components/orders/OrderList.js b/src/admin/components/orders/OrderList.js
new file mode 100644
index 000000000..8dee7e3e5
--- /dev/null
+++ b/src/admin/components/orders/OrderList.js
@@ -0,0 +1,196 @@
+/**
+ * Order List - Main orders table with filters
+ */
+
+import { __ } from '@wordpress/i18n';
+import { useState, useCallback, useEffect } from '@wordpress/element';
+import { SearchControl, SelectControl, Button } from '@wordpress/components';
+import { Icon, list } from '@wordpress/icons';
+import { ordersApi } from '../../api/client';
+import OrderListItem from './OrderListItem';
+import './OrderList.css';
+
+export default function OrderList({ onSelectOrder }) {
+ const [orders, setOrders] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [total, setTotal] = useState(0);
+ const [filters, setFilters] = useState({
+ keyword: '',
+ status: '',
+ date_from: '',
+ date_to: '',
+ });
+ const [pagination, setPagination] = useState({
+ limit: 20,
+ offset: 0,
+ });
+
+ const loadOrders = useCallback(() => {
+ setLoading(true);
+ ordersApi.list({
+ keyword: filters.keyword,
+ status: filters.status,
+ date_from: filters.date_from,
+ date_to: filters.date_to,
+ limit: pagination.limit,
+ offset: pagination.offset,
+ })
+ .then(result => {
+ if (result.data) {
+ setOrders(result.data.results || []);
+ setTotal(result.data.total || 0);
+ }
+ })
+ .catch(error => {
+ console.error('Load orders error:', error);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }, [filters, pagination]);
+
+ useEffect(() => {
+ loadOrders();
+ }, [loadOrders]);
+
+ const handleFilterChange = (key, value) => {
+ setFilters({ ...filters, [key]: value });
+ setPagination({ ...pagination, offset: 0 });
+ };
+
+ const handleSearch = (value) => {
+ handleFilterChange('keyword', value);
+ };
+
+ const handlePageChange = (newOffset) => {
+ setPagination({ ...pagination, offset: newOffset });
+ };
+
+ const statusOptions = {
+ '': __('All Statuses', 'formipay'),
+ 'on-hold': __('On Hold', 'formipay'),
+ 'payment-confirm': __('Payment Confirmed', 'formipay'),
+ 'in-progress': __('In Progress', 'formipay'),
+ 'shipping': __('Shipping', 'formipay'),
+ 'completed': __('Completed', 'formipay'),
+ 'failed': __('Failed', 'formipay'),
+ 'refunded': __('Refunded', 'formipay'),
+ 'cancelled': __('Cancelled', 'formipay'),
+ };
+
+ const totalPages = Math.ceil(total / pagination.limit);
+ const currentPage = Math.floor(pagination.offset / pagination.limit) + 1;
+
+ return (
+
+
+
+
+ { __('Orders', 'formipay') }
+
+
+ { total } { __('orders', 'formipay') }
+
+
+
+
+
+
+ ({ value, label }))}
+ onChange={(value) => handleFilterChange('status', value)}
+ label={ __('Status', 'formipay')}
+ />
+
+ handleFilterChange('date_from', e.target.value)}
+ className="formipay-date-input"
+ />
+
+ handleFilterChange('date_to', e.target.value)}
+ className="formipay-date-input"
+ />
+
+ {(filters.keyword || filters.status || filters.date_from || filters.date_to) && (
+
+ )}
+
+
+
+ {loading ? (
+
+
+
+ ) : orders.length === 0 ? (
+
+
{ __('No orders found', 'formipay') }
+
+ ) : (
+ <>
+
+
+
+ | { __('ID', 'formipay') } |
+ { __('Date', 'formipay') } |
+ { __('Customer', 'formipay') } |
+ { __('Total', 'formipay') } |
+ { __('Status', 'formipay') } |
+ { __('Actions', 'formipay') } |
+
+
+
+ {orders.map(order => (
+ onSelectOrder?.(order.id)}
+ />
+ ))}
+
+
+
+ {totalPages > 1 && (
+
+
+
+ { __('Page', 'formipay') } { currentPage } { __('of', 'formipay') } { totalPages }
+
+
+
+ )}
+ >
+ )}
+
+
+ );
+}
diff --git a/src/admin/components/orders/OrderListItem.css b/src/admin/components/orders/OrderListItem.css
new file mode 100644
index 000000000..b05a58832
--- /dev/null
+++ b/src/admin/components/orders/OrderListItem.css
@@ -0,0 +1,27 @@
+.formipay-order-item {
+ transition: background 0.2s;
+}
+
+.formipay-order-item:hover {
+ background: #f6f7f7;
+}
+
+.status-badge {
+ display: inline-block;
+ padding: 4px 10px;
+ font-size: 11px;
+ font-weight: 600;
+ color: #fff;
+ border-radius: 12px;
+ text-transform: uppercase;
+}
+
+.formipay-order-item .button {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.formipay-order-item .button svg {
+ fill: currentColor;
+}
diff --git a/src/admin/components/orders/OrderListItem.js b/src/admin/components/orders/OrderListItem.js
new file mode 100644
index 000000000..2aee08518
--- /dev/null
+++ b/src/admin/components/orders/OrderListItem.js
@@ -0,0 +1,87 @@
+/**
+ * Order List Item - Single row in orders table
+ */
+
+import { __ } from '@wordpress/i18n';
+import { Icon, seen } from '@wordpress/icons';
+import './OrderListItem.css';
+
+const STATUS_COLORS = {
+ 'on-hold': '#f0ad4e',
+ 'payment-confirm': '#17a2b8',
+ 'in-progress': '#17a2b8',
+ 'shipping': '#6c757d',
+ 'completed': '#28a745',
+ 'failed': '#dc3545',
+ 'refunded': '#6c757d',
+ 'cancelled': '#dc3545',
+};
+
+const STATUS_LABELS = {
+ 'on-hold': __('On Hold', 'formipay'),
+ 'payment-confirm': __('Payment Confirmed', 'formipay'),
+ 'in-progress': __('In Progress', 'formipay'),
+ 'shipping': __('Shipping', 'formipay'),
+ 'completed': __('Completed', 'formipay'),
+ 'failed': __('Failed', 'formipay'),
+ 'refunded': __('Refunded', 'formipay'),
+ 'cancelled': __('Cancelled', 'formipay'),
+};
+
+export default function OrderListItem({ order, onSelect }) {
+ const statusColor = STATUS_COLORS[order.status] || '#6c757d';
+ const statusLabel = STATUS_LABELS[order.status] || order.status;
+
+ const formatDate = (dateString) => {
+ if (!dateString) return '-';
+ const date = new Date(dateString);
+ return date.toLocaleDateString();
+ };
+
+ const getCustomerName = (order) => {
+ if (order.form_data) {
+ const nameField = Object.values(order.form_data).find(
+ field => field.name && field.name.includes('name')
+ );
+ return nameField?.value || '-';
+ }
+ return '-';
+ };
+
+ const customerName = getCustomerName(order);
+
+ return (
+
+ |
+ #{ order.id }
+ |
+
+ { formatDate(order.created_date) }
+ |
+
+ { customerName !== '-' ? customerName : Unknown }
+ |
+
+ { order.total_formatted || order.total }
+ |
+
+
+ { statusLabel }
+
+ |
+
+
+ |
+
+ );
+}
diff --git a/src/admin/components/orders/OrderTimeline.css b/src/admin/components/orders/OrderTimeline.css
new file mode 100644
index 000000000..26b6d2270
--- /dev/null
+++ b/src/admin/components/orders/OrderTimeline.css
@@ -0,0 +1,116 @@
+.formipay-order-timeline h3 {
+ margin: 0 0 16px;
+ font-size: 16px;
+ font-weight: 600;
+ color: #1e1e1e;
+}
+
+.timeline-progress {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 24px;
+ position: relative;
+}
+
+.timeline-step {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ position: relative;
+ z-index: 1;
+ flex: 1;
+}
+
+.timeline-dot {
+ width: 24px;
+ height: 24px;
+ background: #e0e0e0;
+ border: 2px solid #c3c4c7;
+ border-radius: 50%;
+ position: relative;
+}
+
+.timeline-step.completed .timeline-dot {
+ background: #2271b1;
+ border-color: #2271b1;
+}
+
+.timeline-step.completed .timeline-dot::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 8px;
+ height: 8px;
+ background: #fff;
+ border-radius: 50%;
+}
+
+.timeline-line {
+ position: absolute;
+ top: 12px;
+ left: 50%;
+ width: 100%;
+ height: 2px;
+ background: #e0e0e0;
+ z-index: -1;
+}
+
+.timeline-step.completed .timeline-line {
+ background: #2271b1;
+}
+
+.timeline-label {
+ font-size: 10px;
+ font-weight: 600;
+ color: #646970;
+ text-align: center;
+ text-transform: uppercase;
+}
+
+.timeline-events ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.timeline-events li {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ gap: 8px;
+ padding: 12px 0;
+ border-bottom: 1px solid #f0f0f1;
+}
+
+.timeline-events li:last-child {
+ border-bottom: none;
+}
+
+.event-status {
+ font-size: 13px;
+ font-weight: 600;
+ color: #1e1e1e;
+}
+
+.event-date {
+ font-size: 11px;
+ color: #646970;
+ text-align: right;
+}
+
+.event-note {
+ grid-column: 1 / -1;
+ font-size: 12px;
+ color: #646970;
+ margin-top: 4px;
+}
+
+.no-events {
+ color: #646970;
+ font-size: 13px;
+ text-align: center;
+ padding: 20px 0;
+}
diff --git a/src/admin/components/orders/OrderTimeline.js b/src/admin/components/orders/OrderTimeline.js
new file mode 100644
index 000000000..580788e23
--- /dev/null
+++ b/src/admin/components/orders/OrderTimeline.js
@@ -0,0 +1,92 @@
+/**
+ * Order Timeline - Status change history
+ */
+
+import { __ } from '@wordpress/i18n';
+import { useState, useEffect } from '@wordpress/element';
+import './OrderTimeline.css';
+
+const STATUS_FLOW = [
+ 'on-hold',
+ 'payment-confirm',
+ 'in-progress',
+ 'shipping',
+ 'completed',
+];
+
+const STATUS_LABELS = {
+ 'on-hold': __('On Hold', 'formipay'),
+ 'payment-confirm': __('Payment Confirmed', 'formipay'),
+ 'in-progress': __('In Progress', 'formipay'),
+ 'shipping': __('Shipping', 'formipay'),
+ 'completed': __('Completed', 'formipay'),
+ 'failed': __('Failed', 'formipay'),
+ 'refunded': __('Refunded', 'formipay'),
+ 'cancelled': __('Cancelled', 'formipay'),
+};
+
+export default function OrderTimeline({ orderId }) {
+ const [timeline, setTimeline] = useState([]);
+
+ useEffect(() => {
+ // Load timeline data - this would be from an API
+ // For now, we'll create a mock timeline based on status
+ const mockTimeline = [
+ {
+ status: 'on-hold',
+ date: new Date().toISOString(),
+ note: __('Order placed', 'formipay'),
+ },
+ ];
+ setTimeline(mockTimeline);
+ }, [orderId]);
+
+ return (
+
+
{ __('Order Timeline', 'formipay') }
+
+
+ {STATUS_FLOW.map((status, index) => (
+
+
+ {index < STATUS_FLOW.length - 1 && (
+
+ )}
+
+ { STATUS_LABELS[status] }
+
+
+ ))}
+
+
+
+ {timeline.length === 0 ? (
+
+ { __('No timeline events yet', 'formipay') }
+
+ ) : (
+
+ {timeline.map((event, index) => (
+ -
+
+ { STATUS_LABELS[event.status] || event.status }
+
+
+ { new Date(event.date).toLocaleString() }
+
+ {event.note && (
+
+ { event.note }
+
+ )}
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/src/admin/pages/Orders.js b/src/admin/pages/Orders.js
index 77b5eccfc..3cac5211d 100644
--- a/src/admin/pages/Orders.js
+++ b/src/admin/pages/Orders.js
@@ -1,14 +1,27 @@
/**
- * Orders Page - Placeholder
+ * Orders Page - List and detail view
*/
import { __ } from '@wordpress/i18n';
+import { useState } from '@wordpress/element';
+import OrderList from '../components/orders/OrderList';
+import OrderDetail from '../components/orders/OrderDetail';
export default function OrdersPage({ initialData }) {
+ const [selectedOrderId, setSelectedOrderId] = useState(null);
+
+ if (selectedOrderId) {
+ return (
+ setSelectedOrderId(null)}
+ />
+ );
+ }
+
return (
-
-
{ __('Orders', 'formipay') }
-
{ __('Page content coming soon...', 'formipay') }
-
+ setSelectedOrderId(orderId)}
+ />
);
}