This commit is contained in:
louiscklaw
2025-04-16 04:20:44 +08:00
parent 05a3beef9f
commit 7c63b9d0d9
7 changed files with 113 additions and 43 deletions

View File

@@ -58,3 +58,4 @@ NEXT_PUBLIC_MAPBOX_API_KEY=
# Google Tag Manager # Google Tag Manager
NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID= NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID=
NEXT_PUBLIC_POCKETBASE_URL=http://localhost:8090

View File

@@ -38,3 +38,5 @@ NEXT_PUBLIC_MAPBOX_API_KEY=
# Google Tag Manager # Google Tag Manager
NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID= NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID=
NEXT_PUBLIC_POCKETBASE_URL=http://localhost:8090

View File

@@ -0,0 +1,5 @@
import PocketBase, { RecordModel } from 'pocketbase';
export function Helloworld(): string {
return 'helloworld';
}

View File

@@ -9,12 +9,15 @@ import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus'; import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import type { RecordModel } from 'pocketbase';
import PocketBase from 'pocketbase';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { logger } from '@/lib/default-logger'; import { logger } from '@/lib/default-logger';
import { toast } from '@/components/core/toaster'; import { toast } from '@/components/core/toaster';
import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType'; import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
import { 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';
@@ -22,46 +25,55 @@ import { LessonTypesSelectionProvider } from '@/components/dashboard/lesson_type
import { LessonTypesTable } from '@/components/dashboard/lesson_type/lesson-types-table'; import { LessonTypesTable } from '@/components/dashboard/lesson_type/lesson-types-table';
import FormLoading from '@/components/loading'; import FormLoading from '@/components/loading';
import { Helloworld } from './db';
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);
interface PageProps { interface PageProps {
searchParams: { searchParams: {
email?: string; email?: string;
phone?: string; phone?: string;
sortDir?: 'asc' | 'desc'; sortDir?: 'asc' | 'desc';
status?: string; spStatus?: string;
name?: string; spName?: string;
visible?: string; spVisible?: string;
type?: string; spType?: string;
// //
}; };
} }
export default function Page({ searchParams }: PageProps): React.JSX.Element { export default function Page({ searchParams }: PageProps): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
const { email, phone, sortDir, status, name, visible, type } = searchParams; const { email, phone, sortDir, spStatus, spName, spVisible, spType } = searchParams;
const router = useRouter(); const router = useRouter();
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false); const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
const [lessonTypesData, setLessonTypesData] = React.useState<LessonType[]>([]); const [lessonTypesData, setLessonTypesData] = React.useState<LessonType[]>([]);
// const [recordModel, setRecordModel] = React.useState<RecordModel[]>([]);
const sortedLessonTypes = applySort(lessonTypesData, sortDir); const sortedLessonTypes = applySort(lessonTypesData, sortDir);
const filteredLessonTypes = applyFilters(sortedLessonTypes, { const filteredLessonTypes = applyFilters(sortedLessonTypes, {
email, email,
phone, phone,
status, spStatus,
name, spName,
type, spType,
visible, spVisible,
// //
}); });
const reloadRows = () => { const reloadRows = () => {
// listLessonTypes() pb.collection('LessonsTypes')
// .then((lessonTypes: LessonType[]) => { .getFullList()
// setLessonTypesData(lessonTypes); .then((lessonTypes: RecordModel[]) => {
// }) const tempLessonTypes: LessonType[] = lessonTypes.map((lt) => {
// .catch((err) => { return safeAssignment(lt);
// logger.error(err); });
// toast(t('dashboard.lessonTypes.list.error')); setLessonTypesData(tempLessonTypes);
// }); })
.catch((err) => {
logger.error(err);
toast(t('dashboard.lessonTypes.list.error'));
});
}; };
React.useEffect(() => { React.useEffect(() => {
@@ -102,7 +114,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
<LessonTypesSelectionProvider lessonTypes={filteredLessonTypes}> <LessonTypesSelectionProvider lessonTypes={filteredLessonTypes}>
<Card> <Card>
<LessonTypesFilters <LessonTypesFilters
filters={{ email, phone, status, name, visible, type }} filters={{ email, phone, spStatus, spName, spVisible, spType }}
fullData={lessonTypesData} fullData={lessonTypesData}
sortDir={sortDir} sortDir={sortDir}
/> />
@@ -131,7 +143,10 @@ function applySort(row: LessonType[], sortDir: 'asc' | 'desc' | undefined): Less
}); });
} }
function applyFilters(row: LessonType[], { email, phone, status, name, visible }: Filters): LessonType[] { function applyFilters(
row: LessonType[],
{ email, phone, spStatus: status, spName: name, spVisible: visible }: Filters
): LessonType[] {
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())) {

View File

@@ -1,3 +1,9 @@
import type { RecordModel } from 'pocketbase';
import { dayjs } from '@/lib/dayjs';
import type { LessonType } from './ILessonType';
export interface LessonTypeEditFormProps { export interface LessonTypeEditFormProps {
name: string; name: string;
type: string; type: string;
@@ -23,3 +29,41 @@ export const LessonTypeCreateFormDefault: LessonTypeCreateForm = {
pos: 1, pos: 1,
visible: 'visible', visible: 'visible',
}; };
export const defaultLessonType: 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 function safeAssignment(inTemp: LessonType | RecordModel): LessonType {
const { id, name, type, pos, visible, createdAt, email, quota, status } = { ...defaultLessonType, ...inTemp };
const oCreatedAt = dayjs(createdAt).toDate();
const output: LessonType = {
id,
name,
type,
pos,
visible,
createdAt: oCreatedAt,
email,
quota,
status,
};
return output;
}

View File

@@ -19,16 +19,16 @@ import { paths } from '@/paths';
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 { LessonType } from './ILessonType'; import type { LessonType } from './ILessonType';
import { useLessonTypesSelection } from './lesson-types-selection-context'; import { useLessonTypesSelection } from './lesson-types-selection-context';
export interface Filters { export interface Filters {
email?: string; email?: string;
phone?: string; phone?: string;
status?: string; spStatus?: string;
name?: string; spName?: string;
visible?: string; spVisible?: string;
type?: string; spType?: string;
} }
export type SortDir = 'asc' | 'desc'; export type SortDir = 'asc' | 'desc';
@@ -45,7 +45,7 @@ export function LessonTypesFilters({
fullData, fullData,
}: LessonTypesFiltersProps): React.JSX.Element { }: LessonTypesFiltersProps): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
const { email, phone, status, name, visible, type } = filters; const { email, phone, spStatus: status, spName, spVisible, spType } = filters;
const router = useRouter(); const router = useRouter();
@@ -81,8 +81,8 @@ export function LessonTypesFilters({
searchParams.set('sortDir', newSortDir); searchParams.set('sortDir', newSortDir);
} }
if (newFilters.status) { if (newFilters.spStatus) {
searchParams.set('status', newFilters.status); searchParams.set('status', newFilters.spStatus);
} }
if (newFilters.email) { if (newFilters.email) {
@@ -93,16 +93,16 @@ export function LessonTypesFilters({
searchParams.set('phone', newFilters.phone); searchParams.set('phone', newFilters.phone);
} }
if (newFilters.name) { if (newFilters.spName) {
searchParams.set('name', newFilters.name); searchParams.set('name', newFilters.spName);
} }
if (newFilters.type) { if (newFilters.spType) {
searchParams.set('type', newFilters.type); searchParams.set('type', newFilters.spType);
} }
if (newFilters.visible) { if (newFilters.spVisible) {
searchParams.set('visible', newFilters.visible); searchParams.set('visible', newFilters.spVisible);
} }
router.push(`${paths.dashboard.lesson_types.list}?${searchParams.toString()}`); router.push(`${paths.dashboard.lesson_types.list}?${searchParams.toString()}`);
@@ -116,28 +116,28 @@ export function LessonTypesFilters({
const handleStatusChange = React.useCallback( const handleStatusChange = React.useCallback(
(_: React.SyntheticEvent, value: string) => { (_: React.SyntheticEvent, value: string) => {
updateSearchParams({ ...filters, status: value }, sortDir); updateSearchParams({ ...filters, spStatus: value }, sortDir);
}, },
[updateSearchParams, filters, sortDir] [updateSearchParams, filters, sortDir]
); );
const handleVisibleChange = React.useCallback( const handleVisibleChange = React.useCallback(
(_: React.SyntheticEvent, value: string) => { (_: React.SyntheticEvent, value: string) => {
updateSearchParams({ ...filters, visible: value }, sortDir); updateSearchParams({ ...filters, spVisible: value }, sortDir);
}, },
[updateSearchParams, filters, sortDir] [updateSearchParams, filters, sortDir]
); );
const handleNameChange = React.useCallback( const handleNameChange = React.useCallback(
(value?: string) => { (value?: string) => {
updateSearchParams({ ...filters, name: value }, sortDir); updateSearchParams({ ...filters, spName: value }, sortDir);
}, },
[updateSearchParams, filters, sortDir] [updateSearchParams, filters, sortDir]
); );
const handleTypeChange = React.useCallback( const handleTypeChange = React.useCallback(
(value?: string) => { (value?: string) => {
updateSearchParams({ ...filters, type: value }, sortDir); updateSearchParams({ ...filters, spType: value }, sortDir);
}, },
[updateSearchParams, filters, sortDir] [updateSearchParams, filters, sortDir]
); );
@@ -163,11 +163,11 @@ export function LessonTypesFilters({
[updateSearchParams, filters] [updateSearchParams, filters]
); );
const hasFilters = status || email || phone || visible || name || type; const hasFilters = status || email || phone || spVisible || spName || spType;
return ( return (
<div> <div>
<Tabs onChange={handleVisibleChange} sx={{ px: 3 }} value={visible ?? ''} variant="scrollable"> <Tabs onChange={handleVisibleChange} sx={{ px: 3 }} value={spVisible ?? ''} 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" />}
@@ -184,7 +184,7 @@ export function LessonTypesFilters({
<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={name} displayValue={spName}
label={t('Name')} label={t('Name')}
onFilterApply={(value) => { onFilterApply={(value) => {
handleNameChange(value as string); handleNameChange(value as string);
@@ -193,11 +193,11 @@ export function LessonTypesFilters({
handleNameChange(); handleNameChange();
}} }}
popover={<NameFilterPopover />} popover={<NameFilterPopover />}
value={name} value={spName}
/> />
<FilterButton <FilterButton
displayValue={type} displayValue={spType}
label={t('Type')} label={t('Type')}
onFilterApply={(value) => { onFilterApply={(value) => {
handleTypeChange(value as string); handleTypeChange(value as string);
@@ -206,7 +206,7 @@ export function LessonTypesFilters({
handleTypeChange(); handleTypeChange();
}} }}
popover={<TypeFilterPopover />} popover={<TypeFilterPopover />}
value={type} value={spType}
/> />
{/* {/*

View File

@@ -0,0 +1,3 @@
import PocketBase from 'pocketbase';
export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);