build ok,
This commit is contained in:
270
002_source/cms/src/app/dashboard/orders/[orderId]/page.tsx
Normal file
270
002_source/cms/src/app/dashboard/orders/[orderId]/page.tsx
Normal file
@@ -0,0 +1,270 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import CardHeader from '@mui/material/CardHeader';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Link from '@mui/material/Link';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Grid from '@mui/material/Unstable_Grid2';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
|
||||
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
|
||||
import { CreditCard as CreditCardIcon } from '@phosphor-icons/react/dist/ssr/CreditCard';
|
||||
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
|
||||
import { ShoppingCartSimple as ShoppingCartSimpleIcon } from '@phosphor-icons/react/dist/ssr/ShoppingCartSimple';
|
||||
import { Timer as TimerIcon } from '@phosphor-icons/react/dist/ssr/Timer';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { dayjs } from '@/lib/dayjs';
|
||||
import { PropertyItem } from '@/components/core/property-item';
|
||||
import { PropertyList } from '@/components/core/property-list';
|
||||
import type { Event } from '@/components/dashboard/order/events-timeline';
|
||||
import { EventsTimeline } from '@/components/dashboard/order/events-timeline';
|
||||
import { LineItemsTable } from '@/components/dashboard/order/line-items-table';
|
||||
import type { LineItem } from '@/components/dashboard/order/line-items-table';
|
||||
|
||||
export const metadata = { title: `Details | Orders | Dashboard | ${config.site.name}` } satisfies Metadata;
|
||||
|
||||
const lineItems = [
|
||||
{
|
||||
id: 'LI-001',
|
||||
product: 'Erbology Aloe Vera',
|
||||
image: '/assets/product-1.png',
|
||||
quantity: 1,
|
||||
currency: 'USD',
|
||||
unitAmount: 24,
|
||||
totalAmount: 24,
|
||||
},
|
||||
{
|
||||
id: 'LI-002',
|
||||
product: 'Lancome Rouge',
|
||||
image: '/assets/product-2.png',
|
||||
quantity: 1,
|
||||
currency: 'USD',
|
||||
unitAmount: 35,
|
||||
totalAmount: 35,
|
||||
},
|
||||
] satisfies LineItem[];
|
||||
|
||||
const events = [
|
||||
{
|
||||
id: 'EV-004',
|
||||
createdAt: dayjs().subtract(3, 'hour').toDate(),
|
||||
type: 'note_added',
|
||||
author: { name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
|
||||
note: 'Customer states that the products have been damaged by the courier.',
|
||||
},
|
||||
{
|
||||
id: 'EV-003',
|
||||
createdAt: dayjs().subtract(12, 'hour').toDate(),
|
||||
type: 'shipment_notice',
|
||||
description: 'Left the package in front of the door',
|
||||
},
|
||||
{
|
||||
id: 'EV-002',
|
||||
createdAt: dayjs().subtract(18, 'hour').toDate(),
|
||||
type: 'items_shipped',
|
||||
carrier: 'USPS',
|
||||
trackingNumber: '940011189',
|
||||
},
|
||||
{ id: 'EV-001', createdAt: dayjs().subtract(21, 'hour').toDate(), type: 'order_created' },
|
||||
] satisfies Event[];
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: 'var(--Content-maxWidth)',
|
||||
m: 'var(--Content-margin)',
|
||||
p: 'var(--Content-padding)',
|
||||
width: 'var(--Content-width)',
|
||||
}}
|
||||
>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.orders.list}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Orders
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
||||
<Box sx={{ flex: '1 1 auto' }}>
|
||||
<Typography variant="h5">ORD-001</Typography>
|
||||
</Box>
|
||||
<div>
|
||||
<Button endIcon={<CaretDownIcon />} variant="contained">
|
||||
Action
|
||||
</Button>
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
<Grid container spacing={4}>
|
||||
<Grid md={8} xs={12}>
|
||||
<Stack spacing={4}>
|
||||
<Card>
|
||||
<CardHeader
|
||||
action={
|
||||
<Button color="secondary" startIcon={<PencilSimpleIcon />}>
|
||||
Edit
|
||||
</Button>
|
||||
}
|
||||
avatar={
|
||||
<Avatar>
|
||||
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
|
||||
</Avatar>
|
||||
}
|
||||
title="Order information"
|
||||
/>
|
||||
<CardContent>
|
||||
<Card sx={{ borderRadius: 1 }} variant="outlined">
|
||||
<PropertyList divider={<Divider />} sx={{ '--PropertyItem-padding': '12px 24px' }}>
|
||||
{(
|
||||
[
|
||||
{ key: 'Customer', value: <Link variant="subtitle2">Miron Vitold</Link> },
|
||||
{
|
||||
key: 'Address',
|
||||
value: (
|
||||
<Typography variant="subtitle2">
|
||||
1721 Bartlett Avenue
|
||||
<br />
|
||||
Southfield, Michigan, United States
|
||||
<br />
|
||||
48034
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{ key: 'Date', value: dayjs().subtract(3, 'hour').format('MMMM D, YYYY hh:mm A') },
|
||||
{
|
||||
key: 'Status',
|
||||
value: (
|
||||
<Chip
|
||||
icon={<CheckCircleIcon color="var(--mui-palette-success-main)" weight="fill" />}
|
||||
label="Completed"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'Payment method',
|
||||
value: (
|
||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
|
||||
<Avatar
|
||||
sx={{
|
||||
bgcolor: 'var(--mui-palette-background-paper)',
|
||||
boxShadow: 'var(--mui-shadows-8)',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src="/assets/payment-method-1.png"
|
||||
sx={{ borderRadius: '50px', height: 'auto', width: '35px' }}
|
||||
/>
|
||||
</Avatar>
|
||||
<div>
|
||||
<Typography variant="body2">Mastercard</Typography>
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
**** 4242
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
),
|
||||
},
|
||||
] satisfies { key: string; value: React.ReactNode }[]
|
||||
).map(
|
||||
(item): React.JSX.Element => (
|
||||
<PropertyItem key={item.key} name={item.key} value={item.value} />
|
||||
)
|
||||
)}
|
||||
</PropertyList>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader
|
||||
avatar={
|
||||
<Avatar>
|
||||
<ShoppingCartSimpleIcon fontSize="var(--Icon-fontSize)" />
|
||||
</Avatar>
|
||||
}
|
||||
title="Checkout Summary"
|
||||
/>
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<Card sx={{ borderRadius: 1 }} variant="outlined">
|
||||
<Box sx={{ overflowX: 'auto' }}>
|
||||
<LineItemsTable rows={lineItems} />
|
||||
</Box>
|
||||
</Card>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Stack spacing={2} sx={{ width: '300px', maxWidth: '100%' }}>
|
||||
<Stack direction="row" spacing={3} sx={{ justifyContent: 'space-between' }}>
|
||||
<Typography variant="body2">Subtotal</Typography>
|
||||
<Typography variant="body2">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(59)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={3} sx={{ justifyContent: 'space-between' }}>
|
||||
<Typography variant="body2">Discount</Typography>
|
||||
<Typography variant="body2">-</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={3} sx={{ justifyContent: 'space-between' }}>
|
||||
<Typography variant="body2">Shipping</Typography>
|
||||
<Typography variant="body2">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(20)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={3} sx={{ justifyContent: 'space-between' }}>
|
||||
<Typography variant="body2">Taxes</Typography>
|
||||
<Typography variant="body2">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(15.01)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={3} sx={{ justifyContent: 'space-between' }}>
|
||||
<Typography variant="subtitle1">Total</Typography>
|
||||
<Typography variant="subtitle1">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(94.01)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid md={4} xs={12}>
|
||||
<Card>
|
||||
<CardHeader
|
||||
avatar={
|
||||
<Avatar>
|
||||
<TimerIcon fontSize="var(--Icon-fontSize)" />
|
||||
</Avatar>
|
||||
}
|
||||
title="Timeline"
|
||||
/>
|
||||
<CardContent>
|
||||
<EventsTimeline events={events} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
48
002_source/cms/src/app/dashboard/orders/create/page.tsx
Normal file
48
002_source/cms/src/app/dashboard/orders/create/page.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Link from '@mui/material/Link';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { OrderCreateForm } from '@/components/dashboard/order/order-create-form';
|
||||
|
||||
export const metadata = { title: `Create | Orders | Dashboard | ${config.site.name}` } satisfies Metadata;
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: 'var(--Content-maxWidth)',
|
||||
m: 'var(--Content-margin)',
|
||||
p: 'var(--Content-padding)',
|
||||
width: 'var(--Content-width)',
|
||||
}}
|
||||
>
|
||||
<Stack spacing={4}>
|
||||
<Stack spacing={3}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.orders.list}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Orders
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="h4">Create order</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
<OrderCreateForm />
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
159
002_source/cms/src/app/dashboard/orders/page.tsx
Normal file
159
002_source/cms/src/app/dashboard/orders/page.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Card from '@mui/material/Card';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { dayjs } from '@/lib/dayjs';
|
||||
import { OrderModal } from '@/components/dashboard/order/order-modal';
|
||||
import { OrdersFilters } from '@/components/dashboard/order/orders-filters';
|
||||
import type { Filters } from '@/components/dashboard/order/orders-filters';
|
||||
import { OrdersPagination } from '@/components/dashboard/order/orders-pagination';
|
||||
import { OrdersSelectionProvider } from '@/components/dashboard/order/orders-selection-context';
|
||||
import { OrdersTable } from '@/components/dashboard/order/orders-table';
|
||||
import type { Order } from '@/components/dashboard/order/orders-table';
|
||||
|
||||
export const metadata = { title: `List | Orders | Dashboard | ${config.site.name}` } satisfies Metadata;
|
||||
|
||||
const orders = [
|
||||
{
|
||||
id: 'ORD-005',
|
||||
customer: { name: 'Penjani Inyene', avatar: '/assets/avatar-4.png', email: 'penjani.inyene@domain.com' },
|
||||
lineItems: 1,
|
||||
paymentMethod: { type: 'visa', last4: '4011' },
|
||||
currency: 'USD',
|
||||
totalAmount: 56.7,
|
||||
status: 'pending',
|
||||
createdAt: dayjs().subtract(3, 'hour').toDate(),
|
||||
},
|
||||
{
|
||||
id: 'ORD-004',
|
||||
customer: { name: 'Jie Yan', avatar: '/assets/avatar-8.png', email: 'jie.yan@domain.com' },
|
||||
lineItems: 1,
|
||||
paymentMethod: { type: 'amex', last4: '5678' },
|
||||
currency: 'USD',
|
||||
totalAmount: 49.12,
|
||||
status: 'completed',
|
||||
createdAt: dayjs().subtract(6, 'hour').toDate(),
|
||||
},
|
||||
{
|
||||
id: 'ORD-003',
|
||||
customer: { name: 'Fran Perez', avatar: '/assets/avatar-5.png', email: 'fran.perez@domain.com' },
|
||||
lineItems: 2,
|
||||
paymentMethod: { type: 'applepay' },
|
||||
currency: 'USD',
|
||||
totalAmount: 18.75,
|
||||
status: 'canceled',
|
||||
createdAt: dayjs().subtract(7, 'hour').toDate(),
|
||||
},
|
||||
{
|
||||
id: 'ORD-002',
|
||||
customer: { name: 'Carson Darrin', avatar: '/assets/avatar-3.png', email: 'carson.darrin@domain.com' },
|
||||
lineItems: 1,
|
||||
paymentMethod: { type: 'googlepay' },
|
||||
currency: 'USD',
|
||||
totalAmount: 49.99,
|
||||
status: 'rejected',
|
||||
createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
|
||||
},
|
||||
{
|
||||
id: 'ORD-001',
|
||||
customer: { name: 'Miron Vitold', avatar: '/assets/avatar-1.png', email: 'miron.vitold@domain.com' },
|
||||
lineItems: 2,
|
||||
paymentMethod: { type: 'mastercard', last4: '4242' },
|
||||
currency: 'USD',
|
||||
totalAmount: 94.01,
|
||||
status: 'completed',
|
||||
createdAt: dayjs().subtract(3, 'hour').subtract(1, 'day').toDate(),
|
||||
},
|
||||
] satisfies Order[];
|
||||
|
||||
interface PageProps {
|
||||
searchParams: { customer?: string; id?: string; previewId?: string; sortDir?: 'asc' | 'desc'; status?: string };
|
||||
}
|
||||
|
||||
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
const { customer, id, previewId, sortDir, status } = searchParams;
|
||||
|
||||
const sortedOrders = applySort(orders, sortDir);
|
||||
const filteredOrders = applyFilters(sortedOrders, { customer, id, status });
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: 'var(--Content-maxWidth)',
|
||||
m: 'var(--Content-margin)',
|
||||
p: 'var(--Content-padding)',
|
||||
width: 'var(--Content-width)',
|
||||
}}
|
||||
>
|
||||
<Stack spacing={4}>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
||||
<Box sx={{ flex: '1 1 auto' }}>
|
||||
<Typography variant="h4">Orders</Typography>
|
||||
</Box>
|
||||
<div>
|
||||
<Button startIcon={<PlusIcon />} variant="contained">
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</Stack>
|
||||
<OrdersSelectionProvider orders={filteredOrders}>
|
||||
<Card>
|
||||
<OrdersFilters filters={{ customer, id, status }} sortDir={sortDir} />
|
||||
<Divider />
|
||||
<Box sx={{ overflowX: 'auto' }}>
|
||||
<OrdersTable rows={filteredOrders} />
|
||||
</Box>
|
||||
<Divider />
|
||||
<OrdersPagination count={filteredOrders.length} page={0} />
|
||||
</Card>
|
||||
</OrdersSelectionProvider>
|
||||
</Stack>
|
||||
</Box>
|
||||
<OrderModal open={Boolean(previewId)} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
// Sorting and filtering has to be done on the server.
|
||||
|
||||
function applySort(row: Order[], sortDir: 'asc' | 'desc' | undefined): Order[] {
|
||||
return row.sort((a, b) => {
|
||||
if (sortDir === 'asc') {
|
||||
return a.createdAt.getTime() - b.createdAt.getTime();
|
||||
}
|
||||
|
||||
return b.createdAt.getTime() - a.createdAt.getTime();
|
||||
});
|
||||
}
|
||||
|
||||
function applyFilters(row: Order[], { customer, id, status }: Filters): Order[] {
|
||||
return row.filter((item) => {
|
||||
if (customer) {
|
||||
if (!item.customer?.name?.toLowerCase().includes(customer.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (id) {
|
||||
if (!item.id?.toLowerCase().includes(id.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (status) {
|
||||
if (item.status !== status) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user