update build ok,
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
"lint:fix": "next lint --fix",
|
"lint:fix": "next lint --fix",
|
||||||
"lint:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 2 --exec \"pnpm run lint\"",
|
"lint:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 2 --exec \"pnpm run lint\"",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
|
"typecheck:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 2 --exec \"pnpm run typecheck\"",
|
||||||
"format:write": "prettier --write \"**/*.{js,jsx,mjs,ts,tsx,mdx}\" --cache",
|
"format:write": "prettier --write \"**/*.{js,jsx,mjs,ts,tsx,mdx}\" --cache",
|
||||||
"format:check": "prettier --check \"**/*.{js,jsx,mjs,ts,tsx,mdx}\" --cache",
|
"format:check": "prettier --check \"**/*.{js,jsx,mjs,ts,tsx,mdx}\" --cache",
|
||||||
"format:fix": "prettier --write \"**/*.{js,jsx,mjs,ts,tsx,mdx}\" --cache",
|
"format:fix": "prettier --write \"**/*.{js,jsx,mjs,ts,tsx,mdx}\" --cache",
|
||||||
|
@@ -186,5 +186,7 @@
|
|||||||
"dashboard.lessonCategorys.edit.visible": "顯示",
|
"dashboard.lessonCategorys.edit.visible": "顯示",
|
||||||
"dashboard.lessonCategorys.edit.hidden": "隱藏",
|
"dashboard.lessonCategorys.edit.hidden": "隱藏",
|
||||||
"dashboard.lessonCategorys.edit.cancelButton": "取消",
|
"dashboard.lessonCategorys.edit.cancelButton": "取消",
|
||||||
"dashboard.lessonCategorys.edit.updateButton": "更新"
|
"dashboard.lessonCategorys.edit.updateButton": "更新",
|
||||||
|
"dashboard.lessonCategorys.list.title": "課堂種類",
|
||||||
|
"word-count": "字數"
|
||||||
}
|
}
|
@@ -26,6 +26,7 @@ import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr
|
|||||||
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
|
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
|
||||||
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
|
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
|
||||||
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
|
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { config } from '@/config';
|
import { config } from '@/config';
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
@@ -40,6 +41,8 @@ import { ShippingAddress } from '@/components/dashboard/lesson_category/shipping
|
|||||||
// export const metadata = { title: `Details | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
|
// export const metadata = { title: `Details | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
|
||||||
|
|
||||||
export default function Page(): React.JSX.Element {
|
export default function Page(): React.JSX.Element {
|
||||||
|
let { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -60,7 +63,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
variant="subtitle2"
|
variant="subtitle2"
|
||||||
>
|
>
|
||||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
Lesson Categories
|
{t('dashboard.lessonCategorys.list.title')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
||||||
|
@@ -17,11 +17,7 @@ import { paths } from '@/paths';
|
|||||||
import { logger } from '@/lib/default-logger';
|
import { logger } from '@/lib/default-logger';
|
||||||
import { pb } from '@/lib/pb';
|
import { pb } from '@/lib/pb';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
import {
|
import { defaultLessonCategory, type LessonCategory } from '@/components/dashboard/lesson_category/interfaces';
|
||||||
DBLessonCategory,
|
|
||||||
defaultLessonCategory,
|
|
||||||
type LessonCategory,
|
|
||||||
} from '@/components/dashboard/lesson_category/interfaces';
|
|
||||||
import { LessonCategoriesFilters } from '@/components/dashboard/lesson_category/lesson-categories-filters';
|
import { LessonCategoriesFilters } from '@/components/dashboard/lesson_category/lesson-categories-filters';
|
||||||
import type { Filters } 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 { LessonCategoriesPagination } from '@/components/dashboard/lesson_category/lesson-categories-pagination';
|
||||||
@@ -52,18 +48,21 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
const [recordCount, setRecordCount] = React.useState<number>(0);
|
const [recordCount, setRecordCount] = React.useState<number>(0);
|
||||||
const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
|
const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
|
||||||
const [currentPage, setCurrentPage] = React.useState<number>(1);
|
const [currentPage, setCurrentPage] = React.useState<number>(1);
|
||||||
const sortedLessonCategories = applySort(lessonCategoriesSampleData, sortDir);
|
|
||||||
const filteredLessonCategories = applyFilters(sortedLessonCategories, { email, phone, status: spStatus });
|
|
||||||
//
|
//
|
||||||
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
||||||
const [lessonCategoriesData, setLessonCategoriesData] = React.useState<LessonCategory[]>([]);
|
const [lessonCategoriesData, setLessonCategoriesData] = React.useState<LessonCategory[]>([]);
|
||||||
|
|
||||||
|
const sortedLessonCategories = applySort(lessonCategoriesData, sortDir);
|
||||||
|
const filteredLessonCategories = applyFilters(sortedLessonCategories, { email, phone, status: spStatus });
|
||||||
|
|
||||||
const reloadRows = () => {
|
const reloadRows = () => {
|
||||||
pb.collection(COL_LESSON_CATEGORIES)
|
pb.collection(COL_LESSON_CATEGORIES)
|
||||||
.getList(currentPage, rowsPerPage, {})
|
.getList(currentPage, rowsPerPage, {})
|
||||||
.then((lessonCategories: ListResult<RecordModel>) => {
|
.then((lessonCategories: ListResult<RecordModel>) => {
|
||||||
// console.log(lessonTypes);
|
// console.log(lessonTypes);
|
||||||
const { items, page, perPage, totalItems, totalPages } = lessonCategories;
|
const { items, page, perPage, totalItems, totalPages } = lessonCategories;
|
||||||
const tempLessonCategories: DBLessonCategory[] = items.map((item) => {
|
const tempLessonCategories: LessonCategory[] = items.map((item) => {
|
||||||
return { ...defaultLessonCategory, ...item };
|
return { ...defaultLessonCategory, ...item };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -82,6 +81,8 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
|
|
||||||
if (lessonCategoriesData.length < 1) return <FormLoading />;
|
if (lessonCategoriesData.length < 1) return <FormLoading />;
|
||||||
|
|
||||||
|
// return <pre>{JSON.stringify(lessonCategoriesData, null, 2)}</pre>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -94,7 +95,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
||||||
<Box sx={{ flex: '1 1 auto' }}>
|
<Box sx={{ flex: '1 1 auto' }}>
|
||||||
<Typography variant="h4">{t('Lesson Categories')}</Typography>
|
<Typography variant="h4">{t('dashboard.lessonCategorys.list.title')}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
@@ -115,7 +116,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
<LessonCategoriesFilters filters={{ email, phone, status: spStatus }} sortDir={sortDir} />
|
<LessonCategoriesFilters filters={{ email, phone, status: spStatus }} sortDir={sortDir} />
|
||||||
<Divider />
|
<Divider />
|
||||||
<Box sx={{ overflowX: 'auto' }}>
|
<Box sx={{ overflowX: 'auto' }}>
|
||||||
<LessonCategoriesTable rows={filteredLessonCategories} />
|
<LessonCategoriesTable reloadRows={reloadRows} rows={filteredLessonCategories} />
|
||||||
</Box>
|
</Box>
|
||||||
<Divider />
|
<Divider />
|
||||||
<LessonCategoriesPagination count={filteredLessonCategories.length + 100} page={0} />
|
<LessonCategoriesPagination count={filteredLessonCategories.length + 100} page={0} />
|
||||||
@@ -140,17 +141,17 @@ function applySort(row: LessonCategory[], sortDir: 'asc' | 'desc' | undefined):
|
|||||||
|
|
||||||
function applyFilters(row: LessonCategory[], { email, phone, status }: Filters): LessonCategory[] {
|
function applyFilters(row: LessonCategory[], { email, phone, status }: Filters): LessonCategory[] {
|
||||||
return row.filter((item) => {
|
return row.filter((item) => {
|
||||||
if (email) {
|
// if (email) {
|
||||||
if (!item.email?.toLowerCase().includes(email.toLowerCase())) {
|
// if (!item.email?.toLowerCase().includes(email.toLowerCase())) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (phone) {
|
// if (phone) {
|
||||||
if (!item.phone?.toLowerCase().includes(phone.toLowerCase())) {
|
// if (!item.phone?.toLowerCase().includes(phone.toLowerCase())) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
if (item.status !== status) {
|
if (item.status !== status) {
|
||||||
|
@@ -0,0 +1,123 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { COL_LESSON_TYPES } from '@/constants';
|
||||||
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
import { Button, Container, Modal, Paper } from '@mui/material';
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { Note as NoteIcon } from '@phosphor-icons/react/dist/ssr/Note';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { logger } from '@/lib/default-logger';
|
||||||
|
import { toast } from '@/components/core/toaster';
|
||||||
|
|
||||||
|
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);
|
||||||
|
|
||||||
|
export default function ConfirmDeleteModal({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
idToDelete,
|
||||||
|
reloadRows,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (b: boolean) => void;
|
||||||
|
idToDelete: string;
|
||||||
|
reloadRows: () => void;
|
||||||
|
}): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// const handleClose = () => setOpen(false);
|
||||||
|
function handleClose(): void {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [isDeleteing, setIsDeleteing] = React.useState(false);
|
||||||
|
const style = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
};
|
||||||
|
|
||||||
|
function performDelete(id: string): Promise<void> {
|
||||||
|
return pb
|
||||||
|
.collection(COL_LESSON_TYPES)
|
||||||
|
.delete(id)
|
||||||
|
.then(() => {
|
||||||
|
toast(t('dashboard.lessonTypes.delete.success'));
|
||||||
|
reloadRows();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(err);
|
||||||
|
toast(t('dashboard.lessonTypes.delete.error'));
|
||||||
|
})
|
||||||
|
.finally(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUserConfirmDelete(): void {
|
||||||
|
if (idToDelete) {
|
||||||
|
setIsDeleteing(true);
|
||||||
|
performDelete(idToDelete)
|
||||||
|
.then(() => {
|
||||||
|
handleClose();
|
||||||
|
setIsDeleteing(false);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
// console.error(err)
|
||||||
|
logger.error(err);
|
||||||
|
toast(t('dashboard.lessonTypes.delete.error'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
aria-labelledby="modal-modal-title"
|
||||||
|
aria-describedby="modal-modal-description"
|
||||||
|
>
|
||||||
|
<Box sx={style}>
|
||||||
|
<Container maxWidth="sm">
|
||||||
|
<Paper sx={{ border: '1px solid var(--mui-palette-divider)', boxShadow: 'var(--mui-shadows-16)' }}>
|
||||||
|
<Stack direction="row" spacing={2} sx={{ display: 'flex', p: 3 }}>
|
||||||
|
<Avatar sx={{ bgcolor: 'var(--mui-palette-error-50)', color: 'var(--mui-palette-error-main)' }}>
|
||||||
|
<NoteIcon fontSize="var(--Icon-fontSize)" />
|
||||||
|
</Avatar>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<Typography variant="h5">{t('Delete Lesson Type ?')}</Typography>
|
||||||
|
<Typography color="text.secondary" variant="body2">
|
||||||
|
{t('Are you sure you want to delete lesson type ?')}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
<Stack direction="row" spacing={2} sx={{ justifyContent: 'flex-end' }}>
|
||||||
|
<Button color="secondary" onClick={handleClose}>
|
||||||
|
{t('Cancel')}
|
||||||
|
</Button>
|
||||||
|
<LoadingButton
|
||||||
|
color="error"
|
||||||
|
variant="contained"
|
||||||
|
onClick={(e) => {
|
||||||
|
handleUserConfirmDelete();
|
||||||
|
}}
|
||||||
|
loading={isDeleteing}
|
||||||
|
>
|
||||||
|
{t('Delete')}
|
||||||
|
</LoadingButton>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -13,29 +13,15 @@ export interface LessonCategory {
|
|||||||
description: string;
|
description: string;
|
||||||
remarks: string;
|
remarks: string;
|
||||||
//
|
//
|
||||||
name?: string;
|
name: string;
|
||||||
avatar?: string;
|
avatar: string;
|
||||||
email?: string;
|
email: string;
|
||||||
phone?: string;
|
phone: string;
|
||||||
quota: number;
|
quota: number;
|
||||||
status: 'pending' | 'active' | 'blocked' | 'NA';
|
status: 'pending' | 'active' | 'blocked' | 'NA';
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DBLessonCategory {
|
|
||||||
id: string;
|
|
||||||
cat_name: string;
|
|
||||||
cat_image_url?: string;
|
|
||||||
cat_image?: string;
|
|
||||||
pos: number;
|
|
||||||
visible: string;
|
|
||||||
lesson_id: string;
|
|
||||||
description: string;
|
|
||||||
remarks: string;
|
|
||||||
createdAt: Date;
|
|
||||||
status: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultLessonCategory: LessonCategory = {
|
export const defaultLessonCategory: LessonCategory = {
|
||||||
id: 'default-id',
|
id: 'default-id',
|
||||||
cat_name: 'default-category-name',
|
cat_name: 'default-category-name',
|
||||||
@@ -46,8 +32,13 @@ export const defaultLessonCategory: LessonCategory = {
|
|||||||
lesson_id: 'default-lesson-id',
|
lesson_id: 'default-lesson-id',
|
||||||
description: 'default-description',
|
description: 'default-description',
|
||||||
remarks: 'default-remarks',
|
remarks: 'default-remarks',
|
||||||
|
//
|
||||||
createdAt: dayjs('2099-01-01').toDate(),
|
createdAt: dayjs('2099-01-01').toDate(),
|
||||||
|
//
|
||||||
|
name: '',
|
||||||
|
avatar: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
quota: 0,
|
quota: 0,
|
||||||
status: 'NA',
|
status: 'NA',
|
||||||
};
|
};
|
||||||
|
@@ -4,6 +4,7 @@ import * as React from 'react';
|
|||||||
import RouterLink from 'next/link';
|
import RouterLink from 'next/link';
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import LinearProgress from '@mui/material/LinearProgress';
|
import LinearProgress from '@mui/material/LinearProgress';
|
||||||
@@ -12,40 +13,48 @@ import Stack from '@mui/material/Stack';
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
|
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
|
||||||
import { Clock as ClockIcon } from '@phosphor-icons/react/dist/ssr/Clock';
|
import { Clock as ClockIcon } from '@phosphor-icons/react/dist/ssr/Clock';
|
||||||
|
import { Images as ImagesIcon } from '@phosphor-icons/react/dist/ssr/Images';
|
||||||
import { Minus as MinusIcon } from '@phosphor-icons/react/dist/ssr/Minus';
|
import { Minus as MinusIcon } from '@phosphor-icons/react/dist/ssr/Minus';
|
||||||
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
|
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
|
||||||
|
import { TrashSimple as TrashSimpleIcon } from '@phosphor-icons/react/dist/ssr/TrashSimple';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
import { DataTable } from '@/components/core/data-table';
|
import { DataTable } from '@/components/core/data-table';
|
||||||
import type { ColumnDef } from '@/components/core/data-table';
|
import type { ColumnDef } from '@/components/core/data-table';
|
||||||
|
|
||||||
|
import ConfirmDeleteModal from './confirm-delete-modal';
|
||||||
import type { LessonCategory } from './interfaces';
|
import type { LessonCategory } from './interfaces';
|
||||||
import { useLessonCategoriesSelection } from './lesson-categories-selection-context';
|
import { useLessonCategoriesSelection } from './lesson-categories-selection-context';
|
||||||
|
|
||||||
const columns = [
|
function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonCategory>[] {
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
formatter: (row): React.JSX.Element => (
|
formatter: (row): React.JSX.Element => (
|
||||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
|
||||||
<Avatar src={row.avatar} />{' '}
|
|
||||||
<div>
|
|
||||||
<Link
|
<Link
|
||||||
color="inherit"
|
color="inherit"
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
href={paths.dashboard.lesson_categories.details('1')}
|
href={paths.dashboard.lesson_categories.details(row.id)}
|
||||||
sx={{ whiteSpace: 'nowrap' }}
|
sx={{ whiteSpace: 'nowrap' }}
|
||||||
variant="subtitle2"
|
variant="subtitle2"
|
||||||
>
|
>
|
||||||
{row.name}
|
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||||
</Link>
|
<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">
|
<Typography color="text.secondary" variant="body2">
|
||||||
{row.email}
|
slug: {row.cat_name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Link>
|
||||||
),
|
),
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
width: '250px',
|
width: '200px',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
formatter: (row): React.JSX.Element => (
|
formatter: (row): React.JSX.Element => (
|
||||||
@@ -56,17 +65,13 @@ const columns = [
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
),
|
),
|
||||||
name: 'Quota',
|
// NOTE: please refer to translation.json here
|
||||||
width: '250px',
|
name: 'word-count',
|
||||||
},
|
width: '100px',
|
||||||
{ field: 'phone', name: 'Phone number', width: '150px' },
|
|
||||||
{
|
|
||||||
formatter(row) {
|
|
||||||
return dayjs(row.createdAt).format('MMM D, YYYY h:mm A');
|
|
||||||
},
|
|
||||||
name: 'Created at',
|
|
||||||
width: '200px',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{ field: 'phone', name: 'Phone number', width: '100px' },
|
||||||
|
|
||||||
{
|
{
|
||||||
formatter: (row): React.JSX.Element => {
|
formatter: (row): React.JSX.Element => {
|
||||||
const mapping = {
|
const mapping = {
|
||||||
@@ -82,6 +87,13 @@ const columns = [
|
|||||||
name: 'Status',
|
name: 'Status',
|
||||||
width: '150px',
|
width: '150px',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
formatter(row) {
|
||||||
|
return dayjs(row.createdAt).format('MMM D, YYYY');
|
||||||
|
},
|
||||||
|
name: 'Created at',
|
||||||
|
width: '100px',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
formatter: (): React.JSX.Element => (
|
formatter: (): React.JSX.Element => (
|
||||||
<IconButton component={RouterLink} href={paths.dashboard.lesson_categories.details('1')}>
|
<IconButton component={RouterLink} href={paths.dashboard.lesson_categories.details('1')}>
|
||||||
@@ -93,19 +105,31 @@ const columns = [
|
|||||||
width: '100px',
|
width: '100px',
|
||||||
align: 'right',
|
align: 'right',
|
||||||
},
|
},
|
||||||
] satisfies ColumnDef<LessonCategory>[];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
export interface LessonCategoriesTableProps {
|
export interface LessonCategoriesTableProps {
|
||||||
rows: LessonCategory[];
|
rows: LessonCategory[];
|
||||||
|
reloadRows: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LessonCategoriesTable({ rows }: LessonCategoriesTableProps): React.JSX.Element {
|
export function LessonCategoriesTable({ rows, reloadRows }: LessonCategoriesTableProps): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { deselectAll, deselectOne, selectAll, selectOne, selected } = useLessonCategoriesSelection();
|
const { deselectAll, deselectOne, selectAll, selectOne, selected } = useLessonCategoriesSelection();
|
||||||
|
|
||||||
|
const [idToDelete, setIdToDelete] = React.useState('');
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
|
function handleDeleteClick(testId: string): void {
|
||||||
|
setOpen(true);
|
||||||
|
setIdToDelete(testId);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
<ConfirmDeleteModal idToDelete={idToDelete} open={open} reloadRows={reloadRows} setOpen={setOpen} />
|
||||||
<DataTable<LessonCategory>
|
<DataTable<LessonCategory>
|
||||||
columns={columns}
|
columns={columns(handleDeleteClick)}
|
||||||
onDeselectAll={deselectAll}
|
onDeselectAll={deselectAll}
|
||||||
onDeselectOne={(_, row) => {
|
onDeselectOne={(_, row) => {
|
||||||
deselectOne(row.id);
|
deselectOne(row.id);
|
||||||
@@ -121,7 +145,8 @@ export function LessonCategoriesTable({ rows }: LessonCategoriesTableProps): Rea
|
|||||||
{!rows.length ? (
|
{!rows.length ? (
|
||||||
<Box sx={{ p: 3 }}>
|
<Box sx={{ p: 3 }}>
|
||||||
<Typography color="text.secondary" sx={{ textAlign: 'center' }} variant="body2">
|
<Typography color="text.secondary" sx={{ textAlign: 'center' }} variant="body2">
|
||||||
No lesson categories found
|
{/* TODO: use hyphen here */}
|
||||||
|
{t('No lesson categories found')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
|
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import RouterLink from 'next/link';
|
import RouterLink from 'next/link';
|
||||||
import { NO_NUM, NO_VALUE } from '@/constants';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import { Button } from '@mui/material';
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import LinearProgress from '@mui/material/LinearProgress';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
@@ -20,7 +21,6 @@ import { toast } from 'sonner';
|
|||||||
|
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
import { i18n } from '@/lib/i18n';
|
|
||||||
import { DataTable } from '@/components/core/data-table';
|
import { DataTable } from '@/components/core/data-table';
|
||||||
import type { ColumnDef } from '@/components/core/data-table';
|
import type { ColumnDef } from '@/components/core/data-table';
|
||||||
|
|
||||||
@@ -196,6 +196,7 @@ export function LessonTypesTable({ rows, reloadRows }: LessonTypesTableProps): R
|
|||||||
{!rows.length ? (
|
{!rows.length ? (
|
||||||
<Box sx={{ p: 3 }}>
|
<Box sx={{ p: 3 }}>
|
||||||
<Typography color="text.secondary" sx={{ textAlign: 'center' }} variant="body2">
|
<Typography color="text.secondary" sx={{ textAlign: 'center' }} variant="body2">
|
||||||
|
{/* TODO: use hyphen here */}
|
||||||
{t('No lesson types found')}
|
{t('No lesson types found')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
Reference in New Issue
Block a user