update left menu path,

This commit is contained in:
louiscklaw
2025-04-24 23:31:33 +08:00
parent 6884f1466f
commit d73e5f9c22
45 changed files with 247 additions and 5474 deletions

View File

@@ -46,7 +46,7 @@
"deleteButton": "刪除"
},
"list": {
"title": "課程類型列表",
"title": "課程類型列表1",
"message": "請選擇課程類型",
"empty": {
"title": "目前沒有課程類型",

View File

@@ -37,8 +37,11 @@
"dashboard.lessonTypes.edit.error": "課程類型更新失敗",
"dashboard.lessonTypes.delete.success": "課程類型刪除成功",
"dashboard.lessonTypes.delete.error": "課程類型刪除失敗",
"dashboard.lessonTypes.list": "課程類型列表",
"dashboard.lessonTypes.list.title": "課程類型列表",
"dashboard.lessonTypes.list": "課程類型",
"dashboard.lessonCategories.list": "詞語種類",
"dashboard.vocabulary.list": "詞語種類",
"dashboard.connective.list": "詞語種類",
"dashboard.lessonTypes.list.title": "課程類型列表3",
"dashboard.lessonTypes.list.message": "請選擇課程類型",
"dashboard.lessonTypes.list.empty": "目前沒有課程類型",
"dashboard.lessonTypes.list.empty.title": "目前沒有課程類型",

View File

@@ -1,104 +0,0 @@
# Connective Revision Guidelines
## Files and component highlight
1. `_GUIDELINES.md` - this document
1. categories
- list (page.tsx), also containing a button to delete record
- read/view ([cat_id]/page.tsx)
- create (create/page.tsx)
- edit/update (edit/[cat_id]/page.tsx)
- optional data for testing(lp-categories-sample-data.tsx)
1. questions
- list (page.tsx), also containing a button to delete record
- read/view ([cat_id]/page.tsx)
- create (create/page.tsx)
- edit/update (edit/[cat_id]/page.tsx)
- optional data for testing(lp-categories-sample-data.tsx)
## Prompt Documents
Each edit page contains `_PROMPT.md` file that provides guidance for editing.
## Sample Data
- `categories/lp-categories-sample-data.tsx`: Categories sample data
- `questions/cr-categories-sample-data.tsx`: Questions sample data
## Assumptions & Requirements
1. Using PocketBase to handle ConnectiveRevision records
2. Each ConnectiveRevision record has:
- `id` (autogenerated)
- `collectionId` (autogenerated)
- `collectionName` (autogenerated)
- `created` (autogenerated)
- `updated` (autogenerated)
- `title` (string)
- `description` (string)
- `category` (string)
- `status` (string)
- `priority` (number)
- `dueDate` (string)
- `assignee` (string)
- `reporter` (string)
- `comments` (array)
- `attachments` (array)
- `tags` (array)
- `related` (array)
- `history` (array)
## Assumption and Requirements
- the `@` sign refer to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src`
- assume `pb` is located in `@/lib/pb`
- 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`
## Component Development Guidelines
### Requirements
1. **Single Responsibility Principle**:
2. **File Organization**:
- One file per component
- File name should match component name (PascalCase)
- Place components in logical directories based on their purpose
3. **Type Safety**:
- Always use TypeScript types/interfaces
- Import types from `@/db/Customers/type.d.tsx`
4. **PocketBase Integration**:
- Use `pb` instance from `@/lib/pb`
### Component Example
```typescript
'use client';
import { pb } from '@/lib/pb';
import { COL_ExampleModel } from '@/constants';
import type { ExampleType } from '@/db/ExampleModel/type';
// common reference to error display, Provide user-friendly error messages
import ErrorDisplay from '@/components/dashboard/error';
// declare `Props` explicitively
interface Props {
initialData?: ExampleType;
}
export default function ExampleForm({ initialData }: Props) {
let { t } = useTranslate();
// Render form UI
return <>helloworld</>
}
```

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -135,7 +135,11 @@ export default function Layout({ children, params }: LayoutProps): React.JSX.Ele
const filteredThreads = filterThreads(threads, labelId);
return (
<MailProvider currentLabelId={labelId} labels={labels} threads={filteredThreads}>
<MailProvider
currentLabelId={labelId}
labels={labels}
threads={filteredThreads}
>
<MailView>{children}</MailView>
</MailProvider>
);

View File

@@ -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 <ThreadView threadId={threadId} />;
}

View File

@@ -0,0 +1,146 @@
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 (
<MailProvider
currentLabelId={labelId}
labels={labels}
threads={filteredThreads}
>
<MailView>{children}</MailView>
</MailProvider>
);
}

View File

@@ -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 <ThreadsView />;
}

View File

@@ -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 <ThreadsView />;
}

View File

