update page,
This commit is contained in:
9
002_source/cms/src/app/_helloworld/_PROMPT.MD
Normal file
9
002_source/cms/src/app/_helloworld/_PROMPT.MD
Normal file
@@ -0,0 +1,9 @@
|
||||
# task
|
||||
|
||||
## instruction
|
||||
|
||||
with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/_helloworld/page.tsx`
|
||||
|
||||
please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/error/index.tsx`
|
||||
|
||||
please draft a tsx for showing error to user thanks,
|
55
002_source/cms/src/app/_helloworld/page.tsx
Normal file
55
002_source/cms/src/app/_helloworld/page.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ErrorDisplay from '@/components/dashboard/error';
|
||||
import FormLoading from '@/components/loading';
|
||||
|
||||
interface PageProps {
|
||||
hello: {
|
||||
world: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default function Page({ hello }: PageProps): React.JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = React.useState<string>(hello.world);
|
||||
|
||||
//
|
||||
const [showError, setShowError] = React.useState<boolean>(false);
|
||||
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
setShowLoading(false);
|
||||
setShowError(false);
|
||||
setState('blablabla');
|
||||
}, []);
|
||||
|
||||
if (showLoading) return <FormLoading />;
|
||||
|
||||
if (showError)
|
||||
return (
|
||||
<ErrorDisplay
|
||||
message={t('"Unable to process request"')}
|
||||
code="500"
|
||||
details={t('Detailed error information...')}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: 'var(--Content-maxWidth)',
|
||||
m: 'var(--Content-margin)',
|
||||
p: 'var(--Content-padding)',
|
||||
width: 'var(--Content-width)',
|
||||
}}
|
||||
>
|
||||
<Typography variant="h1">Hello World</Typography>
|
||||
{state}
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -17,6 +17,7 @@ import { paths } from '@/paths';
|
||||
import { logger } from '@/lib/default-logger';
|
||||
import { pb } from '@/lib/pb';
|
||||
import { toast } from '@/components/core/toaster';
|
||||
import ErrorDisplay from '@/components/dashboard/error';
|
||||
import { defaultLessonCategory, type LessonCategory } from '@/components/dashboard/lesson_category/interfaces';
|
||||
import { LessonCategoriesFilters } from '@/components/dashboard/lesson_category/lesson-categories-filters';
|
||||
import type { Filters } from '@/components/dashboard/lesson_category/lesson-categories-filters';
|
||||
@@ -48,6 +49,9 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
const [recordCount, setRecordCount] = React.useState<number>(0);
|
||||
const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
|
||||
const [currentPage, setCurrentPage] = React.useState<number>(1);
|
||||
//
|
||||
const [showError, setShowError] = React.useState<boolean>(false);
|
||||
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||
|
||||
//
|
||||
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
||||
@@ -57,6 +61,8 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
const filteredLessonCategories = applyFilters(sortedLessonCategories, { email, phone, status: spStatus });
|
||||
|
||||
const reloadRows = () => {
|
||||
setShowLoading(true);
|
||||
|
||||
pb.collection(COL_LESSON_CATEGORIES)
|
||||
.getList(currentPage, rowsPerPage, {})
|
||||
.then((lessonCategories: ListResult<RecordModel>) => {
|
||||
@@ -72,6 +78,10 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
.catch((err) => {
|
||||
logger.error(err);
|
||||
toast(t('dashboard.lessonTypes.list.error'));
|
||||
setShowError(true);
|
||||
})
|
||||
.finally(() => {
|
||||
setShowLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -79,7 +89,16 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
reloadRows();
|
||||
}, []);
|
||||
|
||||
if (lessonCategoriesData.length < 1) return <FormLoading />;
|
||||
if (showLoading) return <FormLoading />;
|
||||
|
||||
if (showError)
|
||||
return (
|
||||
<ErrorDisplay
|
||||
message={t('"Unable to process request"')}
|
||||
code="500"
|
||||
details={t('Detailed error information...')}
|
||||
/>
|
||||
);
|
||||
|
||||
// return <pre>{JSON.stringify(lessonCategoriesData, null, 2)}</pre>;
|
||||
|
||||
|
50
002_source/cms/src/components/_helloworld/index.tsx
Normal file
50
002_source/cms/src/components/_helloworld/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import { Box } from '@mui/system';
|
||||
import { Trash as TrashIcon } from '@phosphor-icons/react/dist/ssr/Trash';
|
||||
|
||||
interface PropsHelloworld {
|
||||
message: string;
|
||||
}
|
||||
|
||||
// RULES: Sample of function
|
||||
function funcHelloworld(hello: string): string {
|
||||
const helloworld: PropsHelloworld = { message: hello };
|
||||
const output = `${helloworld.message} world!`;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// RULES: sample of inner component
|
||||
function InnerComponent(): React.JSX.Element {
|
||||
return (
|
||||
<Box>
|
||||
<Typography sx={{ color: 'red' }}>inner component</Typography>
|
||||
<ListItemIcon />
|
||||
<TrashIcon />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// RULES: naming should be in Pascal case
|
||||
// RULES: sample of main component
|
||||
function MainComponent(): React.JSX.Element {
|
||||
const [state, setState] = React.useState<string>('');
|
||||
|
||||
React.useEffect(() => {
|
||||
setState(funcHelloworld('hello'));
|
||||
}, []);
|
||||
|
||||
// you should obey react/jsx-no-useless-fragment
|
||||
return (
|
||||
<Box>
|
||||
{state} <InnerComponent />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// RULES: component should be exported
|
||||
export default MainComponent;
|
9
002_source/cms/src/components/dashboard/error/_PROMPT.MD
Normal file
9
002_source/cms/src/components/dashboard/error/_PROMPT.MD
Normal file
@@ -0,0 +1,9 @@
|
||||
# task
|
||||
|
||||
## instruction
|
||||
|
||||
with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/_helloworld/index.tsx`
|
||||
|
||||
please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/error/index.tsx`
|
||||
|
||||
please draft a tsx for showing error to user thanks,
|
84
002_source/cms/src/components/dashboard/error/index.tsx
Normal file
84
002_source/cms/src/components/dashboard/error/index.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Alert, AlertTitle, Collapse, Typography } from '@mui/material';
|
||||
import { Box } from '@mui/system';
|
||||
import { Warning as WarningIcon } from '@phosphor-icons/react/dist/ssr/Warning';
|
||||
|
||||
interface PropsError {
|
||||
message: string;
|
||||
code?: string | number;
|
||||
details?: string;
|
||||
severity?: 'error' | 'warning' | 'info' | 'success';
|
||||
}
|
||||
|
||||
// RULES: Sample of function
|
||||
function formatErrorMessage(message: string, code?: string | number): string {
|
||||
return code ? `[${code}] ${message}` : message;
|
||||
}
|
||||
|
||||
// RULES: sample of inner component
|
||||
function ErrorDetails({ details }: { details: string }): React.JSX.Element {
|
||||
const [expanded, setExpanded] = React.useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'underline',
|
||||
mt: 1,
|
||||
}}
|
||||
onClick={() => {
|
||||
setExpanded(!expanded);
|
||||
}}
|
||||
>
|
||||
{expanded ? 'Hide Details' : 'Show Details'}
|
||||
</Typography>
|
||||
|
||||
<Collapse in={expanded}>
|
||||
<Box sx={{ mt: 1, p: 1, bgcolor: 'background.paper' }}>
|
||||
<Typography variant="body2" component="pre" sx={{ whiteSpace: 'pre-wrap' }}>
|
||||
{details}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// RULES: naming should be in Pascal case
|
||||
// RULES: sample of main component
|
||||
function ErrorDisplay({ message, code, details, severity = 'error' }: PropsError): React.JSX.Element {
|
||||
const [formattedMessage, setFormattedMessage] = React.useState<string>('');
|
||||
|
||||
React.useEffect(() => {
|
||||
setFormattedMessage(formatErrorMessage(message, code));
|
||||
}, [message, code]);
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2, maxWidth: 800, width: '100%' }}>
|
||||
<Alert
|
||||
severity={severity}
|
||||
icon={<WarningIcon weight="fill" />}
|
||||
sx={{
|
||||
'& .MuiAlert-message': {
|
||||
width: '100%',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AlertTitle>{code ? `Error ${code}` : 'Error'}</AlertTitle>
|
||||
|
||||
<Typography variant="body1" gutterBottom>
|
||||
{formattedMessage}
|
||||
</Typography>
|
||||
|
||||
{details && <ErrorDetails details={details} />}
|
||||
</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// RULES: component should be exported
|
||||
export default ErrorDisplay;
|
@@ -33,25 +33,27 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonC
|
||||
return [
|
||||
{
|
||||
formatter: (row): React.JSX.Element => (
|
||||
<Link
|
||||
color="inherit"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.lesson_categories.details(row.id)}
|
||||
sx={{ whiteSpace: 'nowrap' }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||
<Avatar src={row.avatar} variant="rounded">
|
||||
<ImagesIcon size={32} />
|
||||
</Avatar>{' '}
|
||||
<div>
|
||||
<Box sx={{ whiteSpace: 'nowrap' }}>{row.cat_name}</Box>
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
slug: {row.cat_name}
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
</Link>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||
<Link
|
||||
color="inherit"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.lesson_categories.details(row.id)}
|
||||
sx={{ whiteSpace: 'nowrap' }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||
<Avatar src={row.avatar} variant="rounded">
|
||||
<ImagesIcon size={32} />
|
||||
</Avatar>{' '}
|
||||
<div>
|
||||
<Box sx={{ whiteSpace: 'nowrap' }}>{row.cat_name}</Box>
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
slug: {row.cat_name}
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
</Link>
|
||||
</Stack>
|
||||
),
|
||||
name: 'Name',
|
||||
width: '200px',
|
||||
@@ -74,6 +76,9 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonC
|
||||
|
||||
{
|
||||
formatter: (row): React.JSX.Element => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { t } = useTranslation();
|
||||
|
||||
const mapping = {
|
||||
active: { label: 'Active', icon: <CheckCircleIcon color="var(--mui-palette-success-main)" weight="fill" /> },
|
||||
blocked: { label: 'Blocked', icon: <MinusIcon color="var(--mui-palette-error-main)" /> },
|
||||
@@ -82,7 +87,17 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonC
|
||||
} as const;
|
||||
const { label, icon } = mapping[row.status] ?? { label: 'Unknown', icon: null };
|
||||
|
||||
return <Chip icon={icon} label={label} size="small" variant="outlined" />;
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
toast.error('sorry but not implementd');
|
||||
}}
|
||||
style={{ backgroundColor: 'transparent' }}
|
||||
>
|
||||
{/* <Chip icon={icon} label={label} size="small" variant="outlined" /> */}
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
name: 'Status',
|
||||
width: '150px',
|
||||
@@ -95,10 +110,25 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonC
|
||||
width: '100px',
|
||||
},
|
||||
{
|
||||
formatter: (): React.JSX.Element => (
|
||||
<IconButton component={RouterLink} href={paths.dashboard.lesson_categories.details('1')}>
|
||||
<PencilSimpleIcon />
|
||||
</IconButton>
|
||||
formatter: (row): React.JSX.Element => (
|
||||
<Stack direction="row" spacing={1}>
|
||||
<IconButton
|
||||
//
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.lesson_categories.details(row.id)}
|
||||
>
|
||||
<PencilSimpleIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="error"
|
||||
disabled={row.isEmpty}
|
||||
onClick={() => {
|
||||
handleDeleteClick(row.id);
|
||||
}}
|
||||
>
|
||||
<TrashSimpleIcon />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
),
|
||||
name: 'Actions',
|
||||
hideName: true,
|
||||
|
@@ -33,17 +33,15 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonT
|
||||
{
|
||||
formatter: (row): React.JSX.Element => (
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||
<div>
|
||||
<Link
|
||||
color={row.isEmpty ? 'disabled' : 'inherit'}
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.lesson_types.details(row.id)}
|
||||
sx={{ whiteSpace: 'nowrap' }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
{row.isEmpty ? '--' : row.name}
|
||||
</Link>
|
||||
</div>
|
||||
<Link
|
||||
color={row.isEmpty ? 'disabled' : 'inherit'}
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.lesson_types.details(row.id)}
|
||||
sx={{ whiteSpace: 'nowrap' }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
{row.isEmpty ? '--' : row.name}
|
||||
</Link>
|
||||
</Stack>
|
||||
),
|
||||
name: 'Name',
|
||||
@@ -129,16 +127,21 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonT
|
||||
},
|
||||
{
|
||||
formatter(row) {
|
||||
return row.isEmpty ? '--' : dayjs(row.createdAt).format('MMM D, YYYY h:mm A');
|
||||
return row.isEmpty ? '--' : dayjs(row.createdAt).format('MMM D, YYYY ');
|
||||
},
|
||||
name: 'Created at',
|
||||
width: '200px',
|
||||
width: '100px',
|
||||
},
|
||||
|
||||
{
|
||||
formatter: (row): React.JSX.Element => (
|
||||
<Stack direction="row" spacing={1}>
|
||||
<IconButton disabled={row.isEmpty} component={RouterLink} href={paths.dashboard.lesson_types.details(row.id)}>
|
||||
<IconButton
|
||||
//
|
||||
disabled={row.isEmpty}
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.lesson_types.details(row.id)}
|
||||
>
|
||||
<PencilSimpleIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
@@ -153,6 +156,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonT
|
||||
</Stack>
|
||||
),
|
||||
name: 'Actions',
|
||||
hideName: true,
|
||||
width: '100px',
|
||||
align: 'right',
|
||||
},
|
||||
|
Reference in New Issue
Block a user