This commit is contained in:
louiscklaw
2025-04-20 05:50:05 +08:00
parent ba1c0f6897
commit 5a746b3c3a
41 changed files with 593 additions and 231 deletions

View File

@@ -0,0 +1,47 @@
'use client';
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardHeader from '@mui/material/CardHeader';
import Grid from '@mui/material/Unstable_Grid2';
import { House as HouseIcon } from '@phosphor-icons/react/dist/ssr/House';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { useTranslation } from 'react-i18next';
import type { Address } from '@/types/Address';
import { ShippingAddress } from '@/components/dashboard/lp_categories/shipping-address';
import { SampleAddresses } from '../SampleAddresses';
export default function SampleAddressCard(): React.JSX.Element {
const { t } = useTranslation();
return (
<Card>
<CardHeader
action={
<Button color="secondary" startIcon={<PlusIcon />}>
{t('list.add')}
</Button>
}
avatar={
<Avatar>
<HouseIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title={t('list.shipping-addresses')}
/>
<CardContent>
<Grid container spacing={3}>
{(SampleAddresses satisfies Address[]).map((address) => (
<Grid key={address.id} md={6} xs={12}>
<ShippingAddress address={address} />
</Grid>
))}
</Grid>
</CardContent>
</Card>
);
}

View File

@@ -1,9 +1,9 @@
'use client';
// import { dayjs } from 'dayjs';
import type { Notification } from '@/types/notification';
import { dayjs } from '@/lib/dayjs';
import type { Notification } from './type';
export const SampleNotifications: Notification[] = [
{
id: 'EV-002',

View File

@@ -1,80 +0,0 @@
'use client';
import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import getLessonCategoryById from '@/db/LessonCategories/GetById';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardHeader from '@mui/material/CardHeader';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import LinearProgress from '@mui/material/LinearProgress';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { CreditCard as CreditCardIcon } from '@phosphor-icons/react/dist/ssr/CreditCard';
import { House as HouseIcon } from '@phosphor-icons/react/dist/ssr/House';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';
import type { Address } from '@/types/Address';
import { LpCategory } from '@/types/LpCategory';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import LpCategoryDefaultValue, { defaultLpCategory } from '@/components/dashboard/lp_categories/_constants';
import { Notifications } from '@/components/dashboard/lp_categories/notifications';
import { Payments } from '@/components/dashboard/lp_categories/payments';
import { ShippingAddress } from '@/components/dashboard/lp_categories/shipping-address';
import { SampleAddresses } from './SampleAddresses';
import { SampleNotifications } from './SampleNotifications';
import { SamplePayments } from './SamplePayments';
export default function SampleAddressCard(): React.JSX.Element {
const { t } = useTranslation();
return (
<Card>
<CardHeader
action={
<Button color="secondary" startIcon={<PlusIcon />}>
{t('list.add')}
</Button>
}
avatar={
<Avatar>
<HouseIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title={t('list.shipping-addresses')}
/>
<CardContent>
<Grid container spacing={3}>
{(SampleAddresses satisfies Address[]).map((address) => (
<Grid key={address.id} md={6} xs={12}>
<ShippingAddress address={address} />
</Grid>
))}
</Grid>
</CardContent>
</Card>
);
}

View File

@@ -1,53 +1,20 @@
'use client';
import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import getLessonCategoryById from '@/db/LessonCategories/GetById';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardHeader from '@mui/material/CardHeader';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import LinearProgress from '@mui/material/LinearProgress';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { CreditCard as CreditCardIcon } from '@phosphor-icons/react/dist/ssr/CreditCard';
import { House as HouseIcon } from '@phosphor-icons/react/dist/ssr/House';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';
import type { Address } from '@/types/Address';
import { LpCategory } from '@/types/LpCategory';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import LpCategoryDefaultValue, { defaultLpCategory } from '@/components/dashboard/lp_categories/_constants';
import { Notifications } from '@/components/dashboard/lp_categories/notifications';
import { Payments } from '@/components/dashboard/lp_categories/payments';
import { ShippingAddress } from '@/components/dashboard/lp_categories/shipping-address';
import SampleAddressCard from './SampleAddressCard';
import { SampleAddresses } from './SampleAddresses';
import { SampleNotifications } from './SampleNotifications';
import { SamplePayments } from './SamplePayments';
export default function SamplePaymentCard(): React.JSX.Element {

View File

@@ -45,7 +45,8 @@ import { Notifications } from '@/components/dashboard/lesson_category/notificati
import { Payments } from '@/components/dashboard/lesson_category/payments';
import type { Address } from '@/components/dashboard/lesson_category/shipping-address';
import { ShippingAddress } from '@/components/dashboard/lesson_category/shipping-address';
import type { LessonCategory } from '@/components/dashboard/lesson_category/types';
import { LessonCategory } from '@/components/dashboard/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type';
import FormLoading from '@/components/loading';
// export const metadata = { title: `Details | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;

View File

@@ -1,5 +1,7 @@
import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/types';
import { LessonCategory } from '@/components/dashboard/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type';
// import type { LessonCategory } from '@/components/dashboard/lesson_category/lesson-categories-table';
// import type { LessonCategory } from '@/components/dashboard/lesson_category/interfaces';

View File

@@ -24,7 +24,8 @@ import type { Filters } from '@/components/dashboard/lesson_category/lesson-cate
import { LessonCategoriesPagination } from '@/components/dashboard/lesson_category/lesson-categories-pagination';
import { LessonCategoriesSelectionProvider } from '@/components/dashboard/lesson_category/lesson-categories-selection-context';
import { LessonCategoriesTable } from '@/components/dashboard/lesson_category/lesson-categories-table';
import type { LessonCategory } from '@/components/dashboard/lesson_category/types';
import { LessonCategory } from '@/components/dashboard/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type';
import FormLoading from '@/components/loading';
// import { lessonCategoriesSampleData } from './lesson-categories-sample-data';

View File

@@ -17,13 +17,13 @@ import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultLessonType, LessonTypeDefaultValue } from '@/components/dashboard/lesson_type/_constants';
import { type LessonType } from '@/components/dashboard/lesson_type/lesson-type';
import { Notifications } from '@/components/dashboard/lesson_type/notifications';
import { type LessonType } from '@/components/dashboard/lesson_type/types';
import FormLoading from '@/components/loading';
import SampleAddressCard from '../../Sample/AddressCard';
import BasicDetailCard from '../../Sample/BasicDetailCard';
import SampleAddressCard from '../../Sample/SampleAddressCard';
import { SampleNotifications } from '../../Sample/SampleNotifications';
import { SampleNotifications } from '../../Sample/Notifications';
import SamplePaymentCard from '../../Sample/SamplePaymentCard';
import SampleSecurityCard from '../../Sample/SampleSecurityCard';
import SampleTitleCard from '../../Sample/SampleTitleCard';

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs';
import { LessonType } from '@/components/dashboard/lesson_type/types';
import { LessonType } from '@/components/dashboard/lesson_type/lesson-type';
// import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs';
import { LessonType } from '@/components/dashboard/lesson_type/types';
import { LessonType } from '@/components/dashboard/lesson_type/lesson-type';
// import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';

View File

@@ -19,6 +19,7 @@ import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultLessonType } from '@/components/dashboard/lesson_type/_constants';
import type { LessonType } from '@/components/dashboard/lesson_type/lesson-type';
// import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
// import { defaultLessonType, emptyLessonType, safeAssignment } from '@/components/dashboard/lesson_type/interfaces';
import { LessonTypesFilters } from '@/components/dashboard/lesson_type/lesson-types-filters';
@@ -26,7 +27,6 @@ import type { Filters } from '@/components/dashboard/lesson_type/lesson-types-fi
import { LessonTypesPagination } from '@/components/dashboard/lesson_type/lesson-types-pagination';
import { LessonTypesSelectionProvider } from '@/components/dashboard/lesson_type/lesson-types-selection-context';
import { LessonTypesTable } from '@/components/dashboard/lesson_type/lesson-types-table';
import type { LessonType } from '@/components/dashboard/lesson_type/types';
import FormLoading from '@/components/loading';
export default function Page({ searchParams }: PageProps): React.JSX.Element {

View File

@@ -12,18 +12,19 @@ import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/Arrow
import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';
import type { LpCategory } from '@/types/LpCategory';
// import type { LpCategory } from '@/types/type.d';
import { paths } from '@/paths';
import { logger } from '@/lib/default-logger';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultLpCategory, LpCategoryDefaultValue } from '@/components/dashboard/lp_categories/_constants';
import { Notifications } from '@/components/dashboard/lp_categories/notifications';
import { LpCategory } from '@/components/dashboard/lp_categories/type';
import FormLoading from '@/components/loading';
import SampleAddressCard from '../../Sample/AddressCard';
import BasicDetailCard from '../../Sample/BasicDetailCard';
import SampleAddressCard from '../../Sample/SampleAddressCard';
import { SampleNotifications } from '../../Sample/SampleNotifications';
import { SampleNotifications } from '../../Sample/Notifications';
import SamplePaymentCard from '../../Sample/SamplePaymentCard';
import SampleSecurityCard from '../../Sample/SampleSecurityCard';
import SampleTitleCard from '../../Sample/SampleTitleCard';

View File

@@ -0,0 +1,50 @@
'use client';
import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';
import { paths } from '@/paths';
// import { LessonTypeEditForm } from '@/components/dashboard/lesson_type/lesson-type-edit-form';
import { LpCategoryEditForm } from '@/components/dashboard/lp_categories/lp-category-edit-form';
export default function Page(): React.JSX.Element {
const { t } = useTranslation();
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack spacing={3}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.lesson_types.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
{t('dashboard.lessonTypes.title')}
</Link>
</div>
<div>
<Typography variant="h4">{t('dashboard.lessonTypes.edit.title')}</Typography>
</div>
</Stack>
<LpCategoryEditForm />
</Stack>
</Box>
);
}

View File

@@ -13,7 +13,7 @@ import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import type { ListResult, RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';
import type { LpCategory } from '@/types/LpCategory';
// import type { LpCategory } from '@/types/type.d';
import { paths } from '@/paths';
import ErrorDisplay from '@/components/dashboard/error';
import { defaultLpCategory } from '@/components/dashboard/lp_categories/_constants';
@@ -21,7 +21,8 @@ import { LpCategoriesFilters } from '@/components/dashboard/lp_categories/lp-cat
import type { Filters } from '@/components/dashboard/lp_categories/lp-categories-filters';
import { LpCategoriesPagination } from '@/components/dashboard/lp_categories/lp-categories-pagination';
import { LpCategoriesSelectionProvider } from '@/components/dashboard/lp_categories/lp-categories-selection-context';
import { LpCategoriesTable } from '@/components/dashboard/lp_categories/lp-categories-table';
import { LpCategoriesTable } from '@/components/dashboard/lp_categories/lp-category-table';
import { LpCategory } from '@/components/dashboard/lp_categories/type';
import FormLoading from '@/components/loading';
export default function Page({ searchParams }: PageProps): React.JSX.Element {
@@ -45,7 +46,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
const reloadRows = async (): Promise<void> => {
try {
const models: ListResult<RecordModel> = await listWithOption({
currentPage: currentPage + 1,
currentPage,
rowsPerPage,
listOption,
});
@@ -143,6 +144,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
<Box sx={{ overflowX: 'auto' }}>
<LpCategoriesTable
rows={f}
reloadRows={reloadRows}
//
/>
</Box>

View File

@@ -1,6 +1,8 @@
import { dayjs } from '@/lib/dayjs';
import type { CreateForm, LessonCategory } from './types';
import { CreateForm, LessonCategory } from './type';
// import type { CreateForm, LessonCategory } from '../lp_categories/type';
export const defaultLessonCategory: LessonCategory = {
isEmpty: false,
@@ -30,6 +32,10 @@ export const LessonCategoryCreateFormDefault: CreateForm = {
type: '',
pos: 1,
visible: 'visible',
description: '',
isActive: true,
order: 1,
imageUrl: '',
};
export const emptyLessonCategory: LessonCategory = {

View File

@@ -24,8 +24,9 @@ import { pb } from '@/lib/pb';
import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button';
import { Option } from '@/components/core/option';
// import { LessonCategory } from '../lp_categories/type';
import { useLessonCategoriesSelection } from './lesson-categories-selection-context';
import { LessonCategory } from './types';
import { LessonCategory } from './type';
export interface Filters {
email?: string;

View File

@@ -2,10 +2,13 @@
import * as React from 'react';
// import type { LessonCategory } from '@/types/lesson-type';
import { useSelection } from '@/hooks/use-selection';
import type { Selection } from '@/hooks/use-selection';
import type { LessonCategory } from './types';
import { LessonCategory } from './type';
// import type { LessonCategory } from '../lp_categories/type';
// import type { LessonCategory } from './lesson-categories-table';
// import type { LessonCategory } from '@/components/dashboard/lesson_category/interfaces';

View File

@@ -25,10 +25,11 @@ import { dayjs } from '@/lib/dayjs';
import { DataTable } from '@/components/core/data-table';
import type { ColumnDef } from '@/components/core/data-table';
// import { LessonCategory } from '../lp_categories/type';
import ConfirmDeleteModal from './confirm-delete-modal';
// import type { LessonCategory } from './interfaces.1ts';
import { useLessonCategoriesSelection } from './lesson-categories-selection-context';
import { LessonCategory } from './types';
import { LessonCategory } from './type';
function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonCategory>[] {
return [

View File

@@ -42,7 +42,7 @@ import FormLoading from '@/components/loading';
import ErrorDisplay from '../error';
import { defaultLessonCategory } from './_constants';
import { EditFormProps, LessonCategory } from './types';
import type { EditFormProps, LessonCategory } from './type';
// TODO: review this
const schema = zod.object({
@@ -97,6 +97,7 @@ export function LessonCategoryEditForm(): React.JSX.Element {
visible: values.visible,
description: values.description,
remarks: values.remarks,
type: '',
};
pb.collection(COL_LESSON_CATEGORIES)
@@ -325,7 +326,7 @@ export function LessonCategoryEditForm(): React.JSX.Element {
onUpdate={({ editor }) => {
field.onChange({ target: { value: editor.getText() } });
}}
hideToolbar={true}
hideToolbar
placeholder={t('edit.write-something', NS_DEFAULT)}
/>
</Box>

View File

@@ -2,6 +2,8 @@ export interface LessonCategory {
isEmpty?: boolean;
//
id: string;
collectionId: string;
//
cat_name: string;
cat_image_url?: string;
cat_image?: string;
@@ -10,7 +12,6 @@ export interface LessonCategory {
lesson_id: string;
description: string;
remarks: string;
collectionId: string;
//
name: string;
avatar: string;
@@ -26,6 +27,10 @@ export interface CreateForm {
type: string;
pos: number;
visible: string;
description: string;
isActive: boolean;
order: number;
imageUrl: string;
}
export interface EditFormProps {
@@ -34,4 +39,9 @@ export interface EditFormProps {
visible: string;
description?: string;
remarks?: string;
type: string;
}
export interface Helloworld {
helloworld: string;
}

View File

@@ -4,7 +4,7 @@ import type { RecordModel } from 'pocketbase';
import { dayjs } from '@/lib/dayjs';
// import type { LessonType } from './ILessonType';
import type { CreateForm, LessonType } from './types';
import type { CreateForm, LessonType } from './lesson-type';
export const LessonTypeCreateFormDefault: CreateForm = {
name: '',

View File

@@ -37,7 +37,7 @@ import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import { LessonTypeCreateFormDefault } from './_constants';
import { CreateForm } from './types';
import { CreateForm } from './lesson-type';
// import { CreateForm, LessonTypeCreateFormDefault } from './interfaces';

View File

@@ -41,7 +41,7 @@ import { defaultLessonType } from './_constants';
// import { getLessonTypeById, updateLessonType } from './http-actions';
// TODO: this may be wrong
// import type { LessonType } from './ILessonType';
import { LessonTypeEditFormProps } from './types';
import type { LessonTypeEditFormProps } from './lesson-type';
// import { defaultLessonType, type LessonTypeEditFormProps } from './interfaces';

View File

@@ -1,4 +1,3 @@
import { NO_NUM, NO_VALUE } from '@/constants';
import type { RecordModel } from 'pocketbase';
import { dayjs } from '@/lib/dayjs';

View File

@@ -24,8 +24,8 @@ import { pb } from '@/lib/pb';
import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button';
import { Option } from '@/components/core/option';
import type { LessonType } from './lesson-type';
import { useLessonTypesSelection } from './lesson-types-selection-context';
import type { LessonType } from './types';
export interface Filters {
email?: string;

View File

@@ -5,7 +5,7 @@ import * as React from 'react';
import { useSelection } from '@/hooks/use-selection';
import type { Selection } from '@/hooks/use-selection';
import { LessonType } from './types';
import { LessonType } from './lesson-type';
// import { LessonType } from './ILessonType';

View File

@@ -26,9 +26,9 @@ import { DataTable } from '@/components/core/data-table';
import type { ColumnDef } from '@/components/core/data-table';
import ConfirmDeleteModal from './confirm-delete-modal';
import { LessonType } from './lesson-type';
// import type { LessonType } from './ILessonType';
import { useLessonTypesSelection } from './lesson-types-selection-context';
import { LessonType } from './types';
function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonType>[] {
return [

View File

@@ -0,0 +1,53 @@
export interface LessonTypeEditFormProps {
name: string;
type: string;
pos: number;
visible: string;
}
export interface RestLessonTypeUpdateForm {
id: string;
data: LessonTypeEditFormProps;
}
export interface CreateForm {
name: string;
type: string;
pos: number;
visible: string;
}
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;
}

View File

@@ -1,13 +1,17 @@
import { NO_NUM, NO_VALUE } from '@/constants';
import type { RecordModel } from 'pocketbase';
import { CreateForm, LpCategory } from '@/types/LpCategory';
import { dayjs } from '@/lib/dayjs';
import type { CreateForm, LpCategory } from './type';
// import type { CreateForm, LpCategory } from './types';
export const LpCategoryCreateFormDefault: CreateForm = {
name: '',
type: '',
pos: 1,
visible: 'visible',
description: '',
isActive: true,
order: 1,
@@ -16,18 +20,27 @@ export const LpCategoryCreateFormDefault: CreateForm = {
export const defaultLpCategory: LpCategory = {
id: '',
name: '',
cat_name: '',
cat_image: '',
collectionId: '',
//
cat_name: '',
cat_image_url: '',
cat_image: '',
pos: 1,
lesson_id: '1',
description: '',
isActive: true,
order: 1,
imageUrl: '',
remarks: '',
createdAt: dayjs().toDate(),
updatedAt: dayjs().toDate(),
visible: 'active',
status: '',
visible: 'visible',
//
name: '',
avatar: '',
email: '',
phone: '',
quota: 0,
order: 1,
status: 'NA',
isActive: true,
imageUrl: '',
};
export const emptyLpCategory: LpCategory = {

View File

@@ -18,12 +18,13 @@ import Tabs from '@mui/material/Tabs';
import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import type { LpCategory } from '@/types/LpCategory';
import { paths } from '@/paths';
import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button';
import { Option } from '@/components/core/option';
import { useLpCategoriesSelection } from './lp-categories-selection-context';
// import type { LpCategory } from '@/types/type.d';
import type { LpCategory } from './type';
export interface Filters {
email?: string;

View File

@@ -5,7 +5,7 @@ import * as React from 'react';
import { useSelection } from '@/hooks/use-selection';
import type { Selection } from '@/hooks/use-selection';
import type { LpCategory } from '../../../types/LpCategory.tsx';
import type { LpCategory } from './type';
function noop(): void {
return undefined;

View File

@@ -0,0 +1,295 @@
'use client';
import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import { COL_LESSON_TYPES } from '@/constants';
import getQuizListeningById from '@/db/QuizListenings/GetById';
import { zodResolver } from '@hookform/resolvers/zod';
import { LoadingButton } from '@mui/lab';
import { Avatar, MenuItem } from '@mui/material';
// import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
// import Checkbox from '@mui/material/Checkbox';
import Divider from '@mui/material/Divider';
import FormControl from '@mui/material/FormControl';
// import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
import InputLabel from '@mui/material/InputLabel';
import OutlinedInput from '@mui/material/OutlinedInput';
import Select from '@mui/material/Select';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import type { RecordModel } from 'pocketbase';
import PocketBase from 'pocketbase';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z as zod } from 'zod';
import { paths } from '@/paths';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
// import { Option } from '@/components/core/option';
import { toast } from '@/components/core/toaster';
import { EditFormProps } from '@/components/dashboard/lp_categories/type';
import FormLoading from '@/components/loading';
import { LessonTypeEditFormProps } from '../lesson_type/lesson-type';
import { defaultLpCategory } from './_constants';
// import { getLessonTypeById, updateLessonType } from './http-actions';
// import { LessonTypeEditFormProps } from './types';
// import { defaultLessonType, type LessonTypeEditFormProps } from './interfaces';
// function fileToBase64(file: Blob): Promise<string> {
// return new Promise((resolve, reject) => {
// const reader = new FileReader();
// reader.readAsDataURL(file);
// reader.onload = () => {
// resolve(reader.result as string);
// };
// reader.onerror = () => {
// reject(new Error('Error converting file to base64'));
// };
// });
// }
const schema = zod.object({
cat_name: zod.string().min(1, 'Name is required').max(255),
type: zod.string().min(1, 'Name is required').max(255),
slug: zod.string().min(1, 'Name is required').max(255),
pos: zod.number().min(1, 'Phone is required').max(15),
visible_to_user: zod.string().max(255),
});
type Values = zod.infer<typeof schema>;
const defaultValues = {
cat_name: '',
slug: '',
type: '',
pos: 1,
visible_to_user: 'visible',
} satisfies Values;
export function LpCategoryEditForm(): React.JSX.Element {
const router = useRouter();
const { t } = useTranslation();
const { typeId } = useParams<{ typeId: string }>();
//
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
const [showLoading, setShowLoading] = React.useState<boolean>(false);
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
watch,
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
const onSubmit = React.useCallback(async (values: Values): Promise<void> => {
setIsUpdating(true);
const tempUpdate: EditFormProps = {
cat_name: values.cat_name,
type: values.type,
pos: values.pos,
visible: values.visible_to_user ? 'visible' : 'hidden',
};
pb.collection(COL_LESSON_TYPES)
.update(typeId, tempUpdate)
.then((res) => {
logger.debug(res);
toast.success(t('dashboard.lessonTypes.update.success'));
setIsUpdating(false);
router.push(paths.dashboard.lesson_types.list);
})
.catch((err) => {
logger.error(err);
toast.error('Something went wrong!');
setIsUpdating(false);
});
}, []);
const avatarInputRef = React.useRef<HTMLInputElement>(null);
// const avatar = watch('avatar');
const handleAvatarChange = React.useCallback(
async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
// const url = await fileToBase64(file);
// setValue('avatar', url);
}
},
[setValue]
);
const handleLoad = React.useCallback(
(id: string) => {
setShowLoading(true);
getQuizListeningById(id)
.then((model: RecordModel) => {
reset({ ...defaultLpCategory, ...model });
})
.catch((err) => {
logger.error(err);
toast(t('dashboard.lessonTypes.list.error'));
})
.finally(() => {
setShowLoading(false);
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[typeId]
);
React.useEffect(() => {
handleLoad(typeId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [typeId]);
if (showLoading) return <FormLoading />;
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Card>
<CardContent>
<Stack divider={<Divider />} spacing={4}>
<Stack spacing={3}>
<Typography variant="h6">{t('dashboard.lessonTypes.edit.typeInformation')}</Typography>
<Grid container spacing={3}>
<Grid xs={12}>
<Stack direction="row" spacing={3} sx={{ alignItems: 'center' }}>
<Box
sx={{
border: '1px dashed var(--mui-palette-divider)',
borderRadius: '50%',
display: 'inline-flex',
p: '4px',
}}
>
{/*
<Avatar
src={avatar}
sx={{
'--Avatar-size': '100px',
'--Icon-fontSize': 'var(--icon-fontSize-lg)',
alignItems: 'center',
bgcolor: 'var(--mui-palette-background-level1)',
color: 'var(--mui-palette-text-primary)',
display: 'flex',
justifyContent: 'center',
}}
>
<CameraIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
*/}
</Box>
<Stack spacing={1} sx={{ alignItems: 'flex-start' }}>
<Typography variant="subtitle1">{t('dashboard.lessonTypes.edit.avatar')}</Typography>
<Typography variant="caption">{t('dashboard.lessonTypes.edit.avatarRequirements')}</Typography>
<Button
color="secondary"
onClick={() => {
avatarInputRef.current?.click();
}}
variant="outlined"
>
{t('dashboard.lessonTypes.edit.select')}
</Button>
<input hidden onChange={handleAvatarChange} ref={avatarInputRef} type="file" />
</Stack>
</Stack>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="cat_name"
render={({ field }) => (
<FormControl error={Boolean(errors.cat_name)} fullWidth>
<InputLabel required>{t('dashboard.lessonTypes.edit.name')}</InputLabel>
<OutlinedInput {...field} />
{errors.cat_name ? <FormHelperText>{errors.cat_name.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="type"
render={({ field }) => (
<FormControl error={Boolean(errors.type)} fullWidth>
<InputLabel required>{t('dashboard.lessonTypes.edit.type')}</InputLabel>
<OutlinedInput {...field} />
{errors.type ? <FormHelperText>{errors.type.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="pos"
render={({ field }) => (
<FormControl error={Boolean(errors.pos)} fullWidth>
<InputLabel required>{t('dashboard.lessonTypes.edit.position')}</InputLabel>
<OutlinedInput
{...field}
onChange={(e) => {
field.onChange(parseInt(e.target.value));
}}
type="number"
/>
{errors.pos ? <FormHelperText>{errors.pos.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="visible_to_user"
render={({ field }) => (
<FormControl error={Boolean(errors.visible_to_user)} fullWidth>
<InputLabel>{t('dashboard.lessonTypes.edit.visibleToUser')}</InputLabel>
<Select {...field}>
<MenuItem value="visible">{t('dashboard.lessonTypes.edit.visible')}</MenuItem>
<MenuItem value="hidden">{t('dashboard.lessonTypes.edit.hidden')}</MenuItem>
</Select>
{errors.visible_to_user ? (
<FormHelperText>{errors.visible_to_user.message}</FormHelperText>
) : null}
</FormControl>
)}
/>
</Grid>
</Grid>
</Stack>
</Stack>
</CardContent>
<CardActions sx={{ justifyContent: 'flex-end' }}>
<Button color="secondary" component={RouterLink} href={paths.dashboard.lesson_types.list}>
{t('dashboard.lessonTypes.edit.cancelButton')}
</Button>
<LoadingButton disabled={isUpdating} loading={isUpdating} type="submit" variant="contained">
{t('dashboard.lessonTypes.edit.updateButton')}
</LoadingButton>
</CardActions>
</Card>
</form>
);
}

View File

@@ -20,14 +20,14 @@ import { TrashSimple as TrashSimpleIcon } from '@phosphor-icons/react/dist/ssr/T
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
import type { LpCategory } from '@/types/LpCategory';
import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs';
import { DataTable } from '@/components/core/data-table';
import type { ColumnDef } from '@/components/core/data-table';
import type { LessonCategory } from '../lesson_category/types';
import ConfirmDeleteModal from './confirm-delete-modal';
import { useLpCategoriesSelection } from './lp-categories-selection-context';
import type { LpCategory } from './type';
function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LpCategory>[] {
return [
@@ -83,10 +83,8 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LpCateg
blocked: { label: 'Blocked', icon: <MinusIcon color="var(--mui-palette-error-main)" /> },
pending: { label: 'Pending', icon: <ClockIcon color="var(--mui-palette-warning-main)" weight="fill" /> },
NA: { label: 'NA', icon: <ClockIcon color="var(--mui-palette-warning-main)" weight="fill" /> },
visible: { label: 'visible', icon: <ClockIcon color="var(--mui-palette-warning-main)" weight="fill" /> },
hidden: { label: 'hidden', icon: <ClockIcon color="var(--mui-palette-warning-main)" weight="fill" /> },
} as const;
const { label, icon } = mapping[row.visible] ?? { label: 'Unknown', icon: null };
const { label, icon } = mapping[row.status] ?? { label: 'Unknown', icon: null };
return (
<Button
@@ -139,15 +137,16 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LpCateg
];
}
export interface LpCategoriesTableProps {
export interface LpCategoryTableProps {
rows: LpCategory[];
reloadRows: () => void;
}
function getCatImageFromId(row: LpCategory): string | undefined {
return `http://127.0.0.1:8090/api/files/${row.collectionId}/${row.id}/${row.cat_image}`;
}
export function LpCategoriesTable({ rows }: LpCategoriesTableProps): React.JSX.Element {
export function LpCategoriesTable({ rows, reloadRows }: LpCategoryTableProps): React.JSX.Element {
const { t } = useTranslation();
const { deselectAll, deselectOne, selectAll, selectOne, selected } = useLpCategoriesSelection();
@@ -161,6 +160,7 @@ export function LpCategoriesTable({ rows }: LpCategoriesTableProps): React.JSX.E
return (
<React.Fragment>
<ConfirmDeleteModal idToDelete={idToDelete} open={open} reloadRows={reloadRows} setOpen={setOpen} />
<DataTable<LpCategory>
columns={columns(handleDeleteClick)}
onDeselectAll={deselectAll}

View File

@@ -1,6 +1,7 @@
'use client';
import * as React from 'react';
import { Notification } from '@/app/dashboard/Sample/Notifications/type';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
@@ -14,7 +15,7 @@ import Typography from '@mui/material/Typography';
import { EnvelopeSimple as EnvelopeSimpleIcon } from '@phosphor-icons/react/dist/ssr/EnvelopeSimple';
import { useTranslation } from 'react-i18next';
import type { Notification } from '@/types/notification';
// import type { Notification } from '@/types/type';
import { dayjs } from '@/lib/dayjs';
import { DataTable } from '@/components/core/data-table';
import type { ColumnDef } from '@/components/core/data-table';

View File

@@ -0,0 +1,50 @@
export interface LpCategory {
isEmpty?: boolean;
//
id: string;
collectionId: string;
//
cat_name: string;
cat_image_url?: string;
cat_image?: string;
pos: number;
visible: string;
lesson_id: string;
description: string;
remarks: string;
createdAt: Date;
visible: 'pending' | 'active' | 'blocked' | 'NA' | 'visible' | 'hidden';
name: string;
avatar: string;
email: string;
phone: string;
quota: number;
status: 'pending' | 'active' | 'blocked' | 'NA';
isActive: boolean;
order: number;
imageUrl: string;
}
export interface CreateForm {
name: string;
type: string;
pos: number;
visible: string;
description: string;
isActive: boolean;
order: number;
imageUrl: string;
}
export interface EditFormProps {
cat_name: string;
pos: number;
visible: string;
description?: string;
remarks?: string;
type: string;
}
export interface Helloworld {
helloworld: string;
}

View File

@@ -2,7 +2,7 @@ import { COL_LESSON_CATEGORIES } from '@/constants';
import type { RecordModel } from 'pocketbase';
import { pb } from '@/lib/pb';
import type { CreateForm } from '@/components/dashboard/lesson_category/types';
import type { CreateForm } from '@/components/dashboard/lp_categories/type';
export default function createLessonCategory(data: CreateForm): Promise<RecordModel> {
return pb.collection(COL_LESSON_CATEGORIES).create(data);

View File

@@ -2,7 +2,7 @@ import { COL_LESSON_CATEGORIES } from '@/constants';
import type { RecordModel } from 'pocketbase';
import { pb } from '@/lib/pb';
import type { CreateForm } from '@/components/dashboard/lesson_category/types';
import type { CreateForm } from '@/components/dashboard/lp_categories/type';
export default function updateLessonCategory(id: string, data: CreateForm): Promise<RecordModel> {
return pb.collection(COL_LESSON_CATEGORIES).update(id, data);

View File

@@ -2,8 +2,7 @@ import { COL_LESSON_TYPES } from '@/constants';
import type { RecordModel } from 'pocketbase';
import { pb } from '@/lib/pb';
import type { CreateForm } from '@/components/dashboard/lesson_type/types';
import type { CreateForm } from '@/components/dashboard/lesson_type/lesson-type';
export default function createLessonType(data: CreateForm): Promise<RecordModel> {
return pb.collection(COL_LESSON_TYPES).create(data);

View File

@@ -2,7 +2,7 @@ import { COL_LESSON_TYPES } from '@/constants';
import type { RecordModel } from 'pocketbase';
import { pb } from '@/lib/pb';
import type { CreateForm } from '@/components/dashboard/lesson_type/types';
import type { CreateForm } from '@/components/dashboard/lesson_type/lesson-type';
export default function updateLessonType(id: string, data: CreateForm): Promise<RecordModel> {
return pb.collection(COL_LESSON_TYPES).update(id, data);

View File

@@ -1,62 +0,0 @@
/*
RULES:
please follow the naming convention
*/
export interface LpCategoryFormProps {
name: string; // corresponds to cat_name
description: string; // additional business field
isActive: boolean; // additional business field
order: number; // corresponds to pos
imageUrl: string; // corresponds to cat_image
initAnswer?: any; // corresponds to init_answer
}
export interface RestLpCategoryUpdateForm {
id: string;
data: LpCategoryFormProps;
}
// RULES: this one should be obsoleted, no need to take care
// export function safeAssignment(inTemp: LpCategory | RecordModel): LpCategory {
// const { id, name, description, isActive, order, imageUrl, createdAt, updatedAt, initAnswer } = {
// ...defaultLpCategory,
// ...inTemp,
// };
// return {
// id,
// name,
// description,
// isActive,
// order,
// imageUrl,
// createdAt: dayjs(createdAt).toDate(),
// updatedAt: updatedAt ? dayjs(updatedAt).toDate() : new Date(),
// initAnswer,
// visible: 'public', // default value
// };
// }
export interface LpCategory {
isEmpty?: boolean;
id: string;
collectionId: string;
name: string;
cat_name: string; // corresponds to cat_name
cat_image: string; // corresponds to cat_image
description: string; // additional business field
isActive: boolean; // additional business field
order: number; // corresponds to pos
imageUrl: string; // corresponds to cat_image
createdAt: Date;
updatedAt: Date; // new field
initAnswer?: any; // corresponds to init_answer
visible: 'active' | 'blocked' | 'pending' | 'NA' | 'visible' | 'hidden'; // additional business field
quota?: number;
//
email?: string;
phone?: string;
status: string;
}
export type CreateForm = LpCategoryFormProps;