diff --git a/002_source/cms/package.json b/002_source/cms/package.json index 98179a8..61e9a0e 100644 --- a/002_source/cms/package.json +++ b/002_source/cms/package.json @@ -15,7 +15,7 @@ "lint:fix": "next lint --fix", "lint:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 15 --exec \"pnpm run lint\"", "typecheck": "tsc --noEmit", - "typecheck:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 2 --exec \"pnpm run typecheck\" || exit 1", + "typecheck:w": "tsc --noEmit -w", "format:write": "prettier --write \"**/*.{js,jsx,mjs,ts,tsx,mdx}\" --cache", "format:check": "prettier --check \"**/*.{js,jsx,mjs,ts,tsx,mdx}\" --cache", "format:fix": "prettier --write \"**/*.{js,jsx,mjs,ts,tsx,mdx}\" --cache", @@ -116,4 +116,4 @@ "protobufjs" ] } -} \ No newline at end of file +} diff --git a/002_source/cms/src/app/dashboard/customers/page.tsx b/002_source/cms/src/app/dashboard/customers/page.tsx index 724c79c..552f8dd 100644 --- a/002_source/cms/src/app/dashboard/customers/page.tsx +++ b/002_source/cms/src/app/dashboard/customers/page.tsx @@ -70,6 +70,106 @@ const customers = [ status: 'active', createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(), }, + { + id: 'USR-005', + name: 'Fran Perez', + avatar: '/assets/avatar-5.png', + email: 'fran.perez@domain.com', + phone: '(815) 704-0045', + quota: 50, + status: 'active', + createdAt: dayjs().subtract(1, 'hour').toDate(), + }, + { + id: 'USR-004', + name: 'Penjani Inyene', + avatar: '/assets/avatar-4.png', + email: 'penjani.inyene@domain.com', + phone: '(803) 937-8925', + quota: 100, + status: 'active', + createdAt: dayjs().subtract(3, 'hour').toDate(), + }, + { + id: 'USR-003', + name: 'Carson Darrin', + avatar: '/assets/avatar-3.png', + email: 'carson.darrin@domain.com', + phone: '(715) 278-5041', + quota: 10, + status: 'blocked', + createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(), + }, + { + id: 'USR-002', + name: 'Siegbert Gottfried', + avatar: '/assets/avatar-2.png', + email: 'siegbert.gottfried@domain.com', + phone: '(603) 766-0431', + quota: 0, + status: 'pending', + createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(), + }, + { + id: 'USR-001', + name: 'Miron Vitold', + avatar: '/assets/avatar-1.png', + email: 'miron.vitold@domain.com', + phone: '(425) 434-5535', + quota: 50, + status: 'active', + createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(), + }, + { + id: 'USR-005', + name: 'Fran Perez', + avatar: '/assets/avatar-5.png', + email: 'fran.perez@domain.com', + phone: '(815) 704-0045', + quota: 50, + status: 'active', + createdAt: dayjs().subtract(1, 'hour').toDate(), + }, + { + id: 'USR-004', + name: 'Penjani Inyene', + avatar: '/assets/avatar-4.png', + email: 'penjani.inyene@domain.com', + phone: '(803) 937-8925', + quota: 100, + status: 'active', + createdAt: dayjs().subtract(3, 'hour').toDate(), + }, + { + id: 'USR-003', + name: 'Carson Darrin', + avatar: '/assets/avatar-3.png', + email: 'carson.darrin@domain.com', + phone: '(715) 278-5041', + quota: 10, + status: 'blocked', + createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(), + }, + { + id: 'USR-002', + name: 'Siegbert Gottfried', + avatar: '/assets/avatar-2.png', + email: 'siegbert.gottfried@domain.com', + phone: '(603) 766-0431', + quota: 0, + status: 'pending', + createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(), + }, + { + id: 'USR-001', + name: 'Miron Vitold', + avatar: '/assets/avatar-1.png', + email: 'miron.vitold@domain.com', + phone: '(425) 434-5535', + quota: 50, + status: 'active', + createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(), + }, ] satisfies Customer[]; interface PageProps { diff --git a/002_source/cms/src/app/dashboard/lesson_types/[type_id]/page.tsx b/002_source/cms/src/app/dashboard/lesson_types/[type_id]/page.tsx index 20526a5..e732eeb 100644 --- a/002_source/cms/src/app/dashboard/lesson_types/[type_id]/page.tsx +++ b/002_source/cms/src/app/dashboard/lesson_types/[type_id]/page.tsx @@ -47,7 +47,7 @@ import { ShippingAddress } from '@/components/dashboard/lesson_type/shipping-add import FormLoading from '@/components/loading'; export default function Page(): React.JSX.Element { - const { t } = useTranslation(['common']); + const { t } = useTranslation(['common', 'lesson_type']); const router = useRouter(); // const { type_id: typeId } = useParams<{ type_id: string }>(); diff --git a/002_source/cms/src/app/dashboard/lesson_types/page.tsx b/002_source/cms/src/app/dashboard/lesson_types/page.tsx index 8b1df75..bac719b 100644 --- a/002_source/cms/src/app/dashboard/lesson_types/page.tsx +++ b/002_source/cms/src/app/dashboard/lesson_types/page.tsx @@ -19,7 +19,7 @@ import { pb } from '@/lib/pb'; import { toast } from '@/components/core/toaster'; import ErrorDisplay from '@/components/dashboard/error'; import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType'; -import { 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 type { Filters } from '@/components/dashboard/lesson_type/lesson-types-filters'; import { LessonTypesPagination } from '@/components/dashboard/lesson_type/lesson-types-pagination'; @@ -27,81 +27,77 @@ import { LessonTypesSelectionProvider } from '@/components/dashboard/lesson_type import { LessonTypesTable } from '@/components/dashboard/lesson_type/lesson-types-table'; import FormLoading from '@/components/loading'; -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 { const { t } = useTranslation(); - const { email, phone, sortDir, spStatus, spName, spVisible, spType } = searchParams; + const { email, phone, sortDir, status, name, visible, type } = searchParams; const router = useRouter(); + const [lessonTypesData, setLessonTypesData] = React.useState([]); - const [recordCount, setRecordCount] = React.useState(0); - const [rowsPerPage, setRowsPerPage] = React.useState(5); - const [currentPage, setCurrentPage] = React.useState(1); - - // const [isLoadingAddPage, setIsLoadingAddPage] = React.useState(false); const [showLoading, setShowLoading] = React.useState(true); const [showError, setShowError] = React.useState(false); - const [lessonTypesData, setLessonTypesData] = React.useState([]); - // const [recordModel, setRecordModel] = React.useState([]); - const sortedLessonTypes = applySort(lessonTypesData, sortDir); - const filteredLessonTypesOld = applyFilters(sortedLessonTypes, { - email, - phone, - spStatus, - spName, - spType, - spVisible, - // - }); + const [rowsPerPage, setRowsPerPage] = React.useState(5); - const needToFill = 5 - filteredLessonTypesOld.length; + const [f, setF] = React.useState([]); + const [currentPage, setCurrentPage] = React.useState(0); + const [recordCount, setRecordCount] = React.useState(0); - const filteredLessonTypes: LessonType[] = filteredLessonTypesOld; - for (let i = 0; i < needToFill; i++) { - filteredLessonTypes.push(emptyLessonType); - } + const [listOption, setListOption] = React.useState({}); + const [listSort, setListSort] = React.useState({}); - const reloadRows = () => { - pb.collection(COL_LESSON_TYPES) - .getList(currentPage, rowsPerPage, {}) - .then((lessonTypes: ListResult) => { - // console.log(lessonTypes); - const { items, page, perPage, totalItems, totalPages } = lessonTypes; - const tempLessonTypes: LessonType[] = items.map((lt) => { - return safeAssignment(lt); - }); - - setLessonTypesData(tempLessonTypes); - setRecordCount(totalItems); - }) - .catch((err) => { - logger.error(err); - toast(t('dashboard.lessonTypes.list.error')); - - setShowError(true); - }) - .finally(() => { - setShowLoading(false); + // + const reloadRows = async (): Promise => { + try { + const models: ListResult = await pb + .collection(COL_LESSON_TYPES) + .getList(currentPage + 1, rowsPerPage, listOption); + const { items, totalItems } = models; + const tempLessonTypes: LessonType[] = items.map((lt) => { + return { ...defaultLessonType, ...lt }; }); + + setLessonTypesData(tempLessonTypes); + setRecordCount(totalItems); + setF(tempLessonTypes); + } catch (error) { + // + } finally { + setShowLoading(false); + } }; React.useEffect(() => { - reloadRows(); - }, []); + void reloadRows(); + }, [currentPage, rowsPerPage, listOption]); - if (showLoading) return ; + React.useEffect(() => { + let tempFilter = [], + tempSortDir = ''; + + if (visible) { + tempFilter.push(`visible = "${visible}"`); + } + + if (sortDir) { + tempSortDir = `-created`; + } + + if (name) { + tempFilter.push(`name ~ "%${name}%"`); + } + + if (type) { + tempFilter.push(`type ~ "%${type}%"`); + } + + setListOption({ + filter: tempFilter.join(' && '), + sort: tempSortDir, + // + }); + }, [visible, sortDir, name, type]); + + if (f.length === 0 || showLoading) return ; if (showError) return ( @@ -137,24 +133,24 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { - + - + @@ -175,10 +171,7 @@ function applySort(row: LessonType[], sortDir: 'asc' | 'desc' | undefined): Less }); } -function applyFilters( - row: LessonType[], - { email, phone, spStatus: status, spName: name, spVisible }: Filters -): LessonType[] { +function applyFilters(row: LessonType[], { email, phone, status, name, visible }: Filters): LessonType[] { return row.filter((item) => { if (email) { if (!item.email?.toLowerCase().includes(email.toLowerCase())) { @@ -204,8 +197,8 @@ function applyFilters( } } - if (spVisible) { - if (!item.visible?.toLowerCase().includes(spVisible.toLowerCase())) { + if (visible) { + if (!item.visible?.toLowerCase().includes(visible.toLowerCase())) { return false; } } @@ -213,3 +206,16 @@ function applyFilters( return true; }); } + +interface PageProps { + searchParams: { + email?: string; + phone?: string; + sortDir?: 'asc' | 'desc'; + status?: string; + name?: string; + visible?: string; + type?: string; + // + }; +} diff --git a/002_source/cms/src/components/dashboard/customer/customers-pagination.tsx b/002_source/cms/src/components/dashboard/customer/customers-pagination.tsx index 1edb252..ab01272 100644 --- a/002_source/cms/src/components/dashboard/customer/customers-pagination.tsx +++ b/002_source/cms/src/components/dashboard/customer/customers-pagination.tsx @@ -25,6 +25,7 @@ export function CustomersPagination({ count, page }: CustomersPaginationProps): page={page} rowsPerPage={5} rowsPerPageOptions={[5, 10, 25]} + // /> ); } diff --git a/002_source/cms/src/components/dashboard/lesson_type/lesson-types-filters.tsx b/002_source/cms/src/components/dashboard/lesson_type/lesson-types-filters.tsx index 05c3503..4025589 100644 --- a/002_source/cms/src/components/dashboard/lesson_type/lesson-types-filters.tsx +++ b/002_source/cms/src/components/dashboard/lesson_type/lesson-types-filters.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { useRouter } from 'next/navigation'; +import { COL_LESSON_TYPES } from '@/constants'; import Button from '@mui/material/Button'; import Chip from '@mui/material/Chip'; import Divider from '@mui/material/Divider'; @@ -16,6 +17,7 @@ import Typography from '@mui/material/Typography'; import { useTranslation } from 'react-i18next'; import { paths } from '@/paths'; +import { pb } from '@/lib/pb'; import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button'; import { Option } from '@/components/core/option'; @@ -25,10 +27,10 @@ import { useLessonTypesSelection } from './lesson-types-selection-context'; export interface Filters { email?: string; phone?: string; - spStatus?: string; - spName?: string; - spVisible?: string; - spType?: string; + status?: string; + name?: string; + visible?: string; + type?: string; } export type SortDir = 'asc' | 'desc'; @@ -45,7 +47,11 @@ export function LessonTypesFilters({ fullData, }: LessonTypesFiltersProps): React.JSX.Element { const { t } = useTranslation(); - const { email, phone, spStatus: status, spName, spVisible, spType } = filters; + const { email, phone, status, name, visible, type } = filters; + + const [totalCount, setTotalCount] = React.useState(0); + const [visibleCount, setVisibleCount] = React.useState(0); + const [hiddenCount, setHiddenCount] = React.useState(0); const router = useRouter(); @@ -65,12 +71,12 @@ export function LessonTypesFilters({ // The tabs should be generated using API data. const tabs = [ - { label: 'All', value: '', count: fullData.length }, + { 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: getVisible() }, - { label: t('hidden'), value: 'hidden', count: getHidden() }, + { label: t('visible'), value: 'visible', count: visibleCount }, + { label: t('hidden'), value: 'hidden', count: hiddenCount }, ] as const; const updateSearchParams = React.useCallback( @@ -81,8 +87,8 @@ export function LessonTypesFilters({ searchParams.set('sortDir', newSortDir); } - if (newFilters.spStatus) { - searchParams.set('status', newFilters.spStatus); + if (newFilters.status) { + searchParams.set('status', newFilters.status); } if (newFilters.email) { @@ -93,16 +99,16 @@ export function LessonTypesFilters({ searchParams.set('phone', newFilters.phone); } - if (newFilters.spName) { - searchParams.set('name', newFilters.spName); + if (newFilters.name) { + searchParams.set('name', newFilters.name); } - if (newFilters.spType) { - searchParams.set('type', newFilters.spType); + if (newFilters.type) { + searchParams.set('type', newFilters.type); } - if (newFilters.spVisible) { - searchParams.set('visible', newFilters.spVisible); + if (newFilters.visible) { + searchParams.set('visible', newFilters.visible); } router.push(`${paths.dashboard.lesson_types.list}?${searchParams.toString()}`); @@ -116,28 +122,28 @@ export function LessonTypesFilters({ const handleStatusChange = React.useCallback( (_: React.SyntheticEvent, value: string) => { - updateSearchParams({ ...filters, spStatus: value }, sortDir); + updateSearchParams({ ...filters, status: value }, sortDir); }, [updateSearchParams, filters, sortDir] ); const handleVisibleChange = React.useCallback( (_: React.SyntheticEvent, value: string) => { - updateSearchParams({ ...filters, spVisible: value }, sortDir); + updateSearchParams({ ...filters, visible: value }, sortDir); }, [updateSearchParams, filters, sortDir] ); const handleNameChange = React.useCallback( (value?: string) => { - updateSearchParams({ ...filters, spName: value }, sortDir); + updateSearchParams({ ...filters, name: value }, sortDir); }, [updateSearchParams, filters, sortDir] ); const handleTypeChange = React.useCallback( (value?: string) => { - updateSearchParams({ ...filters, spType: value }, sortDir); + updateSearchParams({ ...filters, type: value }, sortDir); }, [updateSearchParams, filters, sortDir] ); @@ -163,11 +169,33 @@ export function LessonTypesFilters({ [updateSearchParams, filters] ); - const hasFilters = status || email || phone || spVisible || spName || spType; + React.useEffect(() => { + const fetchCount = async (): Promise => { + try { + const { totalItems: tc } = await pb.collection(COL_LESSON_TYPES).getList(1, 9999); + setTotalCount(tc); + + const { totalItems: vc } = await pb + .collection(COL_LESSON_TYPES) + .getList(1, 9999, { filter: 'visible = "visible"' }); + setVisibleCount(vc); + + const { totalItems: hc } = await pb + .collection(COL_LESSON_TYPES) + .getList(1, 9999, { filter: 'visible = "hidden"' }); + setHiddenCount(hc); + } catch (error) { + // + } + }; + void fetchCount(); + }, []); + + const hasFilters = status || email || phone || visible || name || type; return (
- + {tabs.map((tab) => ( } @@ -184,7 +212,7 @@ export function LessonTypesFilters({ { handleNameChange(value as string); @@ -193,11 +221,11 @@ export function LessonTypesFilters({ handleNameChange(); }} popover={} - value={spName} + value={name} /> { handleTypeChange(value as string); @@ -206,7 +234,7 @@ export function LessonTypesFilters({ handleTypeChange(); }} popover={} - value={spType} + value={type} /> {/* diff --git a/002_source/cms/src/components/dashboard/lesson_type/lesson-types-pagination.tsx b/002_source/cms/src/components/dashboard/lesson_type/lesson-types-pagination.tsx index 1f0caee..fabe511 100644 --- a/002_source/cms/src/components/dashboard/lesson_type/lesson-types-pagination.tsx +++ b/002_source/cms/src/components/dashboard/lesson_type/lesson-types-pagination.tsx @@ -10,48 +10,39 @@ function noop(): void { interface LessonTypesPaginationProps { count: number; page: number; + setPage: (page: number) => void; + setRowsPerPage: (page: number) => void; rowsPerPage: number; - setRowsPerPage: React.Dispatch>; - setCurrentPage: React.Dispatch>; } export function LessonTypesPagination({ count, page, - rowsPerPage, + setPage, setRowsPerPage, - setCurrentPage, + rowsPerPage, }: LessonTypesPaginationProps): React.JSX.Element { // You should implement the pagination using a similar logic as the filters. // Note that when page change, you should keep the filter search params. + const handleChangePage = (event: unknown, newPage: number) => { + setPage(newPage); + }; - function handleRowsPerPageChange(e: React.ChangeEvent): void { - console.log(e.target.value); - console.log(parseInt(e.target.value)); - setRowsPerPage(parseInt(e.target.value)); - } - - function handlePageChange(e: React.MouseEvent | null): void { - // console.log(e.target.value); - // console.log(parseInt(e.target.value)); - // setCurrentPage(parseInt(e.target.value)); - } + const handleChangeRowsPerPage = (event: React.ChangeEvent) => { + setRowsPerPage(parseInt(event.target.value)); + // console.log(parseInt(event.target.value)); + }; return ( { - handlePageChange(e); - }} - onRowsPerPageChange={(e) => { - handleRowsPerPageChange(e); - }} + onPageChange={handleChangePage} + onRowsPerPageChange={handleChangeRowsPerPage} /> ); } diff --git a/002_source/cms/src/components/dashboard/lesson_type/lesson-types-table.tsx b/002_source/cms/src/components/dashboard/lesson_type/lesson-types-table.tsx index 17845d7..1e784bf 100644 --- a/002_source/cms/src/components/dashboard/lesson_type/lesson-types-table.tsx +++ b/002_source/cms/src/components/dashboard/lesson_type/lesson-types-table.tsx @@ -83,27 +83,27 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef ), name: 'Lesson position', - width: '150px', + width: '50px', }, { formatter: (row): React.JSX.Element => { // eslint-disable-next-line react-hooks/rules-of-hooks - const { t } = useTranslation(); + // const { t } = useTranslation(); const mapping = { active: { label: 'Active', icon: }, blocked: { label: 'Blocked', icon: }, pending: { label: 'Pending', icon: }, visible: { - label: t('visible'), + label: 'visible', icon: , }, hidden: { - label: t('hidden'), + label: 'hidden', icon: , }, no_value: { - label: t('no_value'), + label: 'no_value', icon: , }, } as const; @@ -123,7 +123,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef