Files
how2ice 005cf56baf
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
init project
2025-12-12 14:26:25 +09:00

551 lines
16 KiB
TypeScript

import * as React from 'react';
import { ColorPaletteProp } from '@mui/joy/styles';
import Avatar from '@mui/joy/Avatar';
import Box from '@mui/joy/Box';
import Button from '@mui/joy/Button';
import Chip from '@mui/joy/Chip';
import Divider from '@mui/joy/Divider';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import Link from '@mui/joy/Link';
import Input from '@mui/joy/Input';
import Modal from '@mui/joy/Modal';
import ModalDialog from '@mui/joy/ModalDialog';
import ModalClose from '@mui/joy/ModalClose';
import Select from '@mui/joy/Select';
import Option from '@mui/joy/Option';
import Table from '@mui/joy/Table';
import Sheet from '@mui/joy/Sheet';
import Checkbox from '@mui/joy/Checkbox';
import IconButton, { iconButtonClasses } from '@mui/joy/IconButton';
import Typography from '@mui/joy/Typography';
import Menu from '@mui/joy/Menu';
import MenuButton from '@mui/joy/MenuButton';
import MenuItem from '@mui/joy/MenuItem';
import Dropdown from '@mui/joy/Dropdown';
import FilterAltIcon from '@mui/icons-material/FilterAlt';
import SearchIcon from '@mui/icons-material/Search';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
import BlockIcon from '@mui/icons-material/Block';
import AutorenewRoundedIcon from '@mui/icons-material/AutorenewRounded';
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft';
import MoreHorizRoundedIcon from '@mui/icons-material/MoreHorizRounded';
const rows = [
{
id: 'INV-1234',
date: 'Feb 3, 2023',
status: 'Refunded',
customer: {
initial: 'O',
name: 'Olivia Ryhe',
email: 'olivia@email.com',
},
},
{
id: 'INV-1233',
date: 'Feb 3, 2023',
status: 'Paid',
customer: {
initial: 'S',
name: 'Steve Hampton',
email: 'steve.hamp@email.com',
},
},
{
id: 'INV-1232',
date: 'Feb 3, 2023',
status: 'Refunded',
customer: {
initial: 'C',
name: 'Ciaran Murray',
email: 'ciaran.murray@email.com',
},
},
{
id: 'INV-1231',
date: 'Feb 3, 2023',
status: 'Refunded',
customer: {
initial: 'M',
name: 'Maria Macdonald',
email: 'maria.mc@email.com',
},
},
{
id: 'INV-1230',
date: 'Feb 3, 2023',
status: 'Cancelled',
customer: {
initial: 'C',
name: 'Charles Fulton',
email: 'fulton@email.com',
},
},
{
id: 'INV-1229',
date: 'Feb 3, 2023',
status: 'Cancelled',
customer: {
initial: 'J',
name: 'Jay Hooper',
email: 'hooper@email.com',
},
},
{
id: 'INV-1228',
date: 'Feb 3, 2023',
status: 'Refunded',
customer: {
initial: 'K',
name: 'Krystal Stevens',
email: 'k.stevens@email.com',
},
},
{
id: 'INV-1227',
date: 'Feb 3, 2023',
status: 'Paid',
customer: {
initial: 'S',
name: 'Sachin Flynn',
email: 's.flyn@email.com',
},
},
{
id: 'INV-1226',
date: 'Feb 3, 2023',
status: 'Cancelled',
customer: {
initial: 'B',
name: 'Bradley Rosales',
email: 'brad123@email.com',
},
},
{
id: 'INV-1225',
date: 'Feb 3, 2023',
status: 'Paid',
customer: {
initial: 'O',
name: 'Olivia Ryhe',
email: 'olivia@email.com',
},
},
{
id: 'INV-1224',
date: 'Feb 3, 2023',
status: 'Cancelled',
customer: {
initial: 'S',
name: 'Steve Hampton',
email: 'steve.hamp@email.com',
},
},
{
id: 'INV-1223',
date: 'Feb 3, 2023',
status: 'Paid',
customer: {
initial: 'C',
name: 'Ciaran Murray',
email: 'ciaran.murray@email.com',
},
},
{
id: 'INV-1221',
date: 'Feb 3, 2023',
status: 'Refunded',
customer: {
initial: 'M',
name: 'Maria Macdonald',
email: 'maria.mc@email.com',
},
},
{
id: 'INV-1220',
date: 'Feb 3, 2023',
status: 'Paid',
customer: {
initial: 'C',
name: 'Charles Fulton',
email: 'fulton@email.com',
},
},
{
id: 'INV-1219',
date: 'Feb 3, 2023',
status: 'Cancelled',
customer: {
initial: 'J',
name: 'Jay Hooper',
email: 'hooper@email.com',
},
},
{
id: 'INV-1218',
date: 'Feb 3, 2023',
status: 'Cancelled',
customer: {
initial: 'K',
name: 'Krystal Stevens',
email: 'k.stevens@email.com',
},
},
{
id: 'INV-1217',
date: 'Feb 3, 2023',
status: 'Paid',
customer: {
initial: 'S',
name: 'Sachin Flynn',
email: 's.flyn@email.com',
},
},
{
id: 'INV-1216',
date: 'Feb 3, 2023',
status: 'Cancelled',
customer: {
initial: 'B',
name: 'Bradley Rosales',
email: 'brad123@email.com',
},
},
];
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
}
type Order = 'asc' | 'desc';
function getComparator<Key extends keyof any>(
order: Order,
orderBy: Key,
): (
a: { [key in Key]: number | string },
b: { [key in Key]: number | string },
) => number {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
function RowMenu() {
return (
<Dropdown>
<MenuButton
slots={{ root: IconButton }}
slotProps={{ root: { variant: 'plain', color: 'neutral', size: 'sm' } }}
>
<MoreHorizRoundedIcon />
</MenuButton>
<Menu size="sm" sx={{ minWidth: 140 }}>
<MenuItem>Edit</MenuItem>
<MenuItem>Rename</MenuItem>
<MenuItem>Move</MenuItem>
<Divider />
<MenuItem color="danger">Delete</MenuItem>
</Menu>
</Dropdown>
);
}
export default function OrderTable() {
const [order, setOrder] = React.useState<Order>('desc');
const [selected, setSelected] = React.useState<readonly string[]>([]);
const [open, setOpen] = React.useState(false);
const renderFilters = () => (
<React.Fragment>
<FormControl size="sm">
<FormLabel>Status</FormLabel>
<Select
size="sm"
placeholder="Filter by status"
slotProps={{ button: { sx: { whiteSpace: 'nowrap' } } }}
>
<Option value="paid">Paid</Option>
<Option value="pending">Pending</Option>
<Option value="refunded">Refunded</Option>
<Option value="cancelled">Cancelled</Option>
</Select>
</FormControl>
<FormControl size="sm">
<FormLabel>Category</FormLabel>
<Select size="sm" placeholder="All">
<Option value="all">All</Option>
<Option value="refund">Refund</Option>
<Option value="purchase">Purchase</Option>
<Option value="debit">Debit</Option>
</Select>
</FormControl>
<FormControl size="sm">
<FormLabel>Customer</FormLabel>
<Select size="sm" placeholder="All">
<Option value="all">All</Option>
<Option value="olivia">Olivia Rhye</Option>
<Option value="steve">Steve Hampton</Option>
<Option value="ciaran">Ciaran Murray</Option>
<Option value="marina">Marina Macdonald</Option>
<Option value="charles">Charles Fulton</Option>
<Option value="jay">Jay Hoper</Option>
</Select>
</FormControl>
</React.Fragment>
);
return (
<React.Fragment>
<Sheet
className="SearchAndFilters-mobile"
sx={{ display: { xs: 'flex', sm: 'none' }, my: 1, gap: 1 }}
>
<Input
size="sm"
placeholder="Search"
startDecorator={<SearchIcon />}
sx={{ flexGrow: 1 }}
/>
<IconButton
size="sm"
variant="outlined"
color="neutral"
onClick={() => setOpen(true)}
>
<FilterAltIcon />
</IconButton>
<Modal open={open} onClose={() => setOpen(false)}>
<ModalDialog aria-labelledby="filter-modal" layout="fullscreen">
<ModalClose />
<Typography id="filter-modal" level="h2">
Filters
</Typography>
<Divider sx={{ my: 2 }} />
<Sheet sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{renderFilters()}
<Button color="primary" onClick={() => setOpen(false)}>
Submit
</Button>
</Sheet>
</ModalDialog>
</Modal>
</Sheet>
<Box
className="SearchAndFilters-tabletUp"
sx={{
borderRadius: 'sm',
py: 2,
display: { xs: 'none', sm: 'flex' },
flexWrap: 'wrap',
gap: 1.5,
'& > *': {
minWidth: { xs: '120px', md: '160px' },
},
}}
>
<FormControl sx={{ flex: 1 }} size="sm">
<FormLabel>Search for order</FormLabel>
<Input size="sm" placeholder="Search" startDecorator={<SearchIcon />} />
</FormControl>
{renderFilters()}
</Box>
<Sheet
className="OrderTableContainer"
variant="outlined"
sx={{
display: { xs: 'none', sm: 'initial' },
width: '100%',
borderRadius: 'sm',
flexShrink: 1,
overflow: 'auto',
minHeight: 0,
}}
>
<Table
aria-labelledby="tableTitle"
stickyHeader
hoverRow
sx={{
'--TableCell-headBackground': 'var(--joy-palette-background-level1)',
'--Table-headerUnderlineThickness': '1px',
'--TableRow-hoverBackground': 'var(--joy-palette-background-level1)',
'--TableCell-paddingY': '4px',
'--TableCell-paddingX': '8px',
}}
>
<thead>
<tr>
<th style={{ width: 48, textAlign: 'center', padding: '12px 6px' }}>
<Checkbox
size="sm"
indeterminate={
selected.length > 0 && selected.length !== rows.length
}
checked={selected.length === rows.length}
onChange={(event) => {
setSelected(
event.target.checked ? rows.map((row) => row.id) : [],
);
}}
color={
selected.length > 0 || selected.length === rows.length
? 'primary'
: undefined
}
sx={{ verticalAlign: 'text-bottom' }}
/>
</th>
<th style={{ width: 120, padding: '12px 6px' }}>
<Link
underline="none"
color="primary"
component="button"
onClick={() => setOrder(order === 'asc' ? 'desc' : 'asc')}
endDecorator={<ArrowDropDownIcon />}
sx={[
{
fontWeight: 'lg',
'& svg': {
transition: '0.2s',
transform:
order === 'desc' ? 'rotate(0deg)' : 'rotate(180deg)',
},
},
order === 'desc'
? { '& svg': { transform: 'rotate(0deg)' } }
: { '& svg': { transform: 'rotate(180deg)' } },
]}
>
Invoice
</Link>
</th>
<th style={{ width: 140, padding: '12px 6px' }}>Date</th>
<th style={{ width: 140, padding: '12px 6px' }}>Status</th>
<th style={{ width: 240, padding: '12px 6px' }}>Customer</th>
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
<th style={{ width: 140, padding: '12px 6px' }}> </th>
</tr>
</thead>
<tbody>
{[...rows].sort(getComparator(order, 'id')).map((row) => (
<tr key={row.id}>
<td style={{ textAlign: 'center', width: 120 }}>
<Checkbox
size="sm"
checked={selected.includes(row.id)}
color={selected.includes(row.id) ? 'primary' : undefined}
onChange={(event) => {
setSelected((ids) =>
event.target.checked
? ids.concat(row.id)
: ids.filter((itemId) => itemId !== row.id),
);
}}
slotProps={{ checkbox: { sx: { textAlign: 'left' } } }}
sx={{ verticalAlign: 'text-bottom' }}
/>
</td>
<td>
<Typography level="body-xs">{row.id}</Typography>
</td>
<td>
<Typography level="body-xs">{row.date}</Typography>
</td>
<td>
<Chip
variant="soft"
size="sm"
startDecorator={
{
Paid: <CheckRoundedIcon />,
Refunded: <AutorenewRoundedIcon />,
Cancelled: <BlockIcon />,
}[row.status]
}
color={
{
Paid: 'success',
Refunded: 'neutral',
Cancelled: 'danger',
}[row.status] as ColorPaletteProp
}
>
{row.status}
</Chip>
</td>
<td>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
<Avatar size="sm">{row.customer.initial}</Avatar>
<div>
<Typography level="body-xs">{row.customer.name}</Typography>
<Typography level="body-xs">{row.customer.email}</Typography>
</div>
</Box>
</td>
<td>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
<Link level="body-xs" component="button">
Download
</Link>
<RowMenu />
</Box>
</td>
</tr>
))}
</tbody>
</Table>
</Sheet>
<Box
className="Pagination-laptopUp"
sx={{
pt: 2,
gap: 1,
[`& .${iconButtonClasses.root}`]: { borderRadius: '50%' },
display: {
xs: 'none',
md: 'flex',
},
}}
>
<Button
size="sm"
variant="outlined"
color="neutral"
startDecorator={<KeyboardArrowLeftIcon />}
>
Previous
</Button>
<Box sx={{ flex: 1 }} />
{['1', '2', '3', '…', '8', '9', '10'].map((page) => (
<IconButton
key={page}
size="sm"
variant={Number(page) ? 'outlined' : 'plain'}
color="neutral"
>
{page}
</IconButton>
))}
<Box sx={{ flex: 1 }} />
<Button
size="sm"
variant="outlined"
color="neutral"
endDecorator={<KeyboardArrowRightIcon />}
>
Next
</Button>
</Box>
</React.Fragment>
);
}