build ok,

This commit is contained in:
louiscklaw
2025-04-14 16:04:32 +08:00
parent ea653bc3e0
commit f18acf7594
53 changed files with 0 additions and 4633 deletions

View File

@@ -1,26 +0,0 @@
import PocketBase from 'pocketbase';
const pb = new PocketBase(`http://localhost:8090`);
const COLLECTION_NAME = 'LessonsTypes';
interface LessonTypeCreate {
name: string;
type: string;
pos: number;
visible: string;
}
interface ReqLessonTypeCreate {
data: LessonTypeCreate;
}
// POST - Create a new lesson type
export async function POST(request: Request): Promise<Response> {
const { data } = (await request.json()) as ReqLessonTypeCreate;
const record = await pb.collection(COLLECTION_NAME).create(data);
return new Response(JSON.stringify(record), {
status: 201,
headers: { 'Content-Type': 'application/json' },
});
}

View File

@@ -1,19 +0,0 @@
// please keep the comment and update to perform delete operation
import PocketBase from 'pocketbase';
const { PB_HOSTNAME } = process.env;
export async function DELETE(request: Request) {
try {
const pb = new PocketBase(`http://${PB_HOSTNAME}:8090`);
const { id } = await request.json();
await pb.collection('LessonsTypes').delete(id);
return new Response('Record deleted successfully', {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
console.error(error);
return new Response('Failed to delete record', { status: 500 });
}
}

View File

@@ -1,31 +0,0 @@
// https://nextjs.org/blog/building-apis-with-nextjs
import { NextRequest } from 'next/server';
import PocketBase from 'pocketbase';
const { PB_HOSTNAME } = process.env;
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const id = (await params).id;
const fields = ['id', 'name', 'type', 'visible', 'pos'].join(',');
const pb = new PocketBase(`http://${PB_HOSTNAME}:8090`);
const lessonType = await pb.collection('LessonsTypes').getOne(id, { fields });
return new Response(JSON.stringify(lessonType), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
export async function POST(request: Request) {
// Parse the request body
const body = await request.json();
const { name } = body;
// e.g. Insert new user into your DB
const newUser = { id: Date.now(), name };
return new Response(JSON.stringify(newUser), {
status: 201,
headers: { 'Content-Type': 'application/json' },
});
}

View File

@@ -1,5 +0,0 @@
###
GET http://localhost:3000/api/db/lesson_types/getById/e60v95892466775
###

View File

@@ -1,12 +0,0 @@
import PocketBase from 'pocketbase';
// const { PB_HOSTNAME } = process.env;
export async function GET(): Promise<Response> {
const pb = new PocketBase(`http://localhost:8090`);
const resultList = await pb.collection('LessonsCategories').getList(1, 50, {});
// console.log(resultList);
return Response.json(resultList);
}

View File

@@ -1,17 +0,0 @@
###
GET http://localhost:3000/api/db/lesson_categories/list
Content-Type: application/json
cache: no-store
cache-control: no-cache1
###
GET http://localhost:8090/api/collections/LessonsCategories/records
###
POST http://localhost:3000/api/lesson_categories/helloworld
Content-Type: application/json

View File

@@ -1,5 +0,0 @@
export async function GET(): Promise<Response> {
const record = { hello: 'world' };
return Response.json(record);
}

View File

@@ -1,20 +0,0 @@
import { NextRequest } from 'next/server';
import PocketBase from 'pocketbase';
const { PB_HOSTNAME } = process.env;
export async function PUT(request: NextRequest) {
try {
const { id, data } = await request.json();
const pb = new PocketBase(`http://${PB_HOSTNAME}:8090`);
const updatedRecord = await pb.collection('LessonsTypes').update(id, data);
return new Response(JSON.stringify(updatedRecord), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
console.error(error);
return new Response('Failed to update record', { status: 500 });
}
}

View File

@@ -1,26 +0,0 @@
import PocketBase from 'pocketbase';
const pb = new PocketBase(`http://localhost:8090`);
const COLLECTION_NAME = 'LessonsTypes';
interface LessonTypeCreate {
name: string;
type: string;
pos: number;
visible: string;
}
interface ReqLessonTypeCreate {
data: LessonTypeCreate;
}
// POST - Create a new lesson type
export async function POST(request: Request): Promise<Response> {
const { data } = (await request.json()) as ReqLessonTypeCreate;
const record = await pb.collection(COLLECTION_NAME).create(data);
return new Response(JSON.stringify(record), {
status: 201,
headers: { 'Content-Type': 'application/json' },
});
}

View File

@@ -1,19 +0,0 @@
// please keep the comment and update to perform delete operation
import PocketBase from 'pocketbase';
const { PB_HOSTNAME } = process.env;
export async function DELETE(request: Request) {
try {
const pb = new PocketBase(`http://${PB_HOSTNAME}:8090`);
const { id } = await request.json();
await pb.collection('LessonsTypes').delete(id);
return new Response('Record deleted successfully', {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
console.error(error);
return new Response('Failed to delete record', { status: 500 });
}
}

View File

@@ -1,31 +0,0 @@
// https://nextjs.org/blog/building-apis-with-nextjs
import { NextRequest } from 'next/server';
import PocketBase from 'pocketbase';
const { PB_HOSTNAME } = process.env;
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const id = (await params).id;
const fields = ['id', 'name', 'type', 'visible', 'pos'].join(',');
const pb = new PocketBase(`http://${PB_HOSTNAME}:8090`);
const lessonType = await pb.collection('LessonsTypes').getOne(id, { fields });
return new Response(JSON.stringify(lessonType), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
export async function POST(request: Request) {
// Parse the request body
const body = await request.json();
const { name } = body;
// e.g. Insert new user into your DB
const newUser = { id: Date.now(), name };
return new Response(JSON.stringify(newUser), {
status: 201,
headers: { 'Content-Type': 'application/json' },
});
}

View File

@@ -1,5 +0,0 @@
###
GET http://localhost:3000/api/db/lesson_types/getById/e60v95892466775
###

View File

@@ -1,12 +0,0 @@
import PocketBase from 'pocketbase';
// const { PB_HOSTNAME } = process.env;
export async function GET(): Promise<Response> {
const pb = new PocketBase(`http://localhost:8090`);
const resultList = await pb.collection('LessonsTypes').getList(1, 50, {});
// console.log(resultList);
return Response.json(resultList);
}

View File

@@ -1,17 +0,0 @@
###
GET http://localhost:3000/api/db/lesson_types/list
Content-Type: application/json
cache: no-store
cache-control: no-cache1
###
GET http://localhost:8090/api/collections/LessonsTypes/records
###
POST http://localhost:3000/api/lesson_types/helloworld
Content-Type: application/json

View File

@@ -1,5 +0,0 @@
export async function GET(): Promise<Response> {
const record = { hello: 'world' };
return Response.json(record);
}

View File

@@ -1,20 +0,0 @@
import { NextRequest } from 'next/server';
import PocketBase from 'pocketbase';
const { PB_HOSTNAME } = process.env;
export async function PUT(request: NextRequest) {
try {
const { id, data } = await request.json();
const pb = new PocketBase(`http://${PB_HOSTNAME}:8090`);
const updatedRecord = await pb.collection('LessonsTypes').update(id, data);
return new Response(JSON.stringify(updatedRecord), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
console.error(error);
return new Response('Failed to update record', { status: 500 });
}
}

View File

@@ -1,9 +0,0 @@
import PocketBase from 'pocketbase';
export async function GET(): Promise<Response> {
const { PB_HOSTNAME } = process.env;
const pb = new PocketBase(`http://${PB_HOSTNAME}:8090`);
const resultList = await pb.collection('Vocabularies').getList(1, 50, {});
return Response.json(resultList);
}

View File

@@ -1,5 +0,0 @@
###
GET http://localhost:3000/api/db/lesson_types/list
###

View File

@@ -1,5 +0,0 @@
export const dynamic = 'force-static';
export async function GET(): Promise<Response> {
return Response.json({ hello: 'world' });
}

View File

@@ -1,308 +0,0 @@
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 IconButton from '@mui/material/IconButton';
import LinearProgress from '@mui/material/LinearProgress';
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 { House as HouseIcon } from '@phosphor-icons/react/dist/ssr/House';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
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 { Notifications } from '@/components/dashboard/lesson_category/notifications';
import { Payments } from '@/components/dashboard/lesson_category/payments';
import type { Address } from '@/components/dashboard/lesson_category/shipping-address';
import { ShippingAddress } from '@/components/dashboard/lesson_category/shipping-address';
export const metadata = { title: `Details | Customers | 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.lesson_categories.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Lesson Categories
</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">Miron Vitold</Typography>
<Chip
icon={<CheckCircleIcon color="var(--mui-palette-success-main)" weight="fill" />}
label="Active"
size="small"
variant="outlined"
/>
</Stack>
<Typography color="text.secondary" variant="body1">
miron.vitold@domain.com
</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>
<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="USR-001" size="small" variant="soft" /> },
{ key: 'Name', value: 'Miron Vitold' },
{ key: 'Email', value: 'miron.vitold@domain.com' },
{ key: 'Phone', value: '(425) 434-5535' },
{ key: 'Company', value: 'Devias IO' },
{
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 customer 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>
);
}

View File

@@ -1,48 +0,0 @@
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 { CustomerCreateForm } from '@/components/dashboard/lesson_category/lesson-category-create-form';
export const metadata = { title: `Create | Customers | 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.lesson_categories.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Lesson Categories
</Link>
</div>
<div>
<Typography variant="h4">Create customer</Typography>
</div>
</Stack>
<CustomerCreateForm />
</Stack>
</Box>
);
}

View File

@@ -1,55 +0,0 @@
import { dayjs } from '@/lib/dayjs';
import type { LessonCategory } from '@/components/dashboard/lesson_category/lesson-categories-table';
export const lessonCategoriesSampleData = [
{
id: 'USR-005',
name: 'Fran Perez',
avatar: '/assets/avatar-5.png',
email: 'fran.perez@domain.com',
phone: '(815) 704-0045',
quota: 50,
status: 'active',
createdAt: dayjs().subtract(1, 'hour').toDate(),
},
{
id: 'USR-004',
name: 'Penjani Inyene',
avatar: '/assets/avatar-4.png',
email: 'penjani.inyene@domain.com',
phone: '(803) 937-8925',
quota: 100,
status: 'active',
createdAt: dayjs().subtract(3, 'hour').toDate(),
},
{
id: 'USR-003',
name: 'Carson Darrin',
avatar: '/assets/avatar-3.png',
email: 'carson.darrin@domain.com',
phone: '(715) 278-5041',
quota: 10,
status: 'blocked',
createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
},
{
id: 'USR-002',
name: 'Siegbert Gottfried',
avatar: '/assets/avatar-2.png',
email: 'siegbert.gottfried@domain.com',
phone: '(603) 766-0431',
quota: 0,
status: 'pending',
createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
},
{
id: 'USR-001',
name: 'Miron Vitold',
avatar: '/assets/avatar-1.png',
email: 'miron.vitold@domain.com',
phone: '(425) 434-5535',
quota: 50,
status: 'active',
createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
},
] satisfies LessonCategory[];

View File

@@ -1,103 +0,0 @@
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 { LessonCategoriesFilters } from '@/components/dashboard/lesson_category/lesson-categories-filters';
import type { Filters } from '@/components/dashboard/lesson_category/lesson-categories-filters';
import { LessonCategoriesPagination } from '@/components/dashboard/lesson_category/lesson-categories-pagination';
import { LessonCategoriesSelectionProvider } from '@/components/dashboard/lesson_category/lesson-categories-selection-context';
import { LessonCategoriesTable } from '@/components/dashboard/lesson_category/lesson-categories-table';
import type { LessonCategory } from '@/components/dashboard/lesson_category/lesson-categories-table';
import { lessonCategoriesSampleData } from './lesson-categories-sample-data';
export const metadata = { title: `List | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
interface PageProps {
searchParams: { email?: string; phone?: string; sortDir?: 'asc' | 'desc'; status?: string };
}
export default function Page({ searchParams }: PageProps): React.JSX.Element {
const { email, phone, sortDir, status } = searchParams;
const sortedLessonCategories = applySort(lessonCategoriesSampleData, sortDir);
const filteredLessonCategories = applyFilters(sortedLessonCategories, { email, phone, status });
return (
<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">Lesson Categories</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button startIcon={<PlusIcon />} variant="contained">
Add
</Button>
</Box>
</Stack>
<LessonCategoriesSelectionProvider lessonCategories={filteredLessonCategories}>
<Card>
<LessonCategoriesFilters filters={{ email, phone, status }} sortDir={sortDir} />
<Divider />
<Box sx={{ overflowX: 'auto' }}>
<LessonCategoriesTable rows={filteredLessonCategories} />
</Box>
<Divider />
<LessonCategoriesPagination count={filteredLessonCategories.length + 100} page={0} />
</Card>
</LessonCategoriesSelectionProvider>
</Stack>
</Box>
);
}
// Sorting and filtering has to be done on the server.
function applySort(row: LessonCategory[], sortDir: 'asc' | 'desc' | undefined): LessonCategory[] {
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: LessonCategory[], { email, phone, status }: Filters): LessonCategory[] {
return row.filter((item) => {
if (email) {
if (!item.email?.toLowerCase().includes(email.toLowerCase())) {
return false;
}
}
if (phone) {
if (!item.phone?.toLowerCase().includes(phone.toLowerCase())) {
return false;
}
}
if (status) {
if (item.status !== status) {
return false;
}
}
return true;
});
}

View File

@@ -1,339 +0,0 @@
'use client';
import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
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 IconButton from '@mui/material/IconButton';
import LinearProgress from '@mui/material/LinearProgress';
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 { House as HouseIcon } from '@phosphor-icons/react/dist/ssr/House';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
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();
const { typeId } = useParams<{ typeId: string }>();
const [isLoading, setIsLoading] = React.useState<boolean>(true);
const [showLessonType, setShowLessonType] = React.useState<LessonType>(LessonTypeDefaultValue);
const router = useRouter();
function handleEditClick() {
router.push(paths.dashboard.lesson_types.edit(showLessonType.id));
}
React.useEffect(() => {
getLessonTypeById(typeId)
.then((lessonType: LessonType) => {
setIsLoading(false);
setShowLessonType(lessonType);
})
.catch((err) => {
// console.error(err);
console.error(t('lessonType.load_error'));
});
// console.log('hello');
}, []);
if (isLoading) return <div>{t('common.loading')}</div>;
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>
);
}

View File

@@ -1,48 +0,0 @@
'use client';
import * as React from 'react';
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 { useTranslation } from 'react-i18next';
import { paths } from '@/paths';
import { LessonTypeCreateForm } from '@/components/dashboard/lesson_type/lesson-type-create-form';
export default function Page(): React.JSX.Element {
const { t } = useTranslation();
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('dashboard.lessonTypes.title')}
</Link>
</div>
<div>
<Typography variant="h4">{t('dashboard.lessonTypes.create.title')}</Typography>
</div>
</Stack>
<LessonTypeCreateForm />
</Stack>
</Box>
);
}

View File

@@ -1,48 +0,0 @@
'use client';
import * as React from 'react';
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 { useTranslation } from 'react-i18next';
import { paths } from '@/paths';
import { LessonTypeEditForm } from '@/components/dashboard/lesson_type/lesson-type-edit-form';
export default function Page(): React.JSX.Element {
const { t } = useTranslation();
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('dashboard.lessonTypes.title')}
</Link>
</div>
<div>
<Typography variant="h4">{t('dashboard.lessonTypes.edit.title')}</Typography>
</div>
</Stack>
<LessonTypeEditForm />
</Stack>
</Box>
);
}

View File

@@ -1,37 +0,0 @@
import { dayjs } from '@/lib/dayjs';
import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
// import { helloworld } from '@/components/dashboard/lesson_type/helloworld';
// export const metadata = { title: `List | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
export const lessonTypesSampleData = [
{
id: 'USR-005',
name: 'Fran Perez',
type: 'vocabulary',
pos: 1,
visible: 'visible',
avatar: '/assets/avatar-5.png',
email: 'fran.perez@domain.com',
phone: '(815) 704-0045',
quota: 50,
status: 'active',
createdAt: dayjs().subtract(1, 'hour').toDate(),
},
{
id: 'USR-004',
name: 'Penjani Inyene',
type: 'connectives',
pos: 1,
visible: 'visible',
avatar: '/assets/avatar-4.png',
email: 'penjani.inyene@domain.com',
phone: '(803) 937-8925',
quota: 100,
status: 'active',
createdAt: dayjs().subtract(3, 'hour').toDate(),
},
] satisfies LessonType[];
export const lessonTypesData = (): LessonType[] => {
return lessonTypesSampleData;
};

View File

@@ -1,33 +0,0 @@
import { dayjs } from '@/lib/dayjs';
import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
// import { helloworld } from '@/components/dashboard/lesson_type/helloworld';
// export const metadata = { title: `List | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
export const lessonTypesSampleData = [
{
id: 'USR-005',
name: 'Vocabulary',
type: 'vocabulary',
pos: 1,
visible: 'visible',
avatar: '/assets/avatar-5.png',
email: 'fran.perez@domain.com',
phone: '(815) 704-0045',
quota: 50,
status: 'active',
createdAt: dayjs().subtract(1, 'hour').toDate(),
},
{
id: 'USR-004',
name: 'Connectives',
type: 'connectives',
pos: 2,
visible: 'visible',
avatar: '/assets/avatar-4.png',
email: 'penjani.inyene@domain.com',
phone: '(803) 937-8925',
quota: 100,
status: 'active',
createdAt: dayjs().subtract(3, 'hour').toDate(),
},
] satisfies LessonType[];

View File

@@ -1,169 +0,0 @@
'use client';
import * as React from 'react';
import { useRouter } from 'next/navigation';
import { LoadingButton } from '@mui/lab';
import Box from '@mui/material/Box';
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 { useTranslation } from 'react-i18next';
import { paths } from '@/paths';
import { logger } from '@/lib/default-logger';
import { toast } from '@/components/core/toaster';
import { listLessonTypes } from '@/components/dashboard/lesson_type/http-actions';
import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
import { LessonTypesFilters } from '@/components/dashboard/lesson_type/lesson-types-filters';
import type { Filters } from '@/components/dashboard/lesson_type/lesson-types-filters';
import { LessonTypesPagination } from '@/components/dashboard/lesson_type/lesson-types-pagination';
import { LessonTypesSelectionProvider } from '@/components/dashboard/lesson_type/lesson-types-selection-context';
import { LessonTypesTable } from '@/components/dashboard/lesson_type/lesson-types-table';
import FormLoading from '@/components/loading';
interface PageProps {
searchParams: {
email?: string;
phone?: string;
sortDir?: 'asc' | 'desc';
status?: string;
name?: string;
visible?: string;
type?: string;
//
};
}
export default function Page({ searchParams }: PageProps): React.JSX.Element {
const { t } = useTranslation();
const { email, phone, sortDir, status, name, visible, type } = searchParams;
const router = useRouter();
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
const [lessonTypesData, setLessonTypesData] = React.useState<LessonType[]>([]);
const sortedLessonTypes = applySort(lessonTypesData, sortDir);
const filteredLessonTypes = applyFilters(sortedLessonTypes, {
email,
phone,
status,
name,
type,
visible,
//
});
const reloadRows = () => {
listLessonTypes()
.then((lessonTypes: LessonType[]) => {
setLessonTypesData(lessonTypes);
})
.catch((err) => {
logger.error(err);
toast(t('dashboard.lessonTypes.list.error'));
});
};
React.useEffect(() => {
reloadRows();
}, []);
if (lessonTypesData.length < 1) return <FormLoading />;
return (
<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">{t('Lesson Types')}</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<LoadingButton
loading={isLoadingAddPage}
onClick={(): void => {
setIsLoadingAddPage(true);
router.push(paths.dashboard.lesson_types.create);
}}
startIcon={<PlusIcon />}
variant="contained"
>
{/* add new lesson type */}
{t('dashboard.lessonTypes.add')}
</LoadingButton>
</Box>
</Stack>
<LessonTypesSelectionProvider lessonTypes={filteredLessonTypes}>
<Card>
<LessonTypesFilters
filters={{ email, phone, status, name, visible, type }}
fullData={lessonTypesData}
sortDir={sortDir}
/>
<Divider />
<Box sx={{ overflowX: 'auto' }}>
<LessonTypesTable reloadRows={reloadRows} rows={filteredLessonTypes} />
</Box>
<Divider />
<LessonTypesPagination count={filteredLessonTypes.length + 100} page={0} />
</Card>
</LessonTypesSelectionProvider>
</Stack>
</Box>
);
}
// Sorting and filtering has to be done on the server.
function applySort(row: LessonType[], sortDir: 'asc' | 'desc' | undefined): LessonType[] {
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: LessonType[], { email, phone, status, name, visible }: Filters): LessonType[] {
return row.filter((item) => {
if (email) {
if (!item.email?.toLowerCase().includes(email.toLowerCase())) {
return false;
}
}
if (phone) {
if (!item.phone?.toLowerCase().includes(phone.toLowerCase())) {
return false;
}
}
if (status) {
if (item.status !== status) {
return false;
}
}
if (name) {
if (!item.name?.toLowerCase().includes(name.toLowerCase())) {
return false;
}
}
if (visible) {
if (!item.visible?.toLowerCase().includes(visible.toLowerCase())) {
return false;
}
}
return true;
});
}