=> {
@@ -77,7 +67,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
setLessonCategoriesData(tempLessonTypes);
setRecordCount(totalItems);
setF(tempLessonTypes);
- // console.log({ currentPage, f });
} catch (error) {
//
logger.error(error);
@@ -107,8 +96,8 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
}, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => {
- let tempFilter = [],
- tempSortDir = '';
+ const tempFilter = [];
+ let tempSortDir = '';
if (status) {
tempFilter.push(`status = "${status}"`);
@@ -133,11 +122,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
}
setListOption(preFinalListOption);
- // setListOption({
- // filter: tempFilter.join(' && '),
- // sort: tempSortDir,
- // //
- // });
}, [sortDir, email, phone, status]);
if (showLoading) return ;
@@ -215,42 +199,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
);
}
-// Sorting and filtering has to be done on the server.
-
-function applySort(row: Student[], sortDir: 'asc' | 'desc' | undefined): Student[] {
- return row.sort((a, b) => {
- if (sortDir === 'asc') {
- return a.createdAt.getTime() - b.createdAt.getTime();
- }
-
- return b.createdAt.getTime() - a.createdAt.getTime();
- });
-}
-
-function applyFilters(row: Student[], { email, phone, status }: Filters): Student[] {
- return row.filter((item) => {
- if (email) {
- if (!item.email?.toLowerCase().includes(email.toLowerCase())) {
- return false;
- }
- }
-
- if (phone) {
- if (!item.phone?.toLowerCase().includes(phone.toLowerCase())) {
- return false;
- }
- }
-
- if (status) {
- if (item.status !== status) {
- return false;
- }
- }
-
- return true;
- });
-}
-
interface PageProps {
- searchParams: { email?: string; phone?: string; sortDir?: 'asc' | 'desc'; status?: string };
+ searchParams: {
+ email?: string;
+ phone?: string;
+ sortDir?: 'asc' | 'desc';
+ status?: string;
+ //
+ };
}
diff --git a/002_source/cms/src/app/dashboard/students/xxx/BasicDetailCard.tsx b/002_source/cms/src/app/dashboard/students/xxx/BasicDetailCard.tsx
new file mode 100644
index 0000000..e64427e
--- /dev/null
+++ b/002_source/cms/src/app/dashboard/students/xxx/BasicDetailCard.tsx
@@ -0,0 +1,80 @@
+'use client';
+
+import * as React from 'react';
+import Avatar from '@mui/material/Avatar';
+import Card from '@mui/material/Card';
+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 { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
+import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
+import { useTranslation } from 'react-i18next';
+
+import { PropertyItem } from '@/components/core/property-item';
+import { PropertyList } from '@/components/core/property-list';
+// import { CrCategory } from '@/components/dashboard/cr/categories/type';
+import type { Student } from '@/components/dashboard/student/type.d';
+
+export default function BasicDetailCard({
+ lpModel: model,
+ handleEditClick,
+}: {
+ lpModel: Student;
+ handleEditClick: () => void;
+}): React.JSX.Element {
+ const { t } = useTranslation();
+
+ return (
+
+ {
+ handleEditClick();
+ }}
+ >
+
+
+ }
+ avatar={
+
+
+
+ }
+ title={t('list.basic-details')}
+ />
+ }
+ orientation="vertical"
+ sx={{ '--PropertyItem-padding': '12px 24px' }}
+ >
+ {(
+ [
+ {
+ key: 'Customer ID',
+ value: (
+
+ ),
+ },
+ { key: 'Email', value: model.email },
+ { key: 'Quota', value: model.quota },
+ { key: 'Status', value: model.status },
+ ] satisfies { key: string; value: React.ReactNode }[]
+ ).map(
+ (item): React.JSX.Element => (
+
+ )
+ )}
+
+
+ );
+}
diff --git a/002_source/cms/src/app/dashboard/students/xxx/TitleCard.tsx b/002_source/cms/src/app/dashboard/students/xxx/TitleCard.tsx
new file mode 100644
index 0000000..9be2807
--- /dev/null
+++ b/002_source/cms/src/app/dashboard/students/xxx/TitleCard.tsx
@@ -0,0 +1,76 @@
+'use client';
+
+import * as React from 'react';
+import { Button } from '@mui/material';
+import Avatar from '@mui/material/Avatar';
+import Chip from '@mui/material/Chip';
+import Stack from '@mui/material/Stack';
+import Typography from '@mui/material/Typography';
+import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
+import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
+import { useTranslation } from 'react-i18next';
+import type { Student } from '@/components/dashboard/student/type.d';
+
+// import type { CrCategory } from '@/components/dashboard/cr/categories/type';
+
+function getImageUrlFrRecord(record: Student): string {
+ // TODO: fix this
+ // `http://127.0.0.1:8090/api/files/${'record.collectionId'}/${'record.id'}/${'record.cat_image'}`;
+ return 'getImageUrlFrRecord(helloworld)';
+}
+
+export default function SampleTitleCard({ lpModel }: { lpModel: Student }): React.JSX.Element {
+ const { t } = useTranslation();
+
+ return (
+ <>
+
+
+ {t('empty')}
+
+
+
+ {lpModel.email}
+
+ }
+ label={lpModel.quota}
+ size="small"
+ variant="outlined"
+ />
+
+
+ {lpModel.status}
+
+
+
+
+ }
+ variant="contained"
+ >
+ {t('list.action')}
+
+
+ >
+ );
+}
diff --git a/002_source/cms/src/app/dashboard/students/xxx/page.tsx b/002_source/cms/src/app/dashboard/students/xxx/page.tsx
new file mode 100644
index 0000000..5362910
--- /dev/null
+++ b/002_source/cms/src/app/dashboard/students/xxx/page.tsx
@@ -0,0 +1,142 @@
+'use client';
+
+import * as React from 'react';
+import RouterLink from 'next/link';
+import { useParams, useRouter } from 'next/navigation';
+import SampleAddressCard from '@/app/dashboard/Sample/AddressCard';
+import { SampleNotifications } from '@/app/dashboard/Sample/Notifications';
+import SamplePaymentCard from '@/app/dashboard/Sample/PaymentCard';
+import SampleSecurityCard from '@/app/dashboard/Sample/SecurityCard';
+
+import Box from '@mui/material/Box';
+import Link from '@mui/material/Link';
+import Stack from '@mui/material/Stack';
+import Grid from '@mui/material/Unstable_Grid2';
+import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
+import type { RecordModel } from 'pocketbase';
+import { useTranslation } from 'react-i18next';
+
+import { config } from '@/config';
+import { paths } from '@/paths';
+import { logger } from '@/lib/default-logger';
+import { pb } from '@/lib/pb';
+import { toast } from '@/components/core/toaster';
+
+import ErrorDisplay from '@/components/dashboard/error';
+
+import { Notifications } from '@/components/dashboard/student/notifications';
+import FormLoading from '@/components/loading';
+import BasicDetailCard from './BasicDetailCard';
+import TitleCard from './TitleCard';
+import { defaultStudent } from '@/components/dashboard/student/_constants';
+import type { Student } from '@/components/dashboard/student/type.d';
+import { COL_STUDENTS } from '@/constants';
+
+export default function Page(): React.JSX.Element {
+ const { t } = useTranslation();
+ const router = useRouter();
+ //
+ const { customerId } = useParams<{ customerId: string }>();
+ //
+ const [showLoading, setShowLoading] = React.useState(true);
+ const [showError, setShowError] = React.useState({ show: false, detail: '' });
+ //
+ const [showLessonCategory, setShowLessonCategory] = React.useState(defaultStudent);
+
+ function handleEditClick(): void {
+ router.push(paths.dashboard.students.edit(showLessonCategory.id));
+ }
+
+ React.useEffect(() => {
+ if (customerId) {
+ pb.collection(COL_STUDENTS)
+ .getOne(customerId)
+ .then((model: RecordModel) => {
+ setShowLessonCategory({ ...defaultStudent, ...model });
+ })
+ .catch((err) => {
+ logger.error(err);
+ toast(t('list.error'));
+
+ setShowError({ show: true, detail: JSON.stringify(err) });
+ })
+ .finally(() => {
+ setShowLoading(false);
+ });
+ }
+ }, [customerId]);
+
+ // return <>{JSON.stringify({ showError, showLessonCategory }, null, 2)}>;
+
+ if (showLoading) return ;
+ if (showError.show)
+ return (
+
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/002_source/cms/src/components/dashboard/layout/config.ts b/002_source/cms/src/components/dashboard/layout/config.ts
index d4abda1..4944931 100644
--- a/002_source/cms/src/components/dashboard/layout/config.ts
+++ b/002_source/cms/src/components/dashboard/layout/config.ts
@@ -136,7 +136,7 @@ export const layoutConfig = {
items: [
{ key: 'students', title: 'List students', href: paths.dashboard.students.list },
{ key: 'students:create', title: 'Create student', href: paths.dashboard.students.create },
- { key: 'students:details', title: 'Student details', href: paths.dashboard.students.details('1') },
+ { key: 'students:details', title: 'Student details', href: paths.dashboard.students.view('1') },
],
},
// {
diff --git a/002_source/cms/src/components/dashboard/layout/horizontal/horizontal-layout.tsx b/002_source/cms/src/components/dashboard/layout/horizontal/horizontal-layout.tsx
index abf559d..21fd50a 100644
--- a/002_source/cms/src/components/dashboard/layout/horizontal/horizontal-layout.tsx
+++ b/002_source/cms/src/components/dashboard/layout/horizontal/horizontal-layout.tsx
@@ -30,7 +30,10 @@ export function HorizontalLayout({ children }: HorizontalLayoutProps): React.JSX
minHeight: '100%',
}}
>
-
+
-
+
void): ColumnDef
@@ -143,7 +143,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef
diff --git a/002_source/cms/src/db/Students/Create.tsx b/002_source/cms/src/db/Students/Create.tsx
new file mode 100644
index 0000000..75c6f4e
--- /dev/null
+++ b/002_source/cms/src/db/Students/Create.tsx
@@ -0,0 +1,11 @@
+// api method for crate student record
+// RULES:
+// TBA
+import { pb } from '@/lib/pb';
+import { COL_STUDENTS } from '@/constants';
+import type { CreateFormProps } from '@/components/dashboard/student/type.d';
+import type { RecordModel } from 'pocketbase';
+
+export async function createStudent(data: CreateFormProps): Promise {
+ return pb.collection(COL_STUDENTS).create(data);
+}
diff --git a/002_source/cms/src/db/Students/Delete.tsx b/002_source/cms/src/db/Students/Delete.tsx
new file mode 100644
index 0000000..a9406ae
--- /dev/null
+++ b/002_source/cms/src/db/Students/Delete.tsx
@@ -0,0 +1,6 @@
+import { pb } from '@/lib/pb';
+import { COL_STUDENTS } from '@/constants';
+
+export async function deleteStudent(id: string): Promise {
+ return pb.collection(COL_STUDENTS).delete(id);
+}
diff --git a/002_source/cms/src/db/Students/GetActiveCount.tsx b/002_source/cms/src/db/Students/GetActiveCount.tsx
new file mode 100644
index 0000000..c36fbca
--- /dev/null
+++ b/002_source/cms/src/db/Students/GetActiveCount.tsx
@@ -0,0 +1,9 @@
+import { COL_STUDENTS } from '@/constants';
+import { pb } from '@/lib/pb';
+
+export default async function GetActiveCount(): Promise {
+ const { totalItems: count } = await pb.collection(COL_STUDENTS).getList(1, 1, {
+ filter: 'status = "active"',
+ });
+ return count;
+}
diff --git a/002_source/cms/src/db/Students/GetAll.tsx b/002_source/cms/src/db/Students/GetAll.tsx
new file mode 100644
index 0000000..1e7c2c1
--- /dev/null
+++ b/002_source/cms/src/db/Students/GetAll.tsx
@@ -0,0 +1,7 @@
+import { pb } from '@/lib/pb';
+import { COL_STUDENTS } from '@/constants';
+import { RecordModel } from 'pocketbase';
+
+export async function getAllStudents(options = {}): Promise {
+ return pb.collection(COL_STUDENTS).getFullList(options);
+}
diff --git a/002_source/cms/src/db/Students/GetAllCount.tsx b/002_source/cms/src/db/Students/GetAllCount.tsx
new file mode 100644
index 0000000..91f84cc
--- /dev/null
+++ b/002_source/cms/src/db/Students/GetAllCount.tsx
@@ -0,0 +1,7 @@
+import { pb } from '@/lib/pb';
+import { COL_STUDENTS } from '@/constants';
+
+export async function getAllStudentsCount(): Promise {
+ const result = await pb.collection(COL_STUDENTS).getList(1, 1);
+ return result.totalItems;
+}
diff --git a/002_source/cms/src/db/Students/GetBlockedCount.tsx b/002_source/cms/src/db/Students/GetBlockedCount.tsx
new file mode 100644
index 0000000..eca70b6
--- /dev/null
+++ b/002_source/cms/src/db/Students/GetBlockedCount.tsx
@@ -0,0 +1,9 @@
+import { COL_STUDENTS } from '@/constants';
+import { pb } from '@/lib/pb';
+
+export default async function GetBlockedCount(): Promise {
+ const { totalItems: count } = await pb.collection(COL_STUDENTS).getList(1, 1, {
+ filter: 'status = "blocked"',
+ });
+ return count;
+}
diff --git a/002_source/cms/src/db/Students/GetById.tsx b/002_source/cms/src/db/Students/GetById.tsx
new file mode 100644
index 0000000..39a12d8
--- /dev/null
+++ b/002_source/cms/src/db/Students/GetById.tsx
@@ -0,0 +1,7 @@
+import { pb } from '@/lib/pb';
+import { COL_STUDENTS } from '@/constants';
+import { RecordModel } from 'pocketbase';
+
+export async function getStudentById(id: string): Promise {
+ return pb.collection(COL_STUDENTS).getOne(id);
+}
diff --git a/002_source/cms/src/db/Students/GetPendingCount.tsx b/002_source/cms/src/db/Students/GetPendingCount.tsx
new file mode 100644
index 0000000..b7f3d7d
--- /dev/null
+++ b/002_source/cms/src/db/Students/GetPendingCount.tsx
@@ -0,0 +1,9 @@
+import { COL_STUDENTS } from '@/constants';
+import { pb } from '@/lib/pb';
+
+export default async function GetPendingCount(): Promise {
+ const { totalItems: count } = await pb.collection(COL_STUDENTS).getList(1, 1, {
+ filter: 'status = "pending"',
+ });
+ return count;
+}
diff --git a/002_source/cms/src/db/Students/Helloworld.tsx b/002_source/cms/src/db/Students/Helloworld.tsx
new file mode 100644
index 0000000..2487997
--- /dev/null
+++ b/002_source/cms/src/db/Students/Helloworld.tsx
@@ -0,0 +1,3 @@
+export function helloCustomer() {
+ return 'Hello from Customers module!';
+}
diff --git a/002_source/cms/src/db/Students/Update.tsx b/002_source/cms/src/db/Students/Update.tsx
new file mode 100644
index 0000000..7beeff7
--- /dev/null
+++ b/002_source/cms/src/db/Students/Update.tsx
@@ -0,0 +1,8 @@
+import { pb } from '@/lib/pb';
+import { COL_CUSTOMERS } from '@/constants';
+import type { RecordModel } from 'pocketbase';
+import type { EditFormProps } from '@/components/dashboard/customer/type.d';
+
+export async function updateCustomer(id: string, data: Partial): Promise {
+ return pb.collection(COL_CUSTOMERS).update(id, data);
+}
diff --git a/002_source/cms/src/db/Students/_GUIDELINES.md b/002_source/cms/src/db/Students/_GUIDELINES.md
new file mode 100644
index 0000000..6515d08
--- /dev/null
+++ b/002_source/cms/src/db/Students/_GUIDELINES.md
@@ -0,0 +1,31 @@
+# GUIDELINES
+
+This folder contains drivers for `Customer`/`Customers` records using PocketBase:
+
+- create (Create.tsx)
+- read (GetById.tsx)
+- write (Update.tsx)
+- count (GetAllCount.tsx, GetActiveCount.tsx, GetBlockedCount.tsx, GetPendingCount.tsx)
+- misc (Helloworld.tsx)
+- delete (Delete.tsx)
+- list (GetAll.tsx)
+
+the `@` sign refer to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src`
+
+## Assumption and Requirements
+
+- assume `pb` is located in `@/lib/pb`
+- no need to handle error in this function, i'll handle it in the caller
+- type information defined in `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/Customers/type.d.tsx`
+
+simple template:
+
+```typescript
+import { pb } from '@/lib/pb';
+import { COL_CUSTOMERS } from '@/constants';
+
+export async function createCustomer(data: CreateFormProps) {
+ // ...content
+ // use direct return of pb.collection (e.g. return pb.collection(xxx))
+}
+```
diff --git a/002_source/cms/src/paths.ts b/002_source/cms/src/paths.ts
index 7e51347..d77ca2b 100644
--- a/002_source/cms/src/paths.ts
+++ b/002_source/cms/src/paths.ts
@@ -133,7 +133,7 @@ export const paths = {
students: {
list: '/dashboard/students',
create: '/dashboard/students/create',
- details: (id: string) => `/dashboard/students/${id}`,
+ view: (id: string) => `/dashboard/students/view/${id}`,
edit: (id: string) => `/dashboard/students/edit/${id}`,
},
customers: {