mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
170 lines
5.3 KiB
TypeScript
170 lines
5.3 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react'
|
|
import { Breadcrumbs, Pagination } from '@meshkee/dashboard-ui'
|
|
import { OrderItemsModal } from '../components/OrderItemsModal'
|
|
import { OrderRow } from '../components/OrderRow'
|
|
import { ApiError, isAbortError } from '../lib/api'
|
|
import {
|
|
listOrders,
|
|
type Order,
|
|
type OrdersListResponse,
|
|
} from '../services/orderService'
|
|
import { DEFAULT_ORDER_PROCESS_STEPS } from '../utils/orderSteps'
|
|
import pageStyles from '../components/PageContent.module.css'
|
|
import styles from './OrdersPage.module.css'
|
|
|
|
const PAGE_SIZE = 20
|
|
const COLUMN_COUNT = 7
|
|
|
|
export function OrdersPage() {
|
|
const [data, setData] = useState<OrdersListResponse | null>(null)
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState('')
|
|
const [page, setPage] = useState(1)
|
|
const [viewOrder, setViewOrder] = useState<Order | null>(null)
|
|
|
|
useEffect(() => {
|
|
const controller = new AbortController()
|
|
|
|
async function load() {
|
|
setLoading(true)
|
|
setError('')
|
|
|
|
try {
|
|
const response = await listOrders({ page, pageSize: PAGE_SIZE }, controller.signal)
|
|
if (controller.signal.aborted) return
|
|
setData(response)
|
|
} catch (err) {
|
|
if (isAbortError(err) || controller.signal.aborted) return
|
|
setError(err instanceof ApiError ? err.message : 'Unable to load orders.')
|
|
} finally {
|
|
if (!controller.signal.aborted) setLoading(false)
|
|
}
|
|
}
|
|
|
|
void load()
|
|
return () => controller.abort()
|
|
}, [page])
|
|
|
|
const totalPages = useMemo(() => {
|
|
const total = data?.total ?? 0
|
|
return Math.max(1, Math.ceil(total / PAGE_SIZE))
|
|
}, [data?.total])
|
|
|
|
const showingFrom = useMemo(() => {
|
|
if (!data || data.total === 0) return 0
|
|
return (page - 1) * PAGE_SIZE + 1
|
|
}, [data, page])
|
|
|
|
const showingTo = useMemo(() => {
|
|
if (!data) return 0
|
|
return Math.min(data.total, page * PAGE_SIZE)
|
|
}, [data, page])
|
|
|
|
return (
|
|
<main className={pageStyles.content}>
|
|
<Breadcrumbs items={[{ label: 'Home', href: '/' }, { label: 'My Orders' }]} />
|
|
|
|
<div className={pageStyles.pageHeader}>
|
|
<div>
|
|
<h2 className={pageStyles.pageTitle}>My Orders</h2>
|
|
<p className={pageStyles.pageSubtitle}>View your order history and details.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.tablePanel}>
|
|
<div className={styles.tableWrap}>
|
|
<div className={styles.tableHeader}>
|
|
<div className={styles.tableHeaderTitle}>Order list</div>
|
|
<div className={styles.meta}>
|
|
{data ? (
|
|
data.total > 0 ? (
|
|
<>
|
|
Showing {showingFrom} - {showingTo} of {data.total}
|
|
</>
|
|
) : (
|
|
'No orders'
|
|
)
|
|
) : (
|
|
' '
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{error && <div className={styles.errorBanner}>{error}</div>}
|
|
|
|
<table className={styles.table}>
|
|
<colgroup>
|
|
<col className={styles.colOrderId} />
|
|
<col className={styles.colItems} />
|
|
<col className={styles.colTotal} />
|
|
<col className={styles.colDate} />
|
|
<col className={styles.colStep} />
|
|
<col className={styles.colSource} />
|
|
<col className={styles.colActions} />
|
|
</colgroup>
|
|
<thead>
|
|
<tr>
|
|
<th className={styles.th}>Order ID</th>
|
|
<th className={styles.th}>Items</th>
|
|
<th className={styles.th}>Total cost</th>
|
|
<th className={styles.th}>Date & time</th>
|
|
<th className={styles.th}>Step</th>
|
|
<th className={styles.th}>Registered by</th>
|
|
<th className={`${styles.th} ${styles.thActions}`}>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{loading && (
|
|
<tr>
|
|
<td className={styles.td} colSpan={COLUMN_COUNT}>
|
|
Loading orders...
|
|
</td>
|
|
</tr>
|
|
)}
|
|
|
|
{!loading && data?.items.length === 0 && (
|
|
<tr>
|
|
<td className={styles.td} colSpan={COLUMN_COUNT}>
|
|
You have no orders yet.
|
|
</td>
|
|
</tr>
|
|
)}
|
|
|
|
{!loading &&
|
|
data?.items.map((order) => (
|
|
<OrderRow
|
|
key={order.id}
|
|
order={order}
|
|
processSteps={DEFAULT_ORDER_PROCESS_STEPS}
|
|
onViewItems={setViewOrder}
|
|
/>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
|
|
{data && data.total > PAGE_SIZE && (
|
|
<div className={styles.pagination}>
|
|
<div>
|
|
Page {page} / {totalPages} · {PAGE_SIZE} per page · {data.total} total
|
|
</div>
|
|
<Pagination
|
|
currentPage={page}
|
|
totalPages={totalPages}
|
|
onPageChange={setPage}
|
|
disabled={loading}
|
|
variant="inline"
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<OrderItemsModal
|
|
open={viewOrder !== null}
|
|
order={viewOrder}
|
|
onClose={() => setViewOrder(null)}
|
|
/>
|
|
</main>
|
|
)
|
|
}
|