From 367e58a8cf1b4facd4135e3f40c5196531312b97 Mon Sep 17 00:00:00 2001 From: louiscklaw Date: Tue, 6 May 2025 17:45:25 +0800 Subject: [PATCH] update working on cms users page, --- .../app/dashboard/users/SampleCustomers.tsx | 157 +++++++++++++ .../src/app/dashboard/users/_GUIDELINES.md | 49 ++++ .../src/app/dashboard/users/create/page.tsx | 48 ++++ .../users/edit/[customerId]/_PROMPT.md | 11 + .../users/edit/[customerId]/page.tsx | 54 +++++ .../cms/src/app/dashboard/users/list/page.tsx | 209 ++++++++++++++++++ .../users/mail/[labelId]/[threadId]/page.tsx | 17 ++ .../dashboard/users/mail/[labelId]/layout.tsx | 142 ++++++++++++ .../dashboard/users/mail/[labelId]/page.tsx | 11 + .../users/view/[id]/BasicDetailCard.tsx | 80 +++++++ .../dashboard/users/view/[id]/TitleCard.tsx | 76 +++++++ .../app/dashboard/users/view/[id]/page.tsx | 144 ++++++++++++ .../dashboard/users/xxx/BasicDetailCard.tsx | 80 +++++++ .../src/app/dashboard/users/xxx/TitleCard.tsx | 76 +++++++ .../cms/src/app/dashboard/users/xxx/page.tsx | 142 ++++++++++++ 15 files changed, 1296 insertions(+) create mode 100644 002_source/cms/src/app/dashboard/users/SampleCustomers.tsx create mode 100644 002_source/cms/src/app/dashboard/users/_GUIDELINES.md create mode 100644 002_source/cms/src/app/dashboard/users/create/page.tsx create mode 100644 002_source/cms/src/app/dashboard/users/edit/[customerId]/_PROMPT.md create mode 100644 002_source/cms/src/app/dashboard/users/edit/[customerId]/page.tsx create mode 100644 002_source/cms/src/app/dashboard/users/list/page.tsx create mode 100644 002_source/cms/src/app/dashboard/users/mail/[labelId]/[threadId]/page.tsx create mode 100644 002_source/cms/src/app/dashboard/users/mail/[labelId]/layout.tsx create mode 100644 002_source/cms/src/app/dashboard/users/mail/[labelId]/page.tsx create mode 100644 002_source/cms/src/app/dashboard/users/view/[id]/BasicDetailCard.tsx create mode 100644 002_source/cms/src/app/dashboard/users/view/[id]/TitleCard.tsx create mode 100644 002_source/cms/src/app/dashboard/users/view/[id]/page.tsx create mode 100644 002_source/cms/src/app/dashboard/users/xxx/BasicDetailCard.tsx create mode 100644 002_source/cms/src/app/dashboard/users/xxx/TitleCard.tsx create mode 100644 002_source/cms/src/app/dashboard/users/xxx/page.tsx diff --git a/002_source/cms/src/app/dashboard/users/SampleCustomers.tsx b/002_source/cms/src/app/dashboard/users/SampleCustomers.tsx new file mode 100644 index 0000000..b991279 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/SampleCustomers.tsx @@ -0,0 +1,157 @@ +// src/app/dashboard/customers/page.tsx +'use client'; +import type { Customer } from '@/components/dashboard/customer/type.d'; +import { dayjs } from '@/lib/dayjs'; + +export const SampleCustomers = [ + { + 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(), + }, + { + 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[]; diff --git a/002_source/cms/src/app/dashboard/users/_GUIDELINES.md b/002_source/cms/src/app/dashboard/users/_GUIDELINES.md new file mode 100644 index 0000000..1353901 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/_GUIDELINES.md @@ -0,0 +1,49 @@ +# GUIDELINES + +this folder is part of nextjs typescript project and containing page definition for `Customer` / `Customers` record: + +- list (./page.tsx) +- view (./[customerId]/page.tsx) +- create (./create/page.tsx) +- edit (./[customerId]/page.tsx) +- translation provided by react-i18next + +the `@` sign refer to `/002_source/002_source/cms/src` + +## Assumption and Requirements + +- let one file contains one component only. +- type information defined in `/002_source/cms/src/db/Customers/type.d.tsx` +- it mainly consume the db drivers `Customres` in `/002_source/cms/src/db/Customers` + +simple template: + +```typescript +// src/app/dashboard/customers/page.tsx +'use client'; + +// RULES: +// contains list page for customers (Customers) +// contain definition to collection only +// +import statements here ... +... +... +... + +export default function Page({ searchParams }: PageProps): React.JSX.Element { + // ...content + // use direct return of pb.collection (e.g. return pb.collection(xxx)) + + return ( + <> + {* page content *} + + ) +} + + +interface PageProps { + searchParams: { email?: string; phone?: string; sortDir?: 'asc' | 'desc'; status?: string }; +} +``` diff --git a/002_source/cms/src/app/dashboard/users/create/page.tsx b/002_source/cms/src/app/dashboard/users/create/page.tsx new file mode 100644 index 0000000..98b2ec9 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/create/page.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import type { Metadata } from 'next'; +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 { config } from '@/config'; +import { paths } from '@/paths'; +import { CustomerCreateForm } from '@/components/dashboard/student/student-create-form'; + +export const metadata = { title: `Create | Customers | Dashboard | ${config.site.name}` } satisfies Metadata; + +export default function Page(): React.JSX.Element { + return ( + + + +
+ + + Customers + +
+
+ Create customer +
+
+ +
+
+ ); +} diff --git a/002_source/cms/src/app/dashboard/users/edit/[customerId]/_PROMPT.md b/002_source/cms/src/app/dashboard/users/edit/[customerId]/_PROMPT.md new file mode 100644 index 0000000..abf4465 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/edit/[customerId]/_PROMPT.md @@ -0,0 +1,11 @@ +# task + +## instruction + +with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/_helloworld/page.tsx` + +with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_types/edit/[typeId]/page.tsx` + +please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_categories/edit/page.tsx` + +please draft a tsx for showing error to user thanks, diff --git a/002_source/cms/src/app/dashboard/users/edit/[customerId]/page.tsx b/002_source/cms/src/app/dashboard/users/edit/[customerId]/page.tsx new file mode 100644 index 0000000..8b623fd --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/edit/[customerId]/page.tsx @@ -0,0 +1,54 @@ +'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 { CrCategoryEditForm } from '@/components/dashboard/cr/categories/cr-category-edit-form'; +import { StudentEditForm } from '@/components/dashboard/student/student-edit-form'; + +export default function Page(): React.JSX.Element { + const { t } = useTranslation(['lp_categories']); + + React.useEffect(() => { + // console.log('helloworld'); + }, []); + + return ( + + + +
+ + + {t('edit.title')} + +
+
+ {t('edit.title')} +
+
+ +
+
+ ); +} diff --git a/002_source/cms/src/app/dashboard/users/list/page.tsx b/002_source/cms/src/app/dashboard/users/list/page.tsx new file mode 100644 index 0000000..c507416 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/list/page.tsx @@ -0,0 +1,209 @@ +// src/app/dashboard/customers/page.tsx +'use client'; + +// RULES: +// contains list page for customers (Customers) +// contain definition to collection only +// +import * as React from 'react'; +import { useRouter } from 'next/navigation'; +import { COL_USERS } from '@/constants'; +import { LoadingButton } from '@mui/lab'; +import Box from '@mui/material/Box'; +import Card from '@mui/material/Card'; +import Divider from '@mui/material/Divider'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus'; +import type { ListResult, RecordModel } from 'pocketbase'; + +import { UsersFilters } from '@/components/dashboard/user/users-filters'; +import { UsersPagination } from '@/components/dashboard/user/users-pagination'; +import { UsersSelectionProvider } from '@/components/dashboard/user/users-selection-context'; +import { UsersTable } from '@/components/dashboard/user/users-table'; +import type { User } from '@/components/dashboard/user/type.d'; +import { useTranslation } from 'react-i18next'; + +import { paths } from '@/paths'; +import isDevelopment from '@/lib/check-is-development'; +import { logger } from '@/lib/default-logger'; +import { pb } from '@/lib/pb'; +import ErrorDisplay from '@/components/dashboard/error'; +import { defaultUser } from '@/components/dashboard/user/_constants'; +import FormLoading from '@/components/loading'; + +export default function Page({ searchParams }: PageProps): React.JSX.Element { + const { t } = useTranslation(['customers']); + const router = useRouter(); + + const { email, phone, sortDir, status } = searchParams; + + const [lessonCategoriesData, setLessonCategoriesData] = React.useState([]); + // + + const [isLoadingAddPage, setIsLoadingAddPage] = React.useState(false); + const [showLoading, setShowLoading] = React.useState(true); + const [showError, setShowError] = React.useState({ show: false, detail: '' }); + // + const [rowsPerPage, setRowsPerPage] = React.useState(5); + // + const [f, setF] = React.useState([]); + const [currentPage, setCurrentPage] = React.useState(0); + // + const [recordCount, setRecordCount] = React.useState(0); + const [listOption, setListOption] = React.useState({}); + + // + const reloadRows = async (): Promise => { + try { + const models: ListResult = await pb.collection(COL_USERS).getList(currentPage + 1, rowsPerPage, {}); + const { items, totalItems } = models; + + const tempLessonTypes: User[] = items.map((lt) => { + return { ...defaultUser, ...lt }; + }); + + setLessonCategoriesData(tempLessonTypes); + setRecordCount(totalItems); + setF(tempLessonTypes); + } catch (error) { + // + logger.error(error); + setShowError({ + // + show: true, + detail: JSON.stringify(error, null, 2), + }); + } finally { + setShowLoading(false); + } + }; + + const [lastListOption, setLastListOption] = React.useState({}); + const isFirstRun = React.useRef(false); + React.useEffect(() => { + if (!isFirstRun.current) { + isFirstRun.current = true; + } else if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) { + // reset page number as tab changes + setLastListOption(listOption); + setCurrentPage(0); + void reloadRows(); + } else { + void reloadRows(); + } + }, [currentPage, rowsPerPage, listOption]); + + React.useEffect(() => { + const tempFilter = []; + let tempSortDir = ''; + + if (status) { + tempFilter.push(`status = "${status}"`); + } + + if (sortDir) { + tempSortDir = `-created`; + } + + if (email) { + tempFilter.push(`email ~ "%${email}%"`); + } + if (phone) { + tempFilter.push(`phone ~ "%${phone}%"`); + } + + let preFinalListOption = {}; + if (tempFilter.length > 0) { + preFinalListOption = { filter: tempFilter.join(' && ') }; + } + if (tempSortDir.length > 0) { + preFinalListOption = { ...preFinalListOption, sort: tempSortDir }; + } + setListOption(preFinalListOption); + }, [sortDir, email, phone, status]); + + if (showLoading) return ; + + if (showError.show) + return ( + + ); + + return ( + + + + + {t('list.title')} + + + { + setIsLoadingAddPage(true); + router.push(paths.dashboard.users.create); + }} + startIcon={} + variant="contained" + > + {t('list.add')} + + + + + + + + + + + + + + + + +
{JSON.stringify(f, null, 2)}
+
+
+ ); +} + +interface PageProps { + searchParams: { + email?: string; + phone?: string; + sortDir?: 'asc' | 'desc'; + status?: string; + // + }; +} diff --git a/002_source/cms/src/app/dashboard/users/mail/[labelId]/[threadId]/page.tsx b/002_source/cms/src/app/dashboard/users/mail/[labelId]/[threadId]/page.tsx new file mode 100644 index 0000000..cf93176 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/mail/[labelId]/[threadId]/page.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import type { Metadata } from 'next'; + +import { config } from '@/config'; +import { ThreadView } from '@/components/dashboard/mail/thread-view'; + +export const metadata = { title: `Thread | Mail | Dashboard | ${config.site.name}` } satisfies Metadata; + +interface PageProps { + params: { threadId: string }; +} + +export default function Page({ params }: PageProps): React.JSX.Element { + const { threadId } = params; + + return ; +} diff --git a/002_source/cms/src/app/dashboard/users/mail/[labelId]/layout.tsx b/002_source/cms/src/app/dashboard/users/mail/[labelId]/layout.tsx new file mode 100644 index 0000000..cdde2e8 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/mail/[labelId]/layout.tsx @@ -0,0 +1,142 @@ +import * as React from 'react'; + +import { dayjs } from '@/lib/dayjs'; +import { MailProvider } from '@/components/dashboard/mail/mail-context'; +import { MailView } from '@/components/dashboard/mail/mail-view'; +import type { Label, Thread } from '@/components/dashboard/mail/types'; + +function filterThreads(threads: Thread[], labelId: string): Thread[] { + return threads.filter((thread) => { + if (['inbox', 'sent', 'drafts', 'spam', 'trash'].includes(labelId)) { + return thread.folder === labelId; + } + + if (labelId === 'important') { + return thread.isImportant; + } + + if (labelId === 'starred') { + return thread.isStarred; + } + + if (thread.labels.includes(labelId)) { + return true; + } + + return false; + }); +} + +const labels = [ + { id: 'inbox', type: 'system', name: 'Inbox', unreadCount: 1, totalCount: 0 }, + { id: 'sent', type: 'system', name: 'Sent', unreadCount: 0, totalCount: 0 }, + { id: 'drafts', type: 'system', name: 'Drafts', unreadCount: 0, totalCount: 0 }, + { id: 'spam', type: 'system', name: 'Spam', unreadCount: 0, totalCount: 0 }, + { id: 'trash', type: 'system', name: 'Trash', unreadCount: 0, totalCount: 1 }, + { id: 'important', type: 'system', name: 'Important', unreadCount: 0, totalCount: 1 }, + { id: 'starred', type: 'system', name: 'Starred', unreadCount: 1, totalCount: 1 }, + { id: 'work', type: 'custom', name: 'Work', color: '#43A048', unreadCount: 0, totalCount: 1 }, + { id: 'business', type: 'custom', name: 'Business', color: '#1E88E5', unreadCount: 1, totalCount: 2 }, + { id: 'personal', type: 'custom', name: 'Personal', color: '#FB8A00', unreadCount: 0, totalCount: 1 }, +] satisfies Label[]; + +const threads = [ + { + id: 'TRD-004', + from: { avatar: '/assets/avatar-9.png', email: 'marcus.finn@domain.com', name: 'Marcus Finn' }, + to: [{ avatar: '/assets/avatar.png', email: 'sofia@devias.io', name: 'Sofia Rivers' }], + subject: 'Website redesign. Interested in collaboration', + message: `Hey there, + +I hope this email finds you well. I'm glad you liked my projects, and I would be happy to provide you with a quote for a similar project. + +Please let me know your requirements and any specific details you have in mind, so I can give you an accurate quote. + +Looking forward to hearing from you soon. + +Best regards, + +Marcus Finn`, + attachments: [ + { + id: 'ATT-001', + name: 'working-sketch.png', + size: '128.5 KB', + type: 'image', + url: '/assets/image-abstract-1.png', + }, + { id: 'ATT-002', name: 'summer-customers.pdf', size: '782.3 KB', type: 'file', url: '#' }, + { + id: 'ATT-003', + name: 'desktop-coffee.png', + size: '568.2 KB', + type: 'image', + url: '/assets/image-minimal-1.png', + }, + ], + folder: 'inbox', + labels: ['work', 'business'], + isImportant: true, + isStarred: false, + isUnread: true, + createdAt: dayjs().subtract(3, 'hour').toDate(), + }, + { + id: 'TRD-003', + to: [{ name: 'Sofia Rivers', avatar: '/assets/avatar.png', email: 'sofia@devias.io' }], + from: { name: 'Miron Vitold', avatar: '/assets/avatar-1.png', email: 'miron.vitold@domain.com' }, + subject: 'Amazing work', + message: `Hey, nice projects! I really liked the one in react. What's your quote on kinda similar project?`, + folder: 'spam', + labels: [], + isImportant: false, + isStarred: true, + isUnread: false, + createdAt: dayjs().subtract(1, 'day').toDate(), + }, + { + id: 'TRD-002', + from: { name: 'Penjani Inyene', avatar: '/assets/avatar-4.png', email: 'penjani.inyene@domain.com' }, + to: [{ name: 'Sofia Rivers', avatar: '/assets/avatar.png', email: 'sofia@devias.io' }], + subject: 'Flight reminder', + message: `Dear Sofia, + +Your flight is coming up soon. Please don't forget to check in for your scheduled flight.`, + folder: 'inbox', + labels: ['business'], + isImportant: false, + isStarred: false, + isUnread: false, + createdAt: dayjs().subtract(2, 'day').toDate(), + }, + { + id: 'TRD-001', + from: { name: 'Carson Darrin', avatar: '/assets/avatar-3.png', email: 'carson.darrin@domain.com' }, + to: [{ name: 'Sofia Rivers', avatar: '/assets/avatar.png', email: 'sofia@devias.io' }], + subject: 'Possible candidates for the position', + message: `My market leading client has another fantastic opportunity for an experienced Software Developer to join them on a heavily remote basis`, + folder: 'trash', + labels: ['personal'], + isImportant: false, + isStarred: false, + isUnread: true, + createdAt: dayjs().subtract(2, 'day').toDate(), + }, +] satisfies Thread[]; + +interface LayoutProps { + children: React.ReactNode; + params: { labelId: string }; +} + +export default function Layout({ children, params }: LayoutProps): React.JSX.Element { + const { labelId } = params; + + const filteredThreads = filterThreads(threads, labelId); + + return ( + + {children} + + ); +} diff --git a/002_source/cms/src/app/dashboard/users/mail/[labelId]/page.tsx b/002_source/cms/src/app/dashboard/users/mail/[labelId]/page.tsx new file mode 100644 index 0000000..9989b26 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/mail/[labelId]/page.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; +import type { Metadata } from 'next'; + +import { config } from '@/config'; +import { ThreadsView } from '@/components/dashboard/mail/threads-view'; + +export const metadata = { title: `Mail | Dashboard | ${config.site.name}` } satisfies Metadata; + +export default function Page(): React.JSX.Element { + return ; +} diff --git a/002_source/cms/src/app/dashboard/users/view/[id]/BasicDetailCard.tsx b/002_source/cms/src/app/dashboard/users/view/[id]/BasicDetailCard.tsx new file mode 100644 index 0000000..1d3ed59 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/view/[id]/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 { User } from '@/components/dashboard/user/type.d'; + +export default function BasicDetailCard({ + user: model, + handleEditClick, +}: { + user: User; + 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/users/view/[id]/TitleCard.tsx b/002_source/cms/src/app/dashboard/users/view/[id]/TitleCard.tsx new file mode 100644 index 0000000..5f59495 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/view/[id]/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({ user: lpModel }: { user: Student }): React.JSX.Element { + const { t } = useTranslation(); + + return ( + <> + + + {t('empty')} + +
+ + {lpModel.email} + + } + label={lpModel.quota} + size="small" + variant="outlined" + /> + + + {lpModel.status} + +
+
+
+ +
+ + ); +} diff --git a/002_source/cms/src/app/dashboard/users/view/[id]/page.tsx b/002_source/cms/src/app/dashboard/users/view/[id]/page.tsx new file mode 100644 index 0000000..669ca23 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/view/[id]/page.tsx @@ -0,0 +1,144 @@ +'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/user/notifications'; +import FormLoading from '@/components/loading'; +import BasicDetailCard from './BasicDetailCard'; +import TitleCard from './TitleCard'; +import { defaultUser } from '@/components/dashboard/user/_constants'; +import type { User } from '@/components/dashboard/user/type.d'; +import { COL_USERS } from '@/constants'; + +export default function Page(): React.JSX.Element { + const { t } = useTranslation(); + const router = useRouter(); + // + const { id: userId } = useParams<{ id: string }>(); + // + const [showLoading, setShowLoading] = React.useState(true); + const [showError, setShowError] = React.useState({ show: false, detail: '' }); + // + const [showUser, setShowUser] = React.useState(defaultUser); + + function handleEditClick(): void { + router.push(paths.dashboard.users.edit(showUser.id)); + } + + React.useEffect(() => { + if (userId) { + pb.collection(COL_USERS) + .getOne(userId) + .then((model: RecordModel) => { + setShowUser({ ...defaultUser, ...model }); + }) + .catch((err) => { + logger.error(err); + toast(t('list.error')); + + setShowError({ show: true, detail: JSON.stringify(err) }); + }) + .finally(() => { + setShowLoading(false); + }); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [userId]); + + // return <>{JSON.stringify({ showError, showLessonCategory }, null, 2)}; + + if (showLoading) return ; + if (showError.show) + return ( + + ); + + return ( + + + +
+ + + Users + +
+ + + +
+ + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/002_source/cms/src/app/dashboard/users/xxx/BasicDetailCard.tsx b/002_source/cms/src/app/dashboard/users/xxx/BasicDetailCard.tsx new file mode 100644 index 0000000..e64427e --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/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/users/xxx/TitleCard.tsx b/002_source/cms/src/app/dashboard/users/xxx/TitleCard.tsx new file mode 100644 index 0000000..9be2807 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/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} + +
+
+
+ +
+ + ); +} diff --git a/002_source/cms/src/app/dashboard/users/xxx/page.tsx b/002_source/cms/src/app/dashboard/users/xxx/page.tsx new file mode 100644 index 0000000..5362910 --- /dev/null +++ b/002_source/cms/src/app/dashboard/users/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 ( + + + +
+ + + Students + +
+ + + +
+ + + + + + + + + + + + + + + +
+
+ ); +}