update,
This commit is contained in:
3
002_source/cms/.gitignore
vendored
3
002_source/cms/.gitignore
vendored
@@ -1,3 +1,6 @@
|
|||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
**/*.draft
|
**/*.draft
|
||||||
**/~*
|
**/~*
|
||||||
**/*copy*.tsx
|
**/*copy*.tsx
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "rm -rf .next && next build",
|
"build": "rm -rf .next && next build",
|
||||||
"build:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 1 --exec \"pnpm run build\"",
|
"build:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 15 --exec \"pnpm run build\"",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint --quiet",
|
"lint": "next lint --quiet",
|
||||||
"lint:fix": "next lint --fix",
|
"lint:fix": "next lint --fix",
|
||||||
|
@@ -113,7 +113,6 @@
|
|||||||
"connective_revision": "連接詞 (Connective Revision)",
|
"connective_revision": "連接詞 (Connective Revision)",
|
||||||
"settings": "設定",
|
"settings": "設定",
|
||||||
"students": "學生",
|
"students": "學生",
|
||||||
"dashboard.lessonCategories.add": "新增課程分類",
|
|
||||||
"dashboard.lessonCategories.title": "課程分類",
|
"dashboard.lessonCategories.title": "課程分類",
|
||||||
"dashboard.lessonCategorys.edit.name": "課程分類名稱",
|
"dashboard.lessonCategorys.edit.name": "課程分類名稱",
|
||||||
"dashboard.lessonCategorys.edit.position": "課程分類順序",
|
"dashboard.lessonCategorys.edit.position": "課程分類順序",
|
||||||
|
@@ -59,7 +59,8 @@
|
|||||||
"message": "請建立課程類型",
|
"message": "請建立課程類型",
|
||||||
"create": "建立課程類型"
|
"create": "建立課程類型"
|
||||||
},
|
},
|
||||||
"error": "課程類型載入失敗"
|
"error": "課程類型載入失敗",
|
||||||
|
"add": "新增課程分類"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"basic-details": "基本資訊"
|
"basic-details": "基本資訊"
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"git.ignoreLimitWarning": true
|
|
||||||
}
|
|
@@ -19,7 +19,6 @@ import { pb } from '@/lib/pb';
|
|||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
import ErrorDisplay from '@/components/dashboard/error';
|
import ErrorDisplay from '@/components/dashboard/error';
|
||||||
import { defaultLessonCategory } from '@/components/dashboard/lesson_category/_constants';
|
import { defaultLessonCategory } from '@/components/dashboard/lesson_category/_constants';
|
||||||
// import { 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';
|
||||||
@@ -30,38 +29,27 @@ import FormLoading from '@/components/loading';
|
|||||||
|
|
||||||
// import { lessonCategoriesSampleData } from './lesson-categories-sample-data';
|
// import { lessonCategoriesSampleData } from './lesson-categories-sample-data';
|
||||||
|
|
||||||
interface PageProps {
|
|
||||||
searchParams: {
|
|
||||||
email?: string;
|
|
||||||
phone?: string;
|
|
||||||
sortDir?: 'asc' | 'desc';
|
|
||||||
spStatus?: string;
|
|
||||||
spName?: string;
|
|
||||||
spVisible?: string;
|
|
||||||
spType?: string;
|
|
||||||
//
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||||
const { t } = useTranslation(['common']);
|
const { t } = useTranslation(['lesson_category']);
|
||||||
const { email, phone, sortDir, spStatus } = searchParams;
|
const { email, phone, sortDir, status, name, visible, type } = searchParams;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [lessonCategoriesData, setLessonCategoriesData] = React.useState<LessonCategory[]>([]);
|
||||||
|
//
|
||||||
|
|
||||||
const [recordCount, setRecordCount] = React.useState<number>(0);
|
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
||||||
|
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||||
|
const [showError, setShowError] = React.useState<boolean>(false);
|
||||||
const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
|
const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
|
||||||
|
//
|
||||||
|
const [f, setF] = React.useState<LessonCategory[]>([]);
|
||||||
const [currentPage, setCurrentPage] = React.useState<number>(1);
|
const [currentPage, setCurrentPage] = React.useState<number>(1);
|
||||||
//
|
//
|
||||||
const [showError, setShowError] = React.useState<boolean>(false);
|
const [recordCount, setRecordCount] = React.useState<number>(0);
|
||||||
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
|
||||||
|
|
||||||
//
|
|
||||||
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
|
||||||
const [lessonCategoriesData, setLessonCategoriesData] = React.useState<LessonCategory[]>([]);
|
|
||||||
|
|
||||||
const sortedLessonCategories = applySort(lessonCategoriesData, sortDir);
|
const sortedLessonCategories = applySort(lessonCategoriesData, sortDir);
|
||||||
const filteredLessonCategories = applyFilters(sortedLessonCategories, { email, phone, status: spStatus });
|
const filteredLessonCategories = applyFilters(sortedLessonCategories, { email, phone, status: status });
|
||||||
|
|
||||||
|
//
|
||||||
const reloadRows = () => {
|
const reloadRows = () => {
|
||||||
setShowLoading(true);
|
setShowLoading(true);
|
||||||
|
|
||||||
@@ -116,7 +104,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('dashboard.lessonCategorys.list.title')}</Typography>
|
<Typography variant="h4">{t('list.title')}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
@@ -128,13 +116,17 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
startIcon={<PlusIcon />}
|
startIcon={<PlusIcon />}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
>
|
>
|
||||||
{t('dashboard.lessonCategories.add')}
|
{t('list.add')}
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
<LessonCategoriesSelectionProvider lessonCategories={filteredLessonCategories}>
|
<LessonCategoriesSelectionProvider lessonCategories={f}>
|
||||||
<Card>
|
<Card>
|
||||||
<LessonCategoriesFilters filters={{ email, phone, status: spStatus }} sortDir={sortDir} />
|
<LessonCategoriesFilters
|
||||||
|
filters={{ email, phone, status, name, visible, type }}
|
||||||
|
fullData={lessonCategoriesData}
|
||||||
|
sortDir={sortDir}
|
||||||
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Box sx={{ overflowX: 'auto' }}>
|
<Box sx={{ overflowX: 'auto' }}>
|
||||||
<LessonCategoriesTable reloadRows={reloadRows} rows={filteredLessonCategories} />
|
<LessonCategoriesTable reloadRows={reloadRows} rows={filteredLessonCategories} />
|
||||||
@@ -183,3 +175,16 @@ function applyFilters(row: LessonCategory[], { email, phone, status }: Filters):
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
searchParams: {
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
sortDir?: 'asc' | 'desc';
|
||||||
|
status?: string;
|
||||||
|
name?: string;
|
||||||
|
visible?: string;
|
||||||
|
type?: string;
|
||||||
|
//
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@@ -37,14 +37,15 @@ import { PropertyItem } from '@/components/core/property-item';
|
|||||||
import { PropertyList } from '@/components/core/property-list';
|
import { PropertyList } from '@/components/core/property-list';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
import ErrorDisplay from '@/components/dashboard/error';
|
import ErrorDisplay from '@/components/dashboard/error';
|
||||||
import { defaultLessonType } from '@/components/dashboard/lesson_type/_constants';
|
import { defaultLessonType, LessonTypeDefaultValue } from '@/components/dashboard/lesson_type/_constants';
|
||||||
// import { getLessonTypeById } from '@/components/dashboard/lesson_type/http-actions';
|
// import { getLessonTypeById } from '@/components/dashboard/lesson_type/http-actions';
|
||||||
import { LessonTypeDefaultValue, type LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
// import { LessonTypeDefaultValue, type LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
||||||
// import { defaultLessonType } from '@/components/dashboard/lesson_type/interfaces';
|
// import { defaultLessonType } from '@/components/dashboard/lesson_type/interfaces';
|
||||||
import { Notifications } from '@/components/dashboard/lesson_type/notifications';
|
import { Notifications } from '@/components/dashboard/lesson_type/notifications';
|
||||||
import { Payments } from '@/components/dashboard/lesson_type/payments';
|
import { Payments } from '@/components/dashboard/lesson_type/payments';
|
||||||
import type { Address } from '@/components/dashboard/lesson_type/shipping-address';
|
import type { Address } from '@/components/dashboard/lesson_type/shipping-address';
|
||||||
import { ShippingAddress } from '@/components/dashboard/lesson_type/shipping-address';
|
import { ShippingAddress } from '@/components/dashboard/lesson_type/shipping-address';
|
||||||
|
import { type LessonType } from '@/components/dashboard/lesson_type/types';
|
||||||
import FormLoading from '@/components/loading';
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
export default function Page(): React.JSX.Element {
|
export default function Page(): React.JSX.Element {
|
||||||
@@ -58,7 +59,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
//
|
//
|
||||||
const [showLessonType, setShowLessonType] = React.useState<LessonType>(LessonTypeDefaultValue);
|
const [showLessonType, setShowLessonType] = React.useState<LessonType>(LessonTypeDefaultValue);
|
||||||
|
|
||||||
function handleEditClick() {
|
function handleEditClick(): void {
|
||||||
router.push(paths.dashboard.lesson_types.edit(showLessonType.id));
|
router.push(paths.dashboard.lesson_types.edit(showLessonType.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
import { LessonType } from '@/components/dashboard/lesson_type/types';
|
||||||
|
|
||||||
|
// import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
||||||
|
|
||||||
// import { helloworld } from '@/components/dashboard/lesson_type/helloworld';
|
// import { helloworld } from '@/components/dashboard/lesson_type/helloworld';
|
||||||
// export const metadata = { title: `List | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
|
// export const metadata = { title: `List | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
import { LessonType } from '@/components/dashboard/lesson_type/types';
|
||||||
|
|
||||||
|
// import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
||||||
|
|
||||||
// import { helloworld } from '@/components/dashboard/lesson_type/helloworld';
|
// import { helloworld } from '@/components/dashboard/lesson_type/helloworld';
|
||||||
// export const metadata = { title: `List | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
|
// export const metadata = { title: `List | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
|
||||||
|
@@ -19,28 +19,31 @@ import { pb } from '@/lib/pb';
|
|||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
import ErrorDisplay from '@/components/dashboard/error';
|
import ErrorDisplay from '@/components/dashboard/error';
|
||||||
import { defaultLessonType } from '@/components/dashboard/lesson_type/_constants';
|
import { defaultLessonType } from '@/components/dashboard/lesson_type/_constants';
|
||||||
import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
// import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
||||||
// import { defaultLessonType, emptyLessonType, safeAssignment } from '@/components/dashboard/lesson_type/interfaces';
|
// import { defaultLessonType, emptyLessonType, safeAssignment } from '@/components/dashboard/lesson_type/interfaces';
|
||||||
import { LessonTypesFilters } from '@/components/dashboard/lesson_type/lesson-types-filters';
|
import { LessonTypesFilters } from '@/components/dashboard/lesson_type/lesson-types-filters';
|
||||||
import type { Filters } 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 { LessonTypesPagination } from '@/components/dashboard/lesson_type/lesson-types-pagination';
|
||||||
import { LessonTypesSelectionProvider } from '@/components/dashboard/lesson_type/lesson-types-selection-context';
|
import { LessonTypesSelectionProvider } from '@/components/dashboard/lesson_type/lesson-types-selection-context';
|
||||||
import { LessonTypesTable } from '@/components/dashboard/lesson_type/lesson-types-table';
|
import { LessonTypesTable } from '@/components/dashboard/lesson_type/lesson-types-table';
|
||||||
|
import type { LessonType } from '@/components/dashboard/lesson_type/types';
|
||||||
import FormLoading from '@/components/loading';
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation(['lesson_type']);
|
||||||
const { email, phone, sortDir, status, name, visible, type } = searchParams;
|
const { email, phone, sortDir, status, name, visible, type } = searchParams;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [lessonTypesData, setLessonTypesData] = React.useState<LessonType[]>([]);
|
const [lessonTypesData, setLessonTypesData] = React.useState<LessonType[]>([]);
|
||||||
|
//
|
||||||
|
|
||||||
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
||||||
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||||
const [showError, setShowError] = React.useState<boolean>(false);
|
const [showError, setShowError] = React.useState<boolean>(false);
|
||||||
const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
|
const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
|
||||||
|
//
|
||||||
const [f, setF] = React.useState<LessonType[]>([]);
|
const [f, setF] = React.useState<LessonType[]>([]);
|
||||||
const [currentPage, setCurrentPage] = React.useState<number>(0);
|
const [currentPage, setCurrentPage] = React.useState<number>(0);
|
||||||
|
//
|
||||||
const [recordCount, setRecordCount] = React.useState<number>(0);
|
const [recordCount, setRecordCount] = React.useState<number>(0);
|
||||||
|
|
||||||
const [listOption, setListOption] = React.useState({});
|
const [listOption, setListOption] = React.useState({});
|
||||||
@@ -117,7 +120,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 Types')}</Typography>
|
<Typography variant="h4">{t('list.title')}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
|
@@ -26,8 +26,8 @@ import { Events } from '@/components/dashboard/overview/events';
|
|||||||
import { HelperWidget } from '@/components/dashboard/overview/helper-widget';
|
import { HelperWidget } from '@/components/dashboard/overview/helper-widget';
|
||||||
import { Subscriptions } from '@/components/dashboard/overview/subscriptions';
|
import { Subscriptions } from '@/components/dashboard/overview/subscriptions';
|
||||||
import { Summary } from '@/components/dashboard/overview/summary';
|
import { Summary } from '@/components/dashboard/overview/summary';
|
||||||
|
import ActiveUserCount from '@/components/dashboard/overview/summary/ActiveUserCount';
|
||||||
|
|
||||||
import ActiveUserCount from '../../components/dashboard/overview/summary/ActiveUserCount';
|
|
||||||
import { events as SampleEvents } from './SampleEvents';
|
import { events as SampleEvents } from './SampleEvents';
|
||||||
import { messages as SampleMessages } from './SampleMessages';
|
import { messages as SampleMessages } from './SampleMessages';
|
||||||
import { SamplesubScriptions } from './SamplesubScriptions';
|
import { SamplesubScriptions } from './SamplesubScriptions';
|
||||||
|
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { COL_LESSON_CATEGORIES } from '@/constants';
|
||||||
|
import GetAllCount from '@/db/LessonCategories/GetAllCount';
|
||||||
|
import GetHiddenCount from '@/db/LessonCategories/GetHiddenCount';
|
||||||
|
import GetVisibleCount from '@/db/LessonCategories/GetVisibleCount';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
@@ -13,25 +17,23 @@ import Stack from '@mui/material/Stack';
|
|||||||
import Tab from '@mui/material/Tab';
|
import Tab from '@mui/material/Tab';
|
||||||
import Tabs from '@mui/material/Tabs';
|
import Tabs from '@mui/material/Tabs';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
|
import { pb } from '@/lib/pb';
|
||||||
import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button';
|
import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button';
|
||||||
import { Option } from '@/components/core/option';
|
import { Option } from '@/components/core/option';
|
||||||
|
|
||||||
import { useLessonCategoriesSelection } from './lesson-categories-selection-context';
|
import { useLessonCategoriesSelection } from './lesson-categories-selection-context';
|
||||||
|
import { LessonCategory } from './types';
|
||||||
// The tabs should be generated using API data.
|
|
||||||
const tabs = [
|
|
||||||
{ label: 'All', value: '', count: 5 },
|
|
||||||
{ label: 'Active', value: 'active', count: 3 },
|
|
||||||
{ label: 'Pending', value: 'pending', count: 1 },
|
|
||||||
{ label: 'Blocked', value: 'blocked', count: 1 },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export interface Filters {
|
export interface Filters {
|
||||||
email?: string;
|
email?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
|
name?: string;
|
||||||
|
visible?: string;
|
||||||
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SortDir = 'asc' | 'desc';
|
export type SortDir = 'asc' | 'desc';
|
||||||
@@ -39,18 +41,47 @@ export type SortDir = 'asc' | 'desc';
|
|||||||
export interface LessonCategoriesFiltersProps {
|
export interface LessonCategoriesFiltersProps {
|
||||||
filters?: Filters;
|
filters?: Filters;
|
||||||
sortDir?: SortDir;
|
sortDir?: SortDir;
|
||||||
|
fullData: LessonCategory[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LessonCategoriesFilters({
|
export function LessonCategoriesFilters({
|
||||||
filters = {},
|
filters = {},
|
||||||
sortDir = 'desc',
|
sortDir = 'desc',
|
||||||
|
fullData,
|
||||||
}: LessonCategoriesFiltersProps): React.JSX.Element {
|
}: LessonCategoriesFiltersProps): React.JSX.Element {
|
||||||
const { email, phone, status } = filters;
|
const { t } = useTranslation();
|
||||||
|
const { email, phone, status, name, visible, type } = filters;
|
||||||
|
|
||||||
|
const [totalCount, setTotalCount] = React.useState<number>(0);
|
||||||
|
const [visibleCount, setVisibleCount] = React.useState<number>(0);
|
||||||
|
const [hiddenCount, setHiddenCount] = React.useState<number>(0);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const selection = useLessonCategoriesSelection();
|
const selection = useLessonCategoriesSelection();
|
||||||
|
|
||||||
|
function getVisible(): number {
|
||||||
|
return fullData.reduce((count, item: LessonCategory) => {
|
||||||
|
return item.visible === 'visible' ? count + 1 : count;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHidden(): number {
|
||||||
|
return fullData.reduce((count, item: LessonCategory) => {
|
||||||
|
return item.visible === 'hidden' ? count + 1 : count;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The tabs should be generated using API data.
|
||||||
|
const tabs = [
|
||||||
|
{ label: t('All'), value: '', count: totalCount },
|
||||||
|
// { label: 'Active', value: 'active', count: 3 },
|
||||||
|
// { label: 'Pending', value: 'pending', count: 1 },
|
||||||
|
// { label: 'Blocked', value: 'blocked', count: 1 },
|
||||||
|
{ label: t('visible'), value: 'visible', count: visibleCount },
|
||||||
|
{ label: t('hidden'), value: 'hidden', count: hiddenCount },
|
||||||
|
] as const;
|
||||||
|
|
||||||
const updateSearchParams = React.useCallback(
|
const updateSearchParams = React.useCallback(
|
||||||
(newFilters: Filters, newSortDir: SortDir): void => {
|
(newFilters: Filters, newSortDir: SortDir): void => {
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
@@ -71,6 +102,18 @@ export function LessonCategoriesFilters({
|
|||||||
searchParams.set('phone', newFilters.phone);
|
searchParams.set('phone', newFilters.phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newFilters.name) {
|
||||||
|
searchParams.set('name', newFilters.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newFilters.type) {
|
||||||
|
searchParams.set('type', newFilters.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newFilters.visible) {
|
||||||
|
searchParams.set('visible', newFilters.visible);
|
||||||
|
}
|
||||||
|
|
||||||
router.push(`${paths.dashboard.lesson_categories.list}?${searchParams.toString()}`);
|
router.push(`${paths.dashboard.lesson_categories.list}?${searchParams.toString()}`);
|
||||||
},
|
},
|
||||||
[router]
|
[router]
|
||||||
@@ -87,6 +130,27 @@ export function LessonCategoriesFilters({
|
|||||||
[updateSearchParams, filters, sortDir]
|
[updateSearchParams, filters, sortDir]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleVisibleChange = React.useCallback(
|
||||||
|
(_: React.SyntheticEvent, value: string) => {
|
||||||
|
updateSearchParams({ ...filters, visible: value }, sortDir);
|
||||||
|
},
|
||||||
|
[updateSearchParams, filters, sortDir]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleNameChange = React.useCallback(
|
||||||
|
(value?: string) => {
|
||||||
|
updateSearchParams({ ...filters, name: value }, sortDir);
|
||||||
|
},
|
||||||
|
[updateSearchParams, filters, sortDir]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTypeChange = React.useCallback(
|
||||||
|
(value?: string) => {
|
||||||
|
updateSearchParams({ ...filters, type: value }, sortDir);
|
||||||
|
},
|
||||||
|
[updateSearchParams, filters, sortDir]
|
||||||
|
);
|
||||||
|
|
||||||
const handleEmailChange = React.useCallback(
|
const handleEmailChange = React.useCallback(
|
||||||
(value?: string) => {
|
(value?: string) => {
|
||||||
updateSearchParams({ ...filters, email: value }, sortDir);
|
updateSearchParams({ ...filters, email: value }, sortDir);
|
||||||
@@ -108,11 +172,29 @@ export function LessonCategoriesFilters({
|
|||||||
[updateSearchParams, filters]
|
[updateSearchParams, filters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasFilters = status || email || phone;
|
React.useEffect(() => {
|
||||||
|
const fetchCount = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const tc = await GetAllCount();
|
||||||
|
setTotalCount(tc);
|
||||||
|
|
||||||
|
const vc = await GetVisibleCount();
|
||||||
|
setVisibleCount(vc);
|
||||||
|
|
||||||
|
const hc = await GetHiddenCount();
|
||||||
|
setHiddenCount(hc);
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void fetchCount();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const hasFilters = status || email || phone || visible || name || type;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Tabs onChange={handleStatusChange} sx={{ px: 3 }} value={status ?? ''} variant="scrollable">
|
<Tabs onChange={handleVisibleChange} sx={{ px: 3 }} value={visible ?? ''} variant="scrollable">
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<Tab
|
<Tab
|
||||||
icon={<Chip label={tab.count} size="small" variant="soft" />}
|
icon={<Chip label={tab.count} size="small" variant="soft" />}
|
||||||
@@ -129,50 +211,124 @@ export function LessonCategoriesFilters({
|
|||||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flexWrap: 'wrap', px: 3, py: 2 }}>
|
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flexWrap: 'wrap', px: 3, py: 2 }}>
|
||||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto', flexWrap: 'wrap' }}>
|
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto', flexWrap: 'wrap' }}>
|
||||||
<FilterButton
|
<FilterButton
|
||||||
displayValue={email}
|
displayValue={name}
|
||||||
label="Email"
|
label={t('Name')}
|
||||||
onFilterApply={(value) => {
|
onFilterApply={(value) => {
|
||||||
handleEmailChange(value as string);
|
handleNameChange(value as string);
|
||||||
}}
|
}}
|
||||||
onFilterDelete={() => {
|
onFilterDelete={() => {
|
||||||
handleEmailChange();
|
handleNameChange();
|
||||||
}}
|
}}
|
||||||
popover={<EmailFilterPopover />}
|
popover={<NameFilterPopover />}
|
||||||
value={email}
|
value={name}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FilterButton
|
<FilterButton
|
||||||
displayValue={phone}
|
displayValue={type}
|
||||||
label="Phone number"
|
label={t('Type')}
|
||||||
onFilterApply={(value) => {
|
onFilterApply={(value) => {
|
||||||
handlePhoneChange(value as string);
|
handleTypeChange(value as string);
|
||||||
}}
|
}}
|
||||||
onFilterDelete={() => {
|
onFilterDelete={() => {
|
||||||
handlePhoneChange();
|
handleTypeChange();
|
||||||
}}
|
}}
|
||||||
popover={<PhoneFilterPopover />}
|
popover={<TypeFilterPopover />}
|
||||||
value={phone}
|
value={type}
|
||||||
/>
|
/>
|
||||||
{hasFilters ? <Button onClick={handleClearFilters}>Clear filters</Button> : null}
|
|
||||||
|
{hasFilters ? <Button onClick={handleClearFilters}>{t('Clear filters')}</Button> : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
{selection.selectedAny ? (
|
{selection.selectedAny ? (
|
||||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
|
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
|
||||||
<Typography color="text.secondary" variant="body2">
|
<Typography color="text.secondary" variant="body2">
|
||||||
{selection.selected.size} selected
|
{selection.selected.size} {t('selected')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button color="error" variant="contained">
|
<Button color="error" variant="contained">
|
||||||
Delete
|
{t('Delete')}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
) : null}
|
) : null}
|
||||||
<Select name="sort" onChange={handleSortChange} sx={{ maxWidth: '100%', width: '120px' }} value={sortDir}>
|
<Select name="sort" onChange={handleSortChange} sx={{ maxWidth: '100%', width: '120px' }} value={sortDir}>
|
||||||
<Option value="desc">Newest</Option>
|
<Option value="desc">{t('Newest')}</Option>
|
||||||
<Option value="asc">Oldest</Option>
|
<Option value="asc">{t('Oldest')}</Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TypeFilterPopover(): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { anchorEl, onApply, onClose, open, value: initialValue } = useFilterContext();
|
||||||
|
const [value, setValue] = React.useState<string>('');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setValue((initialValue as string | undefined) ?? '');
|
||||||
|
}, [initialValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FilterPopover anchorEl={anchorEl} onClose={onClose} open={open} title={t('Filter by type')}>
|
||||||
|
<FormControl>
|
||||||
|
<OutlinedInput
|
||||||
|
onChange={(event) => {
|
||||||
|
setValue(event.target.value);
|
||||||
|
}}
|
||||||
|
onKeyUp={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
onApply(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onApply(value);
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{t('Apply')}
|
||||||
|
</Button>
|
||||||
|
</FilterPopover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NameFilterPopover(): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { anchorEl, onApply, onClose, open, value: initialValue } = useFilterContext();
|
||||||
|
const [value, setValue] = React.useState<string>('');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setValue((initialValue as string | undefined) ?? '');
|
||||||
|
}, [initialValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FilterPopover anchorEl={anchorEl} onClose={onClose} open={open} title={t('Filter by name')}>
|
||||||
|
<FormControl>
|
||||||
|
<OutlinedInput
|
||||||
|
onChange={(event) => {
|
||||||
|
setValue(event.target.value);
|
||||||
|
}}
|
||||||
|
onKeyUp={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
onApply(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onApply(value);
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{t('Apply')}
|
||||||
|
</Button>
|
||||||
|
</FilterPopover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function EmailFilterPopover(): React.JSX.Element {
|
function EmailFilterPopover(): React.JSX.Element {
|
||||||
const { anchorEl, onApply, onClose, open, value: initialValue } = useFilterContext();
|
const { anchorEl, onApply, onClose, open, value: initialValue } = useFilterContext();
|
||||||
const [value, setValue] = React.useState<string>('');
|
const [value, setValue] = React.useState<string>('');
|
||||||
|
@@ -145,7 +145,7 @@ export interface LessonCategoriesTableProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LessonCategoriesTable({ rows, reloadRows }: LessonCategoriesTableProps): React.JSX.Element {
|
export function LessonCategoriesTable({ rows, reloadRows }: LessonCategoriesTableProps): React.JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation(['lesson_category']);
|
||||||
const { deselectAll, deselectOne, selectAll, selectOne, selected } = useLessonCategoriesSelection();
|
const { deselectAll, deselectOne, selectAll, selectOne, selected } = useLessonCategoriesSelection();
|
||||||
|
|
||||||
const [idToDelete, setIdToDelete] = React.useState('');
|
const [idToDelete, setIdToDelete] = React.useState('');
|
||||||
@@ -177,7 +177,7 @@ export function LessonCategoriesTable({ rows, reloadRows }: LessonCategoriesTabl
|
|||||||
<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 */}
|
{/* TODO: use hyphen here */}
|
||||||
{t('No lesson categories found')}
|
{t('no-lesson-categories-found')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
|
@@ -2,58 +2,3 @@
|
|||||||
|
|
||||||
// please use src/components/dashboard/lesson_type/interfaces.ts for defining new interfaces
|
// please use src/components/dashboard/lesson_type/interfaces.ts for defining new interfaces
|
||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
|
||||||
export interface LessonType {
|
|
||||||
id: string;
|
|
||||||
isEmpty?: boolean;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
pos: number;
|
|
||||||
visible: 'visible' | 'hidden';
|
|
||||||
createdAt: Date;
|
|
||||||
//
|
|
||||||
// original
|
|
||||||
// id: string;
|
|
||||||
// name: string;
|
|
||||||
//
|
|
||||||
avatar?: string;
|
|
||||||
email: string;
|
|
||||||
phone?: string;
|
|
||||||
quota: number;
|
|
||||||
status: 'pending' | 'active' | 'blocked';
|
|
||||||
// createdAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LessonTypeDefaultValue: LessonType = {
|
|
||||||
id: 'string',
|
|
||||||
name: 'string',
|
|
||||||
type: 'string',
|
|
||||||
pos: 1,
|
|
||||||
visible: 'visible',
|
|
||||||
createdAt: dayjs().toDate(),
|
|
||||||
//
|
|
||||||
// original
|
|
||||||
// id: 'string',
|
|
||||||
// name: 'string',
|
|
||||||
//
|
|
||||||
avatar: 'string',
|
|
||||||
email: 'string',
|
|
||||||
phone: 'string',
|
|
||||||
quota: 1,
|
|
||||||
status: 'pending',
|
|
||||||
// createdAt: Date;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface DBLessonType {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
pos: number;
|
|
||||||
visible: 'visible' | 'hidden';
|
|
||||||
createdAt: Date;
|
|
||||||
created: 'string';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Helloworld {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
@@ -3,8 +3,8 @@ import type { RecordModel } from 'pocketbase';
|
|||||||
|
|
||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
|
||||||
import type { LessonType } from './ILessonType';
|
// import type { LessonType } from './ILessonType';
|
||||||
import type { CreateForm } from './types';
|
import type { CreateForm, LessonType } from './types';
|
||||||
|
|
||||||
export const LessonTypeCreateFormDefault: CreateForm = {
|
export const LessonTypeCreateFormDefault: CreateForm = {
|
||||||
name: '',
|
name: '',
|
||||||
@@ -55,3 +55,23 @@ export function safeAssignment(inTemp: LessonType | RecordModel): LessonType {
|
|||||||
};
|
};
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const LessonTypeDefaultValue: LessonType = {
|
||||||
|
id: 'string',
|
||||||
|
name: 'string',
|
||||||
|
type: 'string',
|
||||||
|
pos: 1,
|
||||||
|
visible: 'visible',
|
||||||
|
createdAt: dayjs().toDate(),
|
||||||
|
//
|
||||||
|
// original
|
||||||
|
// id: 'string',
|
||||||
|
// name: 'string',
|
||||||
|
//
|
||||||
|
avatar: 'string',
|
||||||
|
email: 'string',
|
||||||
|
phone: 'string',
|
||||||
|
quota: 1,
|
||||||
|
status: 'pending',
|
||||||
|
// createdAt: Date;
|
||||||
|
};
|
||||||
|
@@ -40,7 +40,7 @@ import FormLoading from '@/components/loading';
|
|||||||
import { defaultLessonType } from './_constants';
|
import { defaultLessonType } from './_constants';
|
||||||
// import { getLessonTypeById, updateLessonType } from './http-actions';
|
// import { getLessonTypeById, updateLessonType } from './http-actions';
|
||||||
// TODO: this may be wrong
|
// TODO: this may be wrong
|
||||||
import type { LessonType } from './ILessonType';
|
// import type { LessonType } from './ILessonType';
|
||||||
import { LessonTypeEditFormProps } from './types';
|
import { LessonTypeEditFormProps } from './types';
|
||||||
|
|
||||||
// import { defaultLessonType, type LessonTypeEditFormProps } from './interfaces';
|
// import { defaultLessonType, type LessonTypeEditFormProps } from './interfaces';
|
||||||
|
@@ -3,6 +3,9 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { COL_LESSON_TYPES } from '@/constants';
|
import { COL_LESSON_TYPES } from '@/constants';
|
||||||
|
import GetAllCount from '@/db/LessonTypes/GetAllCount';
|
||||||
|
import GetHiddenCount from '@/db/LessonTypes/GetHiddenCount';
|
||||||
|
import GetVisibleCount from '@/db/LessonTypes/GetVisibleCount';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
@@ -21,8 +24,9 @@ import { pb } from '@/lib/pb';
|
|||||||
import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button';
|
import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button';
|
||||||
import { Option } from '@/components/core/option';
|
import { Option } from '@/components/core/option';
|
||||||
|
|
||||||
import type { LessonType } from './ILessonType';
|
// import type { LessonType } from './ILessonType';
|
||||||
import { useLessonTypesSelection } from './lesson-types-selection-context';
|
import { useLessonTypesSelection } from './lesson-types-selection-context';
|
||||||
|
import { LessonType } from './types';
|
||||||
|
|
||||||
export interface Filters {
|
export interface Filters {
|
||||||
email?: string;
|
email?: string;
|
||||||
@@ -172,17 +176,13 @@ export function LessonTypesFilters({
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const fetchCount = async (): Promise<void> => {
|
const fetchCount = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { totalItems: tc } = await pb.collection(COL_LESSON_TYPES).getList(1, 9999);
|
const tc = await GetAllCount();
|
||||||
setTotalCount(tc);
|
setTotalCount(tc);
|
||||||
|
|
||||||
const { totalItems: vc } = await pb
|
const vc = await GetVisibleCount();
|
||||||
.collection(COL_LESSON_TYPES)
|
|
||||||
.getList(1, 9999, { filter: 'visible = "visible"' });
|
|
||||||
setVisibleCount(vc);
|
setVisibleCount(vc);
|
||||||
|
|
||||||
const { totalItems: hc } = await pb
|
const hc = await GetHiddenCount();
|
||||||
.collection(COL_LESSON_TYPES)
|
|
||||||
.getList(1, 9999, { filter: 'visible = "hidden"' });
|
|
||||||
setHiddenCount(hc);
|
setHiddenCount(hc);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
@@ -237,36 +237,6 @@ export function LessonTypesFilters({
|
|||||||
value={type}
|
value={type}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/*
|
|
||||||
<FilterButton
|
|
||||||
displayValue={email}
|
|
||||||
label="Email"
|
|
||||||
onFilterApply={(value) => {
|
|
||||||
handleEmailChange(value as string);
|
|
||||||
}}
|
|
||||||
onFilterDelete={() => {
|
|
||||||
handleEmailChange();
|
|
||||||
}}
|
|
||||||
popover={<EmailFilterPopover />}
|
|
||||||
value={email}
|
|
||||||
/>
|
|
||||||
*/}
|
|
||||||
|
|
||||||
{/*
|
|
||||||
<FilterButton
|
|
||||||
displayValue={phone}
|
|
||||||
label="Phone number"
|
|
||||||
onFilterApply={(value) => {
|
|
||||||
handlePhoneChange(value as string);
|
|
||||||
}}
|
|
||||||
onFilterDelete={() => {
|
|
||||||
handlePhoneChange();
|
|
||||||
}}
|
|
||||||
popover={<PhoneFilterPopover />}
|
|
||||||
value={phone}
|
|
||||||
/>
|
|
||||||
*/}
|
|
||||||
|
|
||||||
{hasFilters ? <Button onClick={handleClearFilters}>{t('Clear filters')}</Button> : null}
|
{hasFilters ? <Button onClick={handleClearFilters}>{t('Clear filters')}</Button> : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
{selection.selectedAny ? (
|
{selection.selectedAny ? (
|
||||||
|
@@ -5,7 +5,9 @@ import * as React from 'react';
|
|||||||
import { useSelection } from '@/hooks/use-selection';
|
import { useSelection } from '@/hooks/use-selection';
|
||||||
import type { Selection } from '@/hooks/use-selection';
|
import type { Selection } from '@/hooks/use-selection';
|
||||||
|
|
||||||
import { LessonType } from './ILessonType';
|
import { LessonType } from './types';
|
||||||
|
|
||||||
|
// import { LessonType } from './ILessonType';
|
||||||
|
|
||||||
function noop(): void {
|
function noop(): void {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@@ -13,6 +13,7 @@ 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 { TrashSimple as TrashSimpleIcon } from '@phosphor-icons/react/dist/ssr/TrashSimple';
|
||||||
@@ -25,8 +26,9 @@ 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 ConfirmDeleteModal from './confirm-delete-modal';
|
||||||
import type { LessonType } from './ILessonType';
|
// import type { LessonType } from './ILessonType';
|
||||||
import { useLessonTypesSelection } from './lesson-types-selection-context';
|
import { useLessonTypesSelection } from './lesson-types-selection-context';
|
||||||
|
import { LessonType } from './types';
|
||||||
|
|
||||||
function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonType>[] {
|
function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonType>[] {
|
||||||
return [
|
return [
|
||||||
@@ -132,7 +134,6 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonT
|
|||||||
name: 'Created at',
|
name: 'Created at',
|
||||||
width: '100px',
|
width: '100px',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
formatter: (row): React.JSX.Element => (
|
formatter: (row): React.JSX.Element => (
|
||||||
<Stack direction="row" spacing={1}>
|
<Stack direction="row" spacing={1}>
|
||||||
@@ -169,7 +170,7 @@ export interface LessonTypesTableProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LessonTypesTable({ rows, reloadRows }: LessonTypesTableProps): React.JSX.Element {
|
export function LessonTypesTable({ rows, reloadRows }: LessonTypesTableProps): React.JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation(['lesson_type']);
|
||||||
const { deselectAll, deselectOne, selectAll, selectOne, selected } = useLessonTypesSelection();
|
const { deselectAll, deselectOne, selectAll, selectOne, selected } = useLessonTypesSelection();
|
||||||
|
|
||||||
const [idToDelete, setIdToDelete] = React.useState('');
|
const [idToDelete, setIdToDelete] = React.useState('');
|
||||||
@@ -201,7 +202,7 @@ export function LessonTypesTable({ rows, reloadRows }: LessonTypesTableProps): R
|
|||||||
<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 */}
|
{/* TODO: use hyphen here */}
|
||||||
{t('No lesson types found')}
|
{t('no-lesson-types-found')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
|
@@ -4,7 +4,8 @@ import type { RecordModel } from 'pocketbase';
|
|||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
|
||||||
import { defaultLessonType } from './_constants';
|
import { defaultLessonType } from './_constants';
|
||||||
import type { LessonType } from './ILessonType';
|
|
||||||
|
// import type { LessonType } from './ILessonType';
|
||||||
|
|
||||||
export interface LessonTypeEditFormProps {
|
export interface LessonTypeEditFormProps {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -42,3 +43,38 @@ export function safeAssignment(inTemp: LessonType | RecordModel): LessonType {
|
|||||||
};
|
};
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LessonType {
|
||||||
|
id: string;
|
||||||
|
isEmpty?: boolean;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
pos: number;
|
||||||
|
visible: 'visible' | 'hidden';
|
||||||
|
createdAt: Date;
|
||||||
|
//
|
||||||
|
// original
|
||||||
|
// id: string;
|
||||||
|
// name: string;
|
||||||
|
//
|
||||||
|
avatar?: string;
|
||||||
|
email: string;
|
||||||
|
phone?: string;
|
||||||
|
quota: number;
|
||||||
|
status: 'pending' | 'active' | 'blocked';
|
||||||
|
// createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DBLessonType {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
pos: number;
|
||||||
|
visible: 'visible' | 'hidden';
|
||||||
|
createdAt: Date;
|
||||||
|
created: 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Helloworld {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
@@ -12,6 +12,10 @@ please read, remember and link up the ideas, i will tell you the task afterwards
|
|||||||
|
|
||||||
working directory: `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db`
|
working directory: `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db`
|
||||||
|
|
||||||
|
clone `GetVisibleCount.tsx` and `GetHiddenCount.tsx` from `LessonTypes` to `LessonCategories` and update it
|
||||||
|
|
||||||
|
please draft `GetHiddenCount.tsx` for COL_LESSON_TYPES and `status = hidden`
|
||||||
|
|
||||||
pleaes clone the `tsx` files from `LessonTypes` and `LessonCategories` to `UserMetas` and update the content
|
pleaes clone the `tsx` files from `LessonTypes` and `LessonCategories` to `UserMetas` and update the content
|
||||||
|
|
||||||
when you draft coding, review file and append with `.tsx.draft`
|
when you draft coding, review file and append with `.tsx.draft`
|
||||||
|
0
002_source/cms/src/db/GetHiddenCount.tsx
Normal file
0
002_source/cms/src/db/GetHiddenCount.tsx
Normal file
@@ -4,8 +4,6 @@ import { COL_LESSON_CATEGORIES } from '@/constants';
|
|||||||
import { pb } from '@/lib/pb';
|
import { pb } from '@/lib/pb';
|
||||||
|
|
||||||
export default async function GetAllCount(): Promise<number> {
|
export default async function GetAllCount(): Promise<number> {
|
||||||
const { totalItems: count } = await pb
|
const { totalItems: count } = await pb.collection(COL_LESSON_CATEGORIES).getList(1, 9999, {});
|
||||||
.collection(COL_LESSON_CATEGORIES)
|
|
||||||
.getList(1, 9999, { filter: 'visible = "visible"' });
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
14
002_source/cms/src/db/LessonCategories/GetHiddenCount.tsx
Normal file
14
002_source/cms/src/db/LessonCategories/GetHiddenCount.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// REQ0006
|
||||||
|
import { COL_LESSON_CATEGORIES } from '@/constants';
|
||||||
|
|
||||||
|
import { pb } from '@/lib/pb';
|
||||||
|
|
||||||
|
export default async function GetHiddenCount(): Promise<number> {
|
||||||
|
try {
|
||||||
|
const result = await pb.collection(COL_LESSON_CATEGORIES).getList(1, 9999, { filter: 'visible = "hidden"' });
|
||||||
|
const { totalItems: count } = result;
|
||||||
|
return count;
|
||||||
|
} catch (error) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
14
002_source/cms/src/db/LessonCategories/GetVisibleCount.tsx
Normal file
14
002_source/cms/src/db/LessonCategories/GetVisibleCount.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// REQ0006
|
||||||
|
import { COL_LESSON_CATEGORIES } from '@/constants';
|
||||||
|
|
||||||
|
import { pb } from '@/lib/pb';
|
||||||
|
|
||||||
|
export default async function GetVisibleCount(): Promise<number> {
|
||||||
|
try {
|
||||||
|
const result = await pb.collection(COL_LESSON_CATEGORIES).getList(1, 9999, { filter: 'visible = "visible"' });
|
||||||
|
const { totalItems: count } = result;
|
||||||
|
return count;
|
||||||
|
} catch (error) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@@ -5,7 +5,7 @@ import { pb } from '@/lib/pb';
|
|||||||
|
|
||||||
export default async function GetAllCount(): Promise<number> {
|
export default async function GetAllCount(): Promise<number> {
|
||||||
try {
|
try {
|
||||||
const result = await pb.collection(COL_LESSON_TYPES).getList(1, 9999, { filter: 'visible = "visible"' });
|
const result = await pb.collection(COL_LESSON_TYPES).getList(1, 9999, {});
|
||||||
const { totalItems: count } = result;
|
const { totalItems: count } = result;
|
||||||
return count;
|
return count;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
14
002_source/cms/src/db/LessonTypes/GetHiddenCount.tsx
Normal file
14
002_source/cms/src/db/LessonTypes/GetHiddenCount.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// REQ0006
|
||||||
|
import { COL_LESSON_TYPES } from '@/constants';
|
||||||
|
|
||||||
|
import { pb } from '@/lib/pb';
|
||||||
|
|
||||||
|
export default async function GetHiddenCount(): Promise<number> {
|
||||||
|
try {
|
||||||
|
const result = await pb.collection(COL_LESSON_TYPES).getList(1, 9999, { filter: 'visible = "hidden"' });
|
||||||
|
const { totalItems: count } = result;
|
||||||
|
return count;
|
||||||
|
} catch (error) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
14
002_source/cms/src/db/LessonTypes/GetVisibleCount.tsx
Normal file
14
002_source/cms/src/db/LessonTypes/GetVisibleCount.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// REQ0006
|
||||||
|
import { COL_LESSON_TYPES } from '@/constants';
|
||||||
|
|
||||||
|
import { pb } from '@/lib/pb';
|
||||||
|
|
||||||
|
export default async function GetVisibleCount(): Promise<number> {
|
||||||
|
try {
|
||||||
|
const result = await pb.collection(COL_LESSON_TYPES).getList(1, 9999, { filter: 'visible = "visible"' });
|
||||||
|
const { totalItems: count } = result;
|
||||||
|
return count;
|
||||||
|
} catch (error) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@@ -93,6 +93,12 @@ export const paths = {
|
|||||||
create: '/dashboard/customers/create',
|
create: '/dashboard/customers/create',
|
||||||
details: (customerId: string) => `/dashboard/customers/${customerId}`,
|
details: (customerId: string) => `/dashboard/customers/${customerId}`,
|
||||||
},
|
},
|
||||||
|
students: {
|
||||||
|
list: '/dashboard/students',
|
||||||
|
create: '/dashboard/students/create',
|
||||||
|
details: (studentId: string) => `/dashboard/students/${studentId}`,
|
||||||
|
edit: (studentId: string) => `/dashboard/students/edit/${studentId}`,
|
||||||
|
},
|
||||||
eCommerce: '/dashboard/e-commerce',
|
eCommerce: '/dashboard/e-commerce',
|
||||||
fileStorage: '/dashboard/file-storage',
|
fileStorage: '/dashboard/file-storage',
|
||||||
invoices: {
|
invoices: {
|
||||||
|
Reference in New Issue
Block a user