diff --git a/002_source/cms/src/components/dashboard/layout/notifications-popover.tsx b/002_source/cms/src/components/dashboard/layout/notifications-popover/index.tsx similarity index 58% rename from 002_source/cms/src/components/dashboard/layout/notifications-popover.tsx rename to 002_source/cms/src/components/dashboard/layout/notifications-popover/index.tsx index b5c6153..0f35fdc 100644 --- a/002_source/cms/src/components/dashboard/layout/notifications-popover.tsx +++ b/002_source/cms/src/components/dashboard/layout/notifications-popover/index.tsx @@ -18,46 +18,14 @@ import { X as XIcon } from '@phosphor-icons/react/dist/ssr/X'; import { useTranslation } from 'react-i18next'; import { dayjs } from '@/lib/dayjs'; - -export type Notification = { id: string; createdAt: Date; read: boolean } & ( - | { type: 'new_feature'; description: string } - | { type: 'new_company'; author: { name: string; avatar?: string }; company: { name: string } } - | { type: 'new_job'; author: { name: string; avatar?: string }; job: { title: string } } -); - -const notifications = [ - { - id: 'EV-004', - createdAt: dayjs().subtract(7, 'minute').subtract(5, 'hour').subtract(1, 'day').toDate(), - read: false, - type: 'new_job', - author: { name: 'Jie Yan', avatar: '/assets/avatar-8.png' }, - job: { title: 'Remote React / React Native Developer' }, - }, - { - id: 'EV-003', - createdAt: dayjs().subtract(18, 'minute').subtract(3, 'hour').subtract(5, 'day').toDate(), - read: true, - type: 'new_job', - author: { name: 'Fran Perez', avatar: '/assets/avatar-5.png' }, - job: { title: 'Senior Golang Backend Engineer' }, - }, - { - id: 'EV-002', - createdAt: dayjs().subtract(4, 'minute').subtract(5, 'hour').subtract(7, 'day').toDate(), - read: true, - type: 'new_feature', - description: 'Logistics management is now available', - }, - { - id: 'EV-001', - createdAt: dayjs().subtract(7, 'minute').subtract(8, 'hour').subtract(7, 'day').toDate(), - read: true, - type: 'new_company', - author: { name: 'Jie Yan', avatar: '/assets/avatar-8.png' }, - company: { name: 'Stripe' }, - }, -] satisfies Notification[]; +// import type { Notification } from './type.d.tsx.del'; +import { SampleNotifications } from './sample-notifications'; +import { useHelloworld } from '@/hooks/use-helloworld'; +import { getAllNotifications } from '@/db/Notifications/GetAll'; +import { ListResult, RecordModel } from 'pocketbase'; +import { defaultNotification } from '@/db/Notifications/constants'; +import { getNotificationsByUserId } from '@/db/Notifications/GetNotificationByUserId'; +import { Notification } from '@/db/Notifications/type'; export interface NotificationsPopoverProps { anchorEl: null | Element; @@ -75,6 +43,38 @@ export function NotificationsPopover({ open = false, }: NotificationsPopoverProps): React.JSX.Element { const { t } = useTranslation(); + const [notiList, setNotiList] = React.useState([]); + + const [loading, setLoading] = React.useState(false); + const [error, setError] = React.useState(null); + + const { data, handleClose, handleOpen, open: testOpen } = useHelloworld(); + React.useEffect(() => { + setLoading(true); + async function LoadAllNotifications() { + const notiList: Notification[] = await getNotificationsByUserId('1'); + setNotiList(notiList); + } + setLoading(false); + void LoadAllNotifications(); + }, []); + + if (loading) return <>Loading; + if (error) return <>Error; + if (notiList.length == 0) + return ( + + list is empty + + ); + return ( - + {t('Notifications')} - + - {notifications.length === 0 ? ( + {notiList.length === 0 ? ( {t('There are no notifications')} ) : ( - {notifications.map((notification, index) => ( + {notiList.map((notification, index) => ( { @@ -124,10 +131,17 @@ interface NotificationItemProps { function NotificationItem({ divider, notification, onRemove }: NotificationItemProps): React.JSX.Element { return ( - + - + @@ -142,14 +156,21 @@ interface NotificationContentProps { function NotificationContent({ notification }: NotificationContentProps): React.JSX.Element { if (notification.type === 'new_feature') { return ( - +
New feature! {notification.description} - + {dayjs(notification.createdAt).format('MMM D, hh:mm A')}
@@ -159,22 +180,35 @@ function NotificationContent({ notification }: NotificationContentProps): React. if (notification.type === 'new_company') { return ( - +
- + {notification.author.name} {' '} created{' '} - + {notification.company.name} {' '} company - + {dayjs(notification.createdAt).format('MMM D, hh:mm A')}
@@ -184,21 +218,34 @@ function NotificationContent({ notification }: NotificationContentProps): React. if (notification.type === 'new_job') { return ( - +
- + {notification.author.name} {' '} added a new job{' '} - + {notification.job.title} - + {dayjs(notification.createdAt).format('MMM D, hh:mm A')}
diff --git a/002_source/cms/src/components/dashboard/layout/notifications-popover/sample-notifications.tsx b/002_source/cms/src/components/dashboard/layout/notifications-popover/sample-notifications.tsx new file mode 100644 index 0000000..1259e17 --- /dev/null +++ b/002_source/cms/src/components/dashboard/layout/notifications-popover/sample-notifications.tsx @@ -0,0 +1,37 @@ +'use client'; +import { dayjs } from '@/lib/dayjs'; +import type { Notification } from './type.d.tsx.del'; + +export const SampleNotifications = [ + { + id: 'EV-004', + createdAt: dayjs().subtract(7, 'minute').subtract(5, 'hour').subtract(1, 'day').toDate(), + read: false, + type: 'new_job', + author: { name: 'Jie Yan', avatar: '/assets/avatar-8.png' }, + job: { title: 'Remote React / React Native Developer' }, + }, + { + id: 'EV-003', + createdAt: dayjs().subtract(18, 'minute').subtract(3, 'hour').subtract(5, 'day').toDate(), + read: true, + type: 'new_job', + author: { name: 'Fran Perez', avatar: '/assets/avatar-5.png' }, + job: { title: 'Senior Golang Backend Engineer' }, + }, + { + id: 'EV-002', + createdAt: dayjs().subtract(4, 'minute').subtract(5, 'hour').subtract(7, 'day').toDate(), + read: true, + type: 'new_feature', + description: 'Logistics management is now available', + }, + { + id: 'EV-001', + createdAt: dayjs().subtract(7, 'minute').subtract(8, 'hour').subtract(7, 'day').toDate(), + read: true, + type: 'new_company', + author: { name: 'Jie Yan', avatar: '/assets/avatar-8.png' }, + company: { name: 'Stripe' }, + }, +] satisfies Notification[]; diff --git a/002_source/cms/src/components/dashboard/notification/type.d.ts b/002_source/cms/src/components/dashboard/notification/type.d.ts new file mode 100644 index 0000000..a5fd6ea --- /dev/null +++ b/002_source/cms/src/components/dashboard/notification/type.d.ts @@ -0,0 +1,12 @@ +export interface NotificationFormProps { + id?: string; + title: string; + message: string; + isRead: boolean; + createdAt: Date; + updatedAt: Date; +} + +export interface NotificationItem extends NotificationFormProps { + id: string; +} diff --git a/002_source/cms/src/db/Notifications/Create.tsx b/002_source/cms/src/db/Notifications/Create.tsx new file mode 100644 index 0000000..b3da625 --- /dev/null +++ b/002_source/cms/src/db/Notifications/Create.tsx @@ -0,0 +1,11 @@ +// api method for create notification record +// RULES: +// TBA +import { pb } from '@/lib/pb'; +import { COL_NOTIFICATIONS } from '@/constants'; +import type { NotificationFormProps } from '@/components/dashboard/notification/type.d'; +import type { RecordModel } from 'pocketbase'; + +export async function createNotification(data: NotificationFormProps): Promise { + return pb.collection(COL_NOTIFICATIONS).create(data); +} diff --git a/002_source/cms/src/db/Notifications/Delete.tsx b/002_source/cms/src/db/Notifications/Delete.tsx new file mode 100644 index 0000000..8e4fbee --- /dev/null +++ b/002_source/cms/src/db/Notifications/Delete.tsx @@ -0,0 +1,9 @@ +// api method for delete notification record +// RULES: +// TBA +import { pb } from '@/lib/pb'; +import { COL_NOTIFICATIONS } from '@/constants'; + +export async function deleteNotification(id: string): Promise { + return pb.collection(COL_NOTIFICATIONS).delete(id); +} diff --git a/002_source/cms/src/db/Notifications/GetActiveCount.tsx b/002_source/cms/src/db/Notifications/GetActiveCount.tsx new file mode 100644 index 0000000..a8679de --- /dev/null +++ b/002_source/cms/src/db/Notifications/GetActiveCount.tsx @@ -0,0 +1,9 @@ +import { COL_CUSTOMERS } from '@/constants'; +import { pb } from '@/lib/pb'; + +export default async function GetActiveCount(): Promise { + const { totalItems: count } = await pb.collection(COL_CUSTOMERS).getList(1, 1, { + filter: 'status = "active"', + }); + return count; +} diff --git a/002_source/cms/src/db/Notifications/GetAll.tsx b/002_source/cms/src/db/Notifications/GetAll.tsx new file mode 100644 index 0000000..f43121c --- /dev/null +++ b/002_source/cms/src/db/Notifications/GetAll.tsx @@ -0,0 +1,10 @@ +// api method for get all notification records +// RULES: +// TBA +import { pb } from '@/lib/pb'; +import { COL_NOTIFICATIONS } from '@/constants'; +import { RecordModel } from 'pocketbase'; + +export async function getAllNotifications(options = {}): Promise { + return pb.collection(COL_NOTIFICATIONS).getFullList(options); +} diff --git a/002_source/cms/src/db/Notifications/GetAllCount.tsx b/002_source/cms/src/db/Notifications/GetAllCount.tsx new file mode 100644 index 0000000..a10b7a3 --- /dev/null +++ b/002_source/cms/src/db/Notifications/GetAllCount.tsx @@ -0,0 +1,7 @@ +import { pb } from '@/lib/pb'; +import { COL_CUSTOMERS } from '@/constants'; + +export async function getAllCustomersCount(): Promise { + const result = await pb.collection(COL_CUSTOMERS).getList(1, 1); + return result.totalItems; +} diff --git a/002_source/cms/src/db/Notifications/GetBlockedCount.tsx b/002_source/cms/src/db/Notifications/GetBlockedCount.tsx new file mode 100644 index 0000000..261321c --- /dev/null +++ b/002_source/cms/src/db/Notifications/GetBlockedCount.tsx @@ -0,0 +1,9 @@ +import { COL_CUSTOMERS } from '@/constants'; +import { pb } from '@/lib/pb'; + +export default async function GetBlockedCount(): Promise { + const { totalItems: count } = await pb.collection(COL_CUSTOMERS).getList(1, 1, { + filter: 'status = "blocked"', + }); + return count; +} diff --git a/002_source/cms/src/db/Notifications/GetById.tsx b/002_source/cms/src/db/Notifications/GetById.tsx new file mode 100644 index 0000000..ed3f66b --- /dev/null +++ b/002_source/cms/src/db/Notifications/GetById.tsx @@ -0,0 +1,10 @@ +// api method for get notification by id +// RULES: +// TBA +import { pb } from '@/lib/pb'; +import { COL_NOTIFICATIONS } from '@/constants'; +import { RecordModel } from 'pocketbase'; + +export async function getNotificationById(id: string): Promise { + return pb.collection(COL_NOTIFICATIONS).getOne(id); +} diff --git a/002_source/cms/src/db/Notifications/GetNotificationByUserId.tsx b/002_source/cms/src/db/Notifications/GetNotificationByUserId.tsx new file mode 100644 index 0000000..1a69f8f --- /dev/null +++ b/002_source/cms/src/db/Notifications/GetNotificationByUserId.tsx @@ -0,0 +1,12 @@ +// api method for get notifications by user id +import { pb } from '@/lib/pb'; +import { COL_NOTIFICATIONS } from '@/constants'; +import type { Notification } from './type.d'; + +export async function getNotificationsByUserId(userId: string): Promise { + const records = await pb.collection(COL_NOTIFICATIONS).getFullList({ + filter: `author.id = "000000000000001"`, + sort: '-created', + }); + return records as unknown as Notification[]; +} diff --git a/002_source/cms/src/db/Notifications/GetPendingCount.tsx b/002_source/cms/src/db/Notifications/GetPendingCount.tsx new file mode 100644 index 0000000..d6661ca --- /dev/null +++ b/002_source/cms/src/db/Notifications/GetPendingCount.tsx @@ -0,0 +1,9 @@ +import { COL_CUSTOMERS } from '@/constants'; +import { pb } from '@/lib/pb'; + +export default async function GetPendingCount(): Promise { + const { totalItems: count } = await pb.collection(COL_CUSTOMERS).getList(1, 1, { + filter: 'status = "pending"', + }); + return count; +} diff --git a/002_source/cms/src/db/Notifications/Helloworld.tsx b/002_source/cms/src/db/Notifications/Helloworld.tsx new file mode 100644 index 0000000..2487997 --- /dev/null +++ b/002_source/cms/src/db/Notifications/Helloworld.tsx @@ -0,0 +1,3 @@ +export function helloCustomer() { + return 'Hello from Customers module!'; +} diff --git a/002_source/cms/src/db/Notifications/Update.tsx b/002_source/cms/src/db/Notifications/Update.tsx new file mode 100644 index 0000000..f9ef55c --- /dev/null +++ b/002_source/cms/src/db/Notifications/Update.tsx @@ -0,0 +1,11 @@ +// api method for update notification record +// RULES: +// TBA +import { pb } from '@/lib/pb'; +import { COL_NOTIFICATIONS } from '@/constants'; +import type { RecordModel } from 'pocketbase'; +import type { NotificationFormProps } from '@/components/dashboard/notification/type.d'; + +export async function updateNotification(id: string, data: Partial): Promise { + return pb.collection(COL_NOTIFICATIONS).update(id, data); +} diff --git a/002_source/cms/src/db/Notifications/_GUIDELINES.md b/002_source/cms/src/db/Notifications/_GUIDELINES.md new file mode 100644 index 0000000..6515d08 --- /dev/null +++ b/002_source/cms/src/db/Notifications/_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/db/Notifications/constants.ts b/002_source/cms/src/db/Notifications/constants.ts new file mode 100644 index 0000000..7ee4500 --- /dev/null +++ b/002_source/cms/src/db/Notifications/constants.ts @@ -0,0 +1,19 @@ +// Default and empty values for Notification + +import type { Notification } from './type'; + +export const defaultNotification: Notification = { + id: '', + read: false, + type: '', + author: {}, + job: {}, + description: '', + NOTI_ID: '', + created: '', + updated: '', +}; + +export const emptyNotification: Notification = { + ...defaultNotification, +}; diff --git a/002_source/cms/src/db/Notifications/type.d.ts b/002_source/cms/src/db/Notifications/type.d.ts new file mode 100644 index 0000000..ae7910e --- /dev/null +++ b/002_source/cms/src/db/Notifications/type.d.ts @@ -0,0 +1,45 @@ +'use client'; + +export type SortDir = 'asc' | 'desc'; + +export interface Notification { + id: string; + read: boolean; + type: string; + author: Record; + job: Record; + description: string; + NOTI_ID: string; + created: string; + updated: string; +} + +export interface CreateFormProps { + read?: boolean; + type: string; + author: Record; + job: Record; + description: string; + NOTI_ID: string; +} + +export interface EditFormProps { + read?: boolean; + type?: string; + author?: Record; + job?: Record; + description?: string; + NOTI_ID?: string; +} + +export interface NotificationsFiltersProps { + filters?: Filters; + sortDir?: SortDir; + fullData: Notification[]; +} + +export interface Filters { + type?: string; + read?: boolean; + NOTI_ID?: string; +}