update build ok,
This commit is contained in:
@@ -30,8 +30,291 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { paths } from '@/paths';
|
||||
import { dayjs } from '@/lib/dayjs';
|
||||
import { PropertyItem } from '@/components/core/property-item';
|
||||
import { PropertyList } from '@/components/core/property-list';
|
||||
// import { getLessonTypeById } from '@/components/dashboard/lesson_type/http-actions';
|
||||
import { LessonTypeDefaultValue, type LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
||||
import { Notifications } from '@/components/dashboard/lesson_type/notifications';
|
||||
import { Payments } from '@/components/dashboard/lesson_type/payments';
|
||||
import type { Address } from '@/components/dashboard/lesson_type/shipping-address';
|
||||
import { ShippingAddress } from '@/components/dashboard/lesson_type/shipping-address';
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
return <>helloworld</>;
|
||||
const { typeId } = useParams<{ typeId: string }>();
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(true);
|
||||
const [showLessonType, setShowLessonType] = React.useState<LessonType>(LessonTypeDefaultValue);
|
||||
const router = useRouter();
|
||||
|
||||
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.lesson_types.list}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
{t('Lesson Types')}
|
||||
</Link>
|
||||
</div>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}>
|
||||
<Avatar src="/assets/avatar-1.png" sx={{ '--Avatar-size': '64px' }}>
|
||||
MV
|
||||
</Avatar>
|
||||
<div>
|
||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
<Typography variant="h4">{showLessonType.name}</Typography>
|
||||
<Chip
|
||||
icon={<CheckCircleIcon color="var(--mui-palette-success-main)" weight="fill" />}
|
||||
label={showLessonType.visible}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
/>
|
||||
</Stack>
|
||||
<Typography color="text.secondary" variant="body1">
|
||||
{showLessonType.id}
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
<div>
|
||||
<Button endIcon={<CaretDownIcon />} variant="contained">
|
||||
Action
|
||||
</Button>
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Grid container spacing={4}>
|
||||
<Grid lg={4} xs={12}>
|
||||
<Stack spacing={4}>
|
||||
<Card>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handleEditClick();
|
||||
}}
|
||||
>
|
||||
<PencilSimpleIcon />
|
||||
</IconButton>
|
||||
}
|
||||
avatar={
|
||||
<Avatar>
|
||||
<UserIcon fontSize="var(--Icon-fontSize)" />
|
||||
</Avatar>
|
||||
}
|
||||
title="Basic details"
|
||||
/>
|
||||
<PropertyList
|
||||
divider={<Divider />}
|
||||
orientation="vertical"
|
||||
sx={{ '--PropertyItem-padding': '12px 24px' }}
|
||||
>
|
||||
{(
|
||||
[
|
||||
{ key: 'Customer ID', value: <Chip label={showLessonType.id} size="small" variant="soft" /> },
|
||||
{ key: 'Name', value: showLessonType.name },
|
||||
{ key: 'Type', value: showLessonType.type },
|
||||
{ key: 'Pos', value: showLessonType.pos },
|
||||
{ key: 'Visible', value: <Chip label={showLessonType.visible} size="small" variant="soft" /> },
|
||||
{
|
||||
key: 'Quota',
|
||||
value: (
|
||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
|
||||
<LinearProgress sx={{ flex: '1 1 auto' }} value={50} variant="determinate" />
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
50%
|
||||
</Typography>
|
||||
</Stack>
|
||||
),
|
||||
},
|
||||
] satisfies { key: string; value: React.ReactNode }[]
|
||||
).map(
|
||||
(item): React.JSX.Element => (
|
||||
<PropertyItem key={item.key} name={item.key} value={item.value} />
|
||||
)
|
||||
)}
|
||||
</PropertyList>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader
|
||||
avatar={
|
||||
<Avatar>
|
||||
<ShieldWarningIcon fontSize="var(--Icon-fontSize)" />
|
||||
</Avatar>
|
||||
}
|
||||
title="Security"
|
||||
/>
|
||||
<CardContent>
|
||||
<Stack spacing={1}>
|
||||
<div>
|
||||
<Button color="error" variant="contained">
|
||||
Delete account
|
||||
</Button>
|
||||
</div>
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
A deleted lesson type cannot be restored. All data will be permanently removed.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid lg={8} xs={12}>
|
||||
<Stack spacing={4}>
|
||||
<Payments
|
||||
ordersValue={2069.48}
|
||||
payments={[
|
||||
{
|
||||
currency: 'USD',
|
||||
amount: 500,
|
||||
invoiceId: 'INV-005',
|
||||
status: 'completed',
|
||||
createdAt: dayjs().subtract(5, 'minute').subtract(1, 'hour').toDate(),
|
||||
},
|
||||
{
|
||||
currency: 'USD',
|
||||
amount: 324.5,
|
||||
invoiceId: 'INV-004',
|
||||
status: 'refunded',
|
||||
createdAt: dayjs().subtract(21, 'minute').subtract(2, 'hour').toDate(),
|
||||
},
|
||||
{
|
||||
currency: 'USD',
|
||||
amount: 746.5,
|
||||
invoiceId: 'INV-003',
|
||||
status: 'completed',
|
||||
createdAt: dayjs().subtract(7, 'minute').subtract(3, 'hour').toDate(),
|
||||
},
|
||||
{
|
||||
currency: 'USD',
|
||||
amount: 56.89,
|
||||
invoiceId: 'INV-002',
|
||||
status: 'completed',
|
||||
createdAt: dayjs().subtract(48, 'minute').subtract(4, 'hour').toDate(),
|
||||
},
|
||||
{
|
||||
currency: 'USD',
|
||||
amount: 541.59,
|
||||
invoiceId: 'INV-001',
|
||||
status: 'completed',
|
||||
createdAt: dayjs().subtract(31, 'minute').subtract(5, 'hour').toDate(),
|
||||
},
|
||||
]}
|
||||
refundsValue={324.5}
|
||||
totalOrders={5}
|
||||
/>
|
||||
<Card>
|
||||
<CardHeader
|
||||
action={
|
||||
<Button color="secondary" startIcon={<PencilSimpleIcon />}>
|
||||
Edit
|
||||
</Button>
|
||||
}
|
||||
avatar={
|
||||
<Avatar>
|
||||
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
|
||||
</Avatar>
|
||||
}
|
||||
title="Billing details"
|
||||
/>
|
||||
<CardContent>
|
||||
<Card sx={{ borderRadius: 1 }} variant="outlined">
|
||||
<PropertyList divider={<Divider />} sx={{ '--PropertyItem-padding': '16px' }}>
|
||||
{(
|
||||
[
|
||||
{ key: 'Credit card', value: '**** 4142' },
|
||||
{ key: 'Country', value: 'United States' },
|
||||
{ key: 'State', value: 'Michigan' },
|
||||
{ key: 'City', value: 'Southfield' },
|
||||
{ key: 'Address', value: '1721 Bartlett Avenue, 48034' },
|
||||
{ key: 'Tax ID', value: 'EU87956621' },
|
||||
] 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
|
||||
action={
|
||||
<Button color="secondary" startIcon={<PlusIcon />}>
|
||||
Add
|
||||
</Button>
|
||||
}
|
||||
avatar={
|
||||
<Avatar>
|
||||
<HouseIcon fontSize="var(--Icon-fontSize)" />
|
||||
</Avatar>
|
||||
}
|
||||
title="Shipping addresses"
|
||||
/>
|
||||
<CardContent>
|
||||
<Grid container spacing={3}>
|
||||
{(
|
||||
[
|
||||
{
|
||||
id: 'ADR-001',
|
||||
country: 'United States',
|
||||
state: 'Michigan',
|
||||
city: 'Lansing',
|
||||
zipCode: '48933',
|
||||
street: '480 Haven Lane',
|
||||
primary: true,
|
||||
},
|
||||
{
|
||||
id: 'ADR-002',
|
||||
country: 'United States',
|
||||
state: 'Missouri',
|
||||
city: 'Springfield',
|
||||
zipCode: '65804',
|
||||
street: '4807 Lighthouse Drive',
|
||||
},
|
||||
] satisfies Address[]
|
||||
).map((address) => (
|
||||
<Grid key={address.id} md={6} xs={12}>
|
||||
<ShippingAddress address={address} />
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Notifications
|
||||
notifications={[
|
||||
{
|
||||
id: 'EV-002',
|
||||
type: 'Refund request approved',
|
||||
status: 'pending',
|
||||
createdAt: dayjs().subtract(34, 'minute').subtract(5, 'hour').subtract(3, 'day').toDate(),
|
||||
},
|
||||
{
|
||||
id: 'EV-001',
|
||||
type: 'Order confirmation',
|
||||
status: 'delivered',
|
||||
createdAt: dayjs().subtract(49, 'minute').subtract(11, 'hour').subtract(4, 'day').toDate(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@@ -0,0 +1,101 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
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 Select from '@mui/material/Select';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { EnvelopeSimple as EnvelopeSimpleIcon } from '@phosphor-icons/react/dist/ssr/EnvelopeSimple';
|
||||
|
||||
import { dayjs } from '@/lib/dayjs';
|
||||
import { DataTable } from '@/components/core/data-table';
|
||||
import type { ColumnDef } from '@/components/core/data-table';
|
||||
import { Option } from '@/components/core/option';
|
||||
|
||||
export interface Notification {
|
||||
id: string;
|
||||
type: string;
|
||||
status: 'delivered' | 'pending' | 'failed';
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
formatter: (row): React.JSX.Element => (
|
||||
<Typography sx={{ whiteSpace: 'nowrap' }} variant="inherit">
|
||||
{row.type}
|
||||
</Typography>
|
||||
),
|
||||
name: 'Type',
|
||||
width: '300px',
|
||||
},
|
||||
{
|
||||
formatter: (row): React.JSX.Element => {
|
||||
const mapping = {
|
||||
delivered: { label: 'Delivered', color: 'success' },
|
||||
pending: { label: 'Pending', color: 'warning' },
|
||||
failed: { label: 'Failed', color: 'error' },
|
||||
} as const;
|
||||
const { label, color } = mapping[row.status] ?? { label: 'Unknown', color: 'secondary' };
|
||||
|
||||
return <Chip color={color} label={label} size="small" variant="soft" />;
|
||||
},
|
||||
name: 'Status',
|
||||
width: '200px',
|
||||
},
|
||||
{
|
||||
formatter: (row): React.JSX.Element => (
|
||||
<Typography sx={{ whiteSpace: 'nowrap' }} variant="inherit">
|
||||
{dayjs(row.createdAt).format('MMM D, YYYY hh:mm A')}
|
||||
</Typography>
|
||||
),
|
||||
name: 'Date',
|
||||
align: 'right',
|
||||
},
|
||||
] satisfies ColumnDef<Notification>[];
|
||||
|
||||
export interface NotificationsProps {
|
||||
notifications: Notification[];
|
||||
}
|
||||
|
||||
export function Notifications({ notifications }: NotificationsProps): React.JSX.Element {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
avatar={
|
||||
<Avatar>
|
||||
<EnvelopeSimpleIcon fontSize="var(--Icon-fontSize)" />
|
||||
</Avatar>
|
||||
}
|
||||
title="Notifications"
|
||||
/>
|
||||
<CardContent>
|
||||
<Stack spacing={3}>
|
||||
<Stack spacing={2}>
|
||||
<Select defaultValue="last_invoice" name="type" sx={{ maxWidth: '100%', width: '320px' }}>
|
||||
<Option value="last_invoice">Resend last invoice</Option>
|
||||
<Option value="password_reset">Send password reset</Option>
|
||||
<Option value="verification">Send verification</Option>
|
||||
</Select>
|
||||
<div>
|
||||
<Button startIcon={<EnvelopeSimpleIcon />} variant="contained">
|
||||
Send email
|
||||
</Button>
|
||||
</div>
|
||||
</Stack>
|
||||
<Card sx={{ borderRadius: 1 }} variant="outlined">
|
||||
<Box sx={{ overflowX: 'auto' }}>
|
||||
<DataTable<Notification> columns={columns} rows={notifications} />
|
||||
</Box>
|
||||
</Card>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
138
002_source/cms/src/components/dashboard/lesson_type/payments.tsx
Normal file
138
002_source/cms/src/components/dashboard/lesson_type/payments.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
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 { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
|
||||
import { ShoppingCartSimple as ShoppingCartSimpleIcon } from '@phosphor-icons/react/dist/ssr/ShoppingCartSimple';
|
||||
|
||||
import { dayjs } from '@/lib/dayjs';
|
||||
import type { ColumnDef } from '@/components/core/data-table';
|
||||
import { DataTable } from '@/components/core/data-table';
|
||||
|
||||
export interface Payment {
|
||||
currency: string;
|
||||
amount: number;
|
||||
invoiceId: string;
|
||||
status: 'pending' | 'completed' | 'canceled' | 'refunded';
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
formatter: (row): React.JSX.Element => (
|
||||
<Typography sx={{ whiteSpace: 'nowrap' }} variant="subtitle2">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: row.currency }).format(row.amount)}
|
||||
</Typography>
|
||||
),
|
||||
name: 'Amount',
|
||||
width: '200px',
|
||||
},
|
||||
{
|
||||
formatter: (row): React.JSX.Element => {
|
||||
const mapping = {
|
||||
pending: { label: 'Pending', color: 'warning' },
|
||||
completed: { label: 'Completed', color: 'success' },
|
||||
canceled: { label: 'Canceled', color: 'error' },
|
||||
refunded: { label: 'Refunded', color: 'error' },
|
||||
} as const;
|
||||
const { label, color } = mapping[row.status] ?? { label: 'Unknown', color: 'secondary' };
|
||||
|
||||
return <Chip color={color} label={label} size="small" variant="soft" />;
|
||||
},
|
||||
name: 'Status',
|
||||
width: '200px',
|
||||
},
|
||||
{
|
||||
formatter: (row): React.JSX.Element => {
|
||||
return <Link variant="inherit">{row.invoiceId}</Link>;
|
||||
},
|
||||
name: 'Invoice ID',
|
||||
width: '150px',
|
||||
},
|
||||
{
|
||||
formatter: (row): React.JSX.Element => (
|
||||
<Typography sx={{ whiteSpace: 'nowrap' }} variant="inherit">
|
||||
{dayjs(row.createdAt).format('MMM D, YYYY hh:mm A')}
|
||||
</Typography>
|
||||
),
|
||||
name: 'Date',
|
||||
align: 'right',
|
||||
},
|
||||
] satisfies ColumnDef<Payment>[];
|
||||
|
||||
export interface PaymentsProps {
|
||||
ordersValue: number;
|
||||
payments: Payment[];
|
||||
refundsValue: number;
|
||||
totalOrders: number;
|
||||
}
|
||||
|
||||
export function Payments({ ordersValue, payments = [], refundsValue, totalOrders }: PaymentsProps): React.JSX.Element {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
action={
|
||||
<Button color="secondary" startIcon={<PlusIcon />}>
|
||||
Create Payment
|
||||
</Button>
|
||||
}
|
||||
avatar={
|
||||
<Avatar>
|
||||
<ShoppingCartSimpleIcon fontSize="var(--Icon-fontSize)" />
|
||||
</Avatar>
|
||||
}
|
||||
title="Payments"
|
||||
/>
|
||||
<CardContent>
|
||||
<Stack spacing={3}>
|
||||
<Card sx={{ borderRadius: 1 }} variant="outlined">
|
||||
<Stack
|
||||
direction="row"
|
||||
divider={<Divider flexItem orientation="vertical" />}
|
||||
spacing={3}
|
||||
sx={{ justifyContent: 'space-between', p: 2 }}
|
||||
>
|
||||
<div>
|
||||
<Typography color="text.secondary" variant="overline">
|
||||
Total orders
|
||||
</Typography>
|
||||
<Typography variant="h6">{new Intl.NumberFormat('en-US').format(totalOrders)}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography color="text.secondary" variant="overline">
|
||||
Orders value
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(ordersValue)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography color="text.secondary" variant="overline">
|
||||
Refunds
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(refundsValue)}
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
</Card>
|
||||
<Card sx={{ borderRadius: 1 }} variant="outlined">
|
||||
<Box sx={{ overflowX: 'auto' }}>
|
||||
<DataTable<Payment> columns={columns} rows={payments} />
|
||||
</Box>
|
||||
</Card>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
import * as React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
|
||||
|
||||
export interface Address {
|
||||
id: string;
|
||||
country: string;
|
||||
state: string;
|
||||
city: string;
|
||||
zipCode: string;
|
||||
street: string;
|
||||
primary?: boolean;
|
||||
}
|
||||
|
||||
export interface ShippingAddressProps {
|
||||
address: Address;
|
||||
}
|
||||
|
||||
export function ShippingAddress({ address }: ShippingAddressProps): React.ReactElement {
|
||||
return (
|
||||
<Card sx={{ borderRadius: 1, height: '100%' }} variant="outlined">
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<Typography>
|
||||
{address.street},
|
||||
<br />
|
||||
{address.city}, {address.state}, {address.country},
|
||||
<br />
|
||||
{address.zipCode}
|
||||
</Typography>
|
||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
{address.primary ? <Chip color="warning" label="Primary" variant="soft" /> : <span />}
|
||||
<Button color="secondary" size="small" startIcon={<PencilSimpleIcon />}>
|
||||
Edit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user