update page,

This commit is contained in:
louiscklaw
2025-04-17 06:20:47 +08:00
parent eef9e5ebd8
commit c4c392b91b
8 changed files with 299 additions and 39 deletions

View 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,

View 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>
);
}

View File

@@ -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>;

View 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;

View 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,

View 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;

View File

@@ -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,

View File

@@ -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',
},