Files
dashboards/apps/customer/src/pages/OrdersPage.tsx
T

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 &amp; 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>
)
}