@@ -35,32 +35,25 @@ export const layoutConfig = {
key: 'lesson-types',
title: 'dashboard.lessonTypes.list',
href: paths.dashboard.lesson_types.list,
//
},
{
key: 'lesson-types:create',
title: 'dashboard.lessonTypes.create',
href: paths.dashboard.lesson_types.create,
},
],
},
{
key: 'lesson-categories',
title: 'categories',
icon: 'users',
items: [
{
key: 'lesson-categories',
title: 'dashboard.lessonTypes.list',
title: 'dashboard.lessonCategories.list',
href: paths.dashboard.lesson_categories.list,
},
{
key: 'lesson-categories:create',
title: 'dashboard.lessonTypes.create',
href: paths.dashboard.lesson_categories.create,
key: 'vocabulary',
title: 'dashboard.vocabulary.list',
href: paths.dashboard.vocabulary.list,
},
{
key: 'connectives',
title: 'dashboard.connective.list',
href: paths.dashboard.connectives.list,
},
],
},
{
key: 'quiz_lp',
title: 'listening-practice',
@@ -124,9 +117,10 @@ export const layoutConfig = {
title: 'teachers',
icon: 'users',
items: [
{ key: 'teachers', title: 'List teachers', href: paths.dashboard.teachers.list },
{ key: 'teachers:create', title: 'Create teacher', href: paths.dashboard.teachers.create },
{ key: 'teachers:details', title: 'Teacher details', href: paths.dashboard.teachers.details('1') },
{ key: 'teachers', title: 'List', href: paths.dashboard.teachers.list },
{ key: 'teacher-mail', title: 'Mail', href: paths.dashboard.teachers.mail.list('1') },
// { key: 'teachers:create', title: 'Create teacher', href: paths.dashboard.teachers.create },
// { key: 'teachers:details', title: 'Teacher details', href: paths.dashboard.teachers.details('1') },
],
},
{

View File

@@ -77,61 +77,79 @@ export const paths = {
},
crypto: '/dashboard/crypto',
lesson_types: {
list: '/dashboard/lesson_types',
list: '/dashboard/lesson_types/list',
create: '/dashboard/lesson_types/create',
details: (id: string) => `/dashboard/lesson_types/${id}`,
details: (id: string) => `/dashboard/lesson_types/view/${id}`,
edit: (id: string) => `/dashboard/lesson_types/edit/${id}`,
},
lesson_categories: {
list: '/dashboard/lesson_categories',
list: '/dashboard/lesson_categories/list',
create: '/dashboard/lesson_categories/create',
details: (id: string) => `/dashboard/lesson_categories/${id}`,
details: (id: string) => `/dashboard/lesson_categories/view/${id}`,
edit: (id: string) => `/dashboard/lesson_categories/edit/${id}`,
},
vocabulary: {
list: '/dashboard/vocabulary/list',
create: '/dashboard/vocabulary/create',
details: (id: string) => `/dashboard/vocabulary/view/${id}`,
edit: (id: string) => `/dashboard/vocabulary/edit/${id}`,
},
connectives: {
list: '/dashboard/connectives/list',
create: '/dashboard/connectives/create',
details: (id: string) => `/dashboard/connectives/view/${id}`,
edit: (id: string) => `/dashboard/connectives/edit/${id}`,
},
lp_categories: {
list: '/dashboard/lp/categories',
list: '/dashboard/lp/categories/list',
create: '/dashboard/lp/categories/create',
details: (id: string) => `/dashboard/lp/categories/${id}`,
details: (id: string) => `/dashboard/lp/categories/view/${id}`,
edit: (id: string) => `/dashboard/lp/categories/edit/${id}`,
},
lp_questions: {
list: '/dashboard/lp/questions',
list: '/dashboard/lp/questions/list',
create: '/dashboard/lp/questions/create',
details: (id: string) => `/dashboard/lp/questions/${id}`,
details: (id: string) => `/dashboard/lp/questions/view/${id}`,
edit: (id: string) => `/dashboard/lp/questions/edit/${id}`,
},
mf_categories: {
list: '/dashboard/mf/categories',
list: '/dashboard/mf/categories/list',
create: '/dashboard/mf/categories/create',
details: (id: string) => `/dashboard/mf/categories/${id}`,
details: (id: string) => `/dashboard/mf/categories/view/${id}`,
edit: (id: string) => `/dashboard/mf/categories/edit/${id}`,
},
mf_questions: {
list: '/dashboard/mf/questions',
list: '/dashboard/mf/questions/list',
create: '/dashboard/mf/questions/create',
details: (id: string) => `/dashboard/mf/questions/${id}`,
details: (id: string) => `/dashboard/mf/questions/view/${id}`,
edit: (id: string) => `/dashboard/mf/questions/edit/${id}`,
},
cr_categories: {
list: '/dashboard/cr/categories',
list: '/dashboard/cr/categories/list',
create: '/dashboard/cr/categories/create',
details: (id: string) => `/dashboard/cr/categories/${id}`,
details: (id: string) => `/dashboard/cr/categories/view/${id}`,
edit: (id: string) => `/dashboard/cr/categories/edit/${id}`,
},
cr_questions: {
list: '/dashboard/cr/questions',
list: '/dashboard/cr/questions/list',
create: '/dashboard/cr/questions/create',
details: (id: string) => `/dashboard/cr/questions/${id}`,
details: (id: string) => `/dashboard/cr/questions/view/${id}`,
edit: (id: string) => `/dashboard/cr/questions/edit/${id}`,
},
teachers: {
list: '/dashboard/teachers',
list: '/dashboard/teachers/list',
create: '/dashboard/teachers/create',
details: (id: string) => `/dashboard/teachers/${id}`,
details: (id: string) => `/dashboard/teachers/view/${id}`,
edit: (id: string) => `/dashboard/teachers/edit/${id}`,
mail: {
list: (id: string) => `/dashboard/teachers/mail/${id}/list`,
// create: '/dashboard/teachers/create',
// details: (id: string) => `/dashboard/teachers/${id}`,
// edit: (id: string) => `/dashboard/teachers/edit/${id}`,
},
},
students: {
list: '/dashboard/students',
list: '/dashboard/students/list',
create: '/dashboard/students/create',
view: (id: string) => `/dashboard/students/view/${id}`,
edit: (id: string) => `/dashboard/students/edit/${id}`,