Compare commits
27 Commits
b8e8968866
...
develop/cm
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5640d4d4f6 | ||
![]() |
7105bc85e3 | ||
![]() |
61c10821b6 | ||
![]() |
ea3f99654d | ||
![]() |
30f494fc35 | ||
![]() |
367e58a8cf | ||
![]() |
99274b9c73 | ||
![]() |
51935d203a | ||
![]() |
9d3e832081 | ||
![]() |
4c72861eda | ||
![]() |
d0ea7e5452 | ||
![]() |
7d70b5826b | ||
![]() |
a00d1ee7ce | ||
![]() |
7296a10ec1 | ||
![]() |
957df690f4 | ||
![]() |
45d5c23512 | ||
![]() |
caa224cbb6 | ||
![]() |
9be92b41d1 | ||
![]() |
6e8fea3bdd | ||
![]() |
df87cfb037 | ||
![]() |
d73e5f9c22 | ||
![]() |
6884f1466f | ||
![]() |
d308131a8a | ||
![]() |
b3ebe8309a | ||
![]() |
fa35ef2bef | ||
![]() |
0785fcd144 | ||
![]() |
2dcc765072 |
5
002_source/cms/.gitignore
vendored
5
002_source/cms/.gitignore
vendored
@@ -1,3 +1,8 @@
|
|||||||
|
**/*del
|
||||||
|
**/*bak
|
||||||
|
**/*log
|
||||||
|
**/*tmp
|
||||||
|
|
||||||
.env
|
.env
|
||||||
.env.production
|
.env.production
|
||||||
|
|
||||||
|
33
002_source/cms/.vscode/extensions.json
vendored
Normal file
33
002_source/cms/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"wmaurer.change-case",
|
||||||
|
"saoudrizwan.claude-dev",
|
||||||
|
"naumovs.color-highlight",
|
||||||
|
"bocovo.dbml-erd-visualizer",
|
||||||
|
"aflalo.dbml-formatter",
|
||||||
|
"editorconfig.editorconfig",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"ms-vscode.vscode-typescript-next",
|
||||||
|
"tttpob.list-aligner",
|
||||||
|
"bierner.markdown-mermaid",
|
||||||
|
"onatm.open-in-new-window",
|
||||||
|
"christian-kohler.path-intellisense",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"humao.rest-client",
|
||||||
|
"ryu1kn.text-marker",
|
||||||
|
"gruntfuggly.todo-tree",
|
||||||
|
"bibhasdn.unique-lines",
|
||||||
|
"neptunedesign.vs-sequential-number",
|
||||||
|
"matt-meyers.vscode-dbml",
|
||||||
|
"codeium.codeium",
|
||||||
|
"tencent-cloud.coding-copilot",
|
||||||
|
"yzhang.markdown-all-in-one",
|
||||||
|
"stubedston.align-text",
|
||||||
|
"mads-hartmann.bash-ide-vscode",
|
||||||
|
"duynvu.dbml-language",
|
||||||
|
"mikestead.dotenv",
|
||||||
|
"bpruitt-goddard.mermaid-markdown-syntax-highlighting",
|
||||||
|
"davidanson.vscode-markdownlint",
|
||||||
|
"foxundermoon.shell-format"
|
||||||
|
]
|
||||||
|
}
|
@@ -1,9 +1,12 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
need to fix local storage error
|
need to fix local storage error
|
||||||
|
|
||||||
page right (next page) button is not working
|
page right (next page) button is not working
|
||||||
in the middle of clone lesson type to lesson category
|
in the middle of clone lesson type to lesson category
|
||||||
- [ ] listing page
|
|
||||||
- [ ] delete button on each row
|
- [ ] listing page
|
||||||
- [ ] create page
|
- [ ] delete button on each row
|
||||||
- [ ] edit
|
- [ ] create page
|
||||||
- [ ] delete
|
- [ ] edit
|
||||||
|
- [ ] delete
|
||||||
|
@@ -1,15 +0,0 @@
|
|||||||
# guideline
|
|
||||||
|
|
||||||
- please divide the problem into small parts
|
|
||||||
- if you found youself cannot understand the problem, please stop and ask how to do
|
|
||||||
- if you found youself cannot solve the problem, plesae stop and ask how to do
|
|
||||||
- review the whole solution before you reply to user
|
|
||||||
- if code syntax is already there, do follow (e.g. naming convention, syntax) the existing code
|
|
||||||
|
|
||||||
- example for page can be found in `./src/app/_helloworld/page.tsx`
|
|
||||||
- example for component can be found in `./src/components/_helloworld/index.tsx`
|
|
||||||
|
|
||||||
- no need to explain the reason until you are told to do so
|
|
||||||
- no need to show me the code change, at the end just simple summary in point form is ok
|
|
||||||
|
|
||||||
Thanks
|
|
@@ -1,44 +0,0 @@
|
|||||||
# AI GUIDELINE
|
|
||||||
|
|
||||||
## getting started
|
|
||||||
|
|
||||||
Imagine there is a:
|
|
||||||
|
|
||||||
1. developer (provide the modification)
|
|
||||||
2. QA engineer (provide the feedback, and testing)
|
|
||||||
3. software engineer
|
|
||||||
4. technical writer
|
|
||||||
|
|
||||||
they will:
|
|
||||||
|
|
||||||
- conclude and integrate the ideas from developer and QA engineer
|
|
||||||
- make decision to modify the code accordingly.
|
|
||||||
|
|
||||||
## project background and initial setup
|
|
||||||
|
|
||||||
- No need to reply me what you are going on and your digest in this phase.
|
|
||||||
Just reply me "OK" when done
|
|
||||||
|
|
||||||
- base_dir=`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project`
|
|
||||||
|
|
||||||
- `schema.dbml`
|
|
||||||
|
|
||||||
- read `<base_dir>/001_documentation/Requirements/REQ0006/schema.dbml`
|
|
||||||
this is file in `dbml` format stating the main database structure
|
|
||||||
|
|
||||||
- `schema.json`
|
|
||||||
|
|
||||||
- read `<base_dir>/002_source/cms/src/db/schema.json`
|
|
||||||
this is the file of current pocketbase schema
|
|
||||||
|
|
||||||
- read `<base_dir>/002_source/cms/src/constants.ts`
|
|
||||||
this is the content of `@/constants`
|
|
||||||
|
|
||||||
- look into the md files in folder `<base_dir>/002_source/cms/_AI_WORKSPACE/001_guideline`
|
|
||||||
|
|
||||||
- directory may contain `repomix-output.xml` file, that is a simple summary of all files inside the directory
|
|
||||||
|
|
||||||
- if the directory user provided contins `_GUIDELINES.md`, please read the file
|
|
||||||
|
|
||||||
- read the files, remember and link up the ideas in file stated above,
|
|
||||||
i will tell them the task afterwards
|
|
@@ -1,52 +0,0 @@
|
|||||||
---
|
|
||||||
description: Next.js with TypeScript and Tailwind UI best practices
|
|
||||||
globs: **/*.tsx, **/*.ts, src/**/*.ts, src/**/*.tsx
|
|
||||||
---
|
|
||||||
|
|
||||||
# Next.js Best Practices
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
- Use the App Router directory structure
|
|
||||||
- Place components in `app` directory for route-specific components
|
|
||||||
- Place shared components in `components` directory
|
|
||||||
- Place utilities and helpers in `lib` directory
|
|
||||||
- Use lowercase with dashes for directories (e.g., `components/auth-wizard`)
|
|
||||||
|
|
||||||
## Components
|
|
||||||
- Use Server Components by default
|
|
||||||
- Mark client components explicitly with 'use client'
|
|
||||||
- Wrap client components in Suspense with fallback
|
|
||||||
- Use dynamic loading for non-critical components
|
|
||||||
- Implement proper error boundaries
|
|
||||||
- Place static content and interfaces at file end
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
- Optimize images: Use WebP format, size data, lazy loading
|
|
||||||
- Minimize use of 'useEffect' and 'setState'
|
|
||||||
- Favor Server Components (RSC) where possible
|
|
||||||
- Use dynamic loading for non-critical components
|
|
||||||
- Implement proper caching strategies
|
|
||||||
|
|
||||||
## Data Fetching
|
|
||||||
- Use Server Components for data fetching when possible
|
|
||||||
- Implement proper error handling for data fetching
|
|
||||||
- Use appropriate caching strategies
|
|
||||||
- Handle loading and error states appropriately
|
|
||||||
|
|
||||||
## Routing
|
|
||||||
- Use the App Router conventions
|
|
||||||
- Implement proper loading and error states for routes
|
|
||||||
- Use dynamic routes appropriately
|
|
||||||
- Handle parallel routes when needed
|
|
||||||
|
|
||||||
## Forms and Validation
|
|
||||||
- Use Zod for form validation
|
|
||||||
- Implement proper server-side validation
|
|
||||||
- Handle form errors appropriately
|
|
||||||
- Show loading states during form submission
|
|
||||||
|
|
||||||
## State Management
|
|
||||||
- Minimize client-side state
|
|
||||||
- Use React Context sparingly
|
|
||||||
- Prefer server state when possible
|
|
||||||
- Implement proper loading states
|
|
@@ -1,57 +0,0 @@
|
|||||||
---
|
|
||||||
description: TypeScript coding standards and best practices for modern web development
|
|
||||||
globs: **/*.ts, **/*.tsx, **/*.d.ts
|
|
||||||
---
|
|
||||||
|
|
||||||
# TypeScript Best Practices
|
|
||||||
|
|
||||||
## Type System
|
|
||||||
- Prefer interfaces over types for object definitions
|
|
||||||
- Use type for unions, intersections, and mapped types
|
|
||||||
- Avoid using `any`, prefer `unknown` for unknown types
|
|
||||||
- Use strict TypeScript configuration
|
|
||||||
- Leverage TypeScript's built-in utility types
|
|
||||||
- Use generics for reusable type patterns
|
|
||||||
|
|
||||||
## Naming Conventions
|
|
||||||
- Use PascalCase for type names and interfaces
|
|
||||||
- Use camelCase for variables and functions
|
|
||||||
- Use UPPER_CASE for constants
|
|
||||||
- Use descriptive names with auxiliary verbs (e.g., isLoading, hasError)
|
|
||||||
- Prefix interfaces for React props with 'Props' (e.g., ButtonProps)
|
|
||||||
|
|
||||||
## Code Organization
|
|
||||||
- Keep type definitions close to where they're used
|
|
||||||
- Export types and interfaces from dedicated type files when shared
|
|
||||||
- Use barrel exports (index.ts) for organizing exports
|
|
||||||
- Place shared types in a `types` directory
|
|
||||||
- Co-locate component props with their components
|
|
||||||
|
|
||||||
## Functions
|
|
||||||
- Use explicit return types for public functions
|
|
||||||
- Use arrow functions for callbacks and methods
|
|
||||||
- Implement proper error handling with custom error types
|
|
||||||
- Use function overloads for complex type scenarios
|
|
||||||
- Prefer async/await over Promises
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
- Enable strict mode in tsconfig.json
|
|
||||||
- Use readonly for immutable properties
|
|
||||||
- Leverage discriminated unions for type safety
|
|
||||||
- Use type guards for runtime type checking
|
|
||||||
- Implement proper null checking
|
|
||||||
- Avoid type assertions unless necessary
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
- Create custom error types for domain-specific errors
|
|
||||||
- Use Result types for operations that can fail
|
|
||||||
- Implement proper error boundaries
|
|
||||||
- Use try-catch blocks with typed catch clauses
|
|
||||||
- Handle Promise rejections properly
|
|
||||||
|
|
||||||
## Patterns
|
|
||||||
- Use the Builder pattern for complex object creation
|
|
||||||
- Implement the Repository pattern for data access
|
|
||||||
- Use the Factory pattern for object creation
|
|
||||||
- Leverage dependency injection
|
|
||||||
- Use the Module pattern for encapsulation
|
|
@@ -1,44 +0,0 @@
|
|||||||
# AI GUIDELINE
|
|
||||||
|
|
||||||
## getting started
|
|
||||||
|
|
||||||
Imagine there is a:
|
|
||||||
|
|
||||||
1. developer (provide the modification)
|
|
||||||
2. QA engineer (provide the feedback, and testing)
|
|
||||||
3. software engineer
|
|
||||||
4. technical writer
|
|
||||||
|
|
||||||
they will:
|
|
||||||
|
|
||||||
- conclude and integrate the ideas from developer and QA engineer
|
|
||||||
- make decision to modify the code accordingly.
|
|
||||||
|
|
||||||
## project background and initial setup
|
|
||||||
|
|
||||||
- No need to reply me what you are going on and your digest in this phase.
|
|
||||||
Just reply me "OK" when done
|
|
||||||
|
|
||||||
- base_dir=`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project`
|
|
||||||
|
|
||||||
- `schema.dbml`
|
|
||||||
|
|
||||||
- read `<base_dir>/001_documentation/Requirements/REQ0006/schema.dbml`
|
|
||||||
this is file in `dbml` format stating the main database structure
|
|
||||||
|
|
||||||
- `schema.json`
|
|
||||||
|
|
||||||
- read `<base_dir>/002_source/cms/src/db/schema.json`
|
|
||||||
this is the file of current pocketbase schema
|
|
||||||
|
|
||||||
- read `<base_dir>/002_source/cms/src/constants.ts`
|
|
||||||
this is the content of `@/constants`
|
|
||||||
|
|
||||||
- look into the md files in folder `<base_dir>/002_source/cms/_AI_WORKSPACE/001_guideline`
|
|
||||||
|
|
||||||
- directory may contain `repomix-output.xml` file, that is a simple summary of all files inside the directory
|
|
||||||
|
|
||||||
- if the directory user provided contins `_GUIDELINES.md`, please read the file
|
|
||||||
|
|
||||||
- read the files, remember and link up the ideas in file stated above,
|
|
||||||
i will tell them the task afterwards
|
|
@@ -1,22 +0,0 @@
|
|||||||
Hi, i need your help.
|
|
||||||
|
|
||||||
i am working on a nextjs react typescript project
|
|
||||||
|
|
||||||
please read the code in directory `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/QuizLPQuestions` (denoted `QuizLPQuestions`)
|
|
||||||
|
|
||||||
i want you to:
|
|
||||||
|
|
||||||
- list files from directory `QuizLPQuestions` and use them as template
|
|
||||||
- understand the relations (e.g. `lp` -> `lp_categories`, `lp` -> `COL_QUIZ_LP_CATEGORIES`)
|
|
||||||
- review if any remaining `lp` or `mf` exist in `cr` directory and please help to do replace it to `cr`. (e.g. `COL_QUIZ_LP_CATEGORIES` -> `COL_QUIZ_CR_CATEGORIES` )
|
|
||||||
- please create if you find any missing files/codes/constants
|
|
||||||
- using template, create the similar code for `cr` named `QuizCRCategories`
|
|
||||||
|
|
||||||
thank you
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- I proofed the code is working already, what you need to do is refactoring of the `variables`, `functions`, `classes`, `constants` (`lp` -> `cr`)
|
|
||||||
- compare the difference between `lp` and `mf`,
|
|
||||||
- remember the differences and
|
|
||||||
- draft the new type `cr` (e.g. modify the `import` locations, variables, functions, classes, constants name etc.)
|
|
@@ -1,22 +0,0 @@
|
|||||||
Hi, i need your help.
|
|
||||||
|
|
||||||
i am working on a nextjs react typescript project
|
|
||||||
|
|
||||||
please read the code in directory `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/QuizLPCategories` (denoted `QuizLPCategories`)
|
|
||||||
|
|
||||||
i want you to:
|
|
||||||
|
|
||||||
- list files from directory `QuizLPCategories` and use them as template
|
|
||||||
- understand the relations (e.g. `lp` -> `lp_categories`, `lp` -> `COL_QUIZ_LP_CATEGORIES`)
|
|
||||||
- review if any remaining `lp` or `mf` exist in `cr` directory and please help to do replace it to `cr`. (e.g. `COL_QUIZ_LP_CATEGORIES` -> `COL_QUIZ_CR_CATEGORIES` )
|
|
||||||
- please create if you find any missing files/codes/constants
|
|
||||||
- using template, create the similar code for `cr` named `QuizCRCategories`
|
|
||||||
|
|
||||||
thank you
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- I proofed the code is working already, what you need to do is refactoring of the `variables`, `functions`, `classes`, `constants` (`lp` -> `cr`)
|
|
||||||
- compare the difference between `lp` and `mf`,
|
|
||||||
- remember the differences and
|
|
||||||
- draft the new type `cr` (e.g. modify the `import` locations, variables, functions, classes, constants name etc.)
|
|
@@ -1,22 +0,0 @@
|
|||||||
Hi, i need your help.
|
|
||||||
|
|
||||||
i am working on a nextjs react typescript project
|
|
||||||
|
|
||||||
please read the code in directory `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/QuizLPQuestions` (denoted `QuizLPQuestions`)
|
|
||||||
|
|
||||||
i want you to:
|
|
||||||
|
|
||||||
- list files from directory `QuizLPQuestions` and use them as template
|
|
||||||
- understand the relations (e.g. `lp` -> `lp_Questions`, `lp` -> `COL_QUIZ_LP_QUESTIONS`)
|
|
||||||
- review if any remaining `lp` or `mf` exist in `cr` directory and please help to do replace it to `cr`. (e.g. `COL_QUIZ_LP_QUESTIONS` -> `COL_QUIZ_CR_Questions` )
|
|
||||||
- please create if you find any missing files/codes/constants
|
|
||||||
- using template, create the similar code for `customer` named `Customers`
|
|
||||||
|
|
||||||
thank you
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- I proofed the code is working already, what you need to do is refactoring of the `variables`, `functions`, `classes`, `constants` (`lp` -> `cr`)
|
|
||||||
- compare the difference between `lp` and `mf`,
|
|
||||||
- remember the differences and
|
|
||||||
- draft the new type `cr` (e.g. modify the `import` locations, variables, functions, classes, constants name etc.)
|
|
@@ -1,20 +0,0 @@
|
|||||||
Hi, i need your help.
|
|
||||||
|
|
||||||
## task
|
|
||||||
|
|
||||||
i am working on a dbml file
|
|
||||||
i got a schema.json from exported from pocketbase
|
|
||||||
and i want to update it to my current dbml file (for documentation)
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
|
|
||||||
- the collection from json file started with `_` can be ignored. they are system collection and should not appear in dbml
|
|
||||||
- one collection from json file mapped with one table in dbml file
|
|
||||||
- the `presentable` field from json file should be ignored.
|
|
||||||
|
|
||||||
## information
|
|
||||||
|
|
||||||
json file: `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/schema.json`
|
|
||||||
dbml file: `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/001_documentation/Requirements/REQ0006/schema.dbml`
|
|
||||||
|
|
||||||
thanks
|
|
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
tags: db, driver
|
|
||||||
---
|
|
||||||
|
|
||||||
# clone db driver
|
|
||||||
|
|
||||||
please understand the tsx files in folder
|
|
||||||
`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/Teachers`
|
|
||||||
|
|
||||||
change all occurrence of `customer` to `teacher` thanks.
|
|
@@ -1,18 +0,0 @@
|
|||||||
# task
|
|
||||||
|
|
||||||
update `dbml` from `schema.json`
|
|
||||||
|
|
||||||
## things to note
|
|
||||||
|
|
||||||
1. please skip `presentable` properties from `schema.json`
|
|
||||||
|
|
||||||
## steps
|
|
||||||
|
|
||||||
1. read file `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/schema.json`. this is the export from `pocketbase`.
|
|
||||||
1. read file `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/001_documentation/Requirements/REQ0006/schema.dbml`. this is file written in dbml format.
|
|
||||||
1. currently the collection in `schama.json` is mapped to table in `schema.dbml`
|
|
||||||
1. compare the `schema.json` and `schema.dbml`, remember the differences
|
|
||||||
1. you may found some comment already exist in `schema.dbml`, please keep them
|
|
||||||
1. while keeping `schema.json` content unchanged. write file to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/001_documentation/Requirements/REQ0006/schema.dbml` content based on `schema.json`.
|
|
||||||
|
|
||||||
thanks.
|
|
@@ -1,10 +0,0 @@
|
|||||||
# task
|
|
||||||
|
|
||||||
extend function by clone and updating exist code
|
|
||||||
|
|
||||||
## steps
|
|
||||||
|
|
||||||
please read file
|
|
||||||
`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/QuizListenings/GetAllCount.tsx`
|
|
||||||
|
|
||||||
duplicate it to cover `GetActiveCount`, `GetPendingCount` and `GetBlockedCount`. create them in `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/Customers` directory thanks.
|
|
Binary file not shown.
@@ -1,108 +0,0 @@
|
|||||||
# AI GUIDELINE
|
|
||||||
|
|
||||||
## getting started
|
|
||||||
|
|
||||||
Imagine there is a software developer and a QA engineer to solve the problems together
|
|
||||||
|
|
||||||
They will:
|
|
||||||
|
|
||||||
no need to reply me what you are going on and your digest in this phase.
|
|
||||||
just reply me "OK" when done
|
|
||||||
|
|
||||||
base_dir=`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project`
|
|
||||||
|
|
||||||
- read `<base_dir>/001_documentation/Requirements/REQ0006/schema.dbml`
|
|
||||||
this is file in dbml syntax state the main database
|
|
||||||
|
|
||||||
- read `<base_dir>/002_source/cms/src/db/schema.json`
|
|
||||||
this is the file of live pocketbase schema output
|
|
||||||
|
|
||||||
- read `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/constants.ts`
|
|
||||||
this is the content of `@/constants`
|
|
||||||
|
|
||||||
- look into the md files in folder `<base_dir>/002_source/cms/_AI_GUIDELINE`
|
|
||||||
|
|
||||||
- read, remember and link up the ideas in file stated above,
|
|
||||||
i will tell them the task afterwards
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
The software engineer will provide solutions,
|
|
||||||
while QA engineer will feedback the opinion.
|
|
||||||
|
|
||||||
this is now not in debug phase,
|
|
||||||
so, no need to reply me what they are going on or their insight throught the prompt.
|
|
||||||
just reply me "OK" when done
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
clone `GetVisibleCount.tsx` and `GetHiddenCount.tsx` from `LessonTypes` to `LessonCategories` and update it
|
|
||||||
|
|
||||||
please draft `GetHiddenCount.tsx` for COL_LESSON_TYPES and `status = hidden`
|
|
||||||
|
|
||||||
well done !, please proceed to another request
|
|
||||||
|
|
||||||
working directory: `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db`
|
|
||||||
|
|
||||||
according information from `schema.json`, get the collection of `Students`
|
|
||||||
|
|
||||||
pleaes clone the `tsx` files from `LessonTypes` and `LessonCategories` to `Students` and update the content
|
|
||||||
|
|
||||||
when you draft coding, review file and append with `.tsx.draft`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- this is part of react typescript project, with pocketbase
|
|
||||||
- `schema.dbml`, describe the collections(tables)
|
|
||||||
- folder `LessonCategories`, the correct references
|
|
||||||
- folder `LessonTypes`, the correct references
|
|
||||||
- you can find the `schema.dbml` and schema information from `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/001_documentation/Requirements/REQ0006`
|
|
||||||
- do not read root directory, assume it is a fresh copy of nextjs project is ok
|
|
||||||
|
|
||||||
## instruction
|
|
||||||
|
|
||||||
- break the questions into smaller parts
|
|
||||||
- review file append with `.draft`, see if the content aligned with the correct references
|
|
||||||
- read and understand `dbml` file
|
|
||||||
- lookup the every folder
|
|
||||||
|
|
||||||
## tasks
|
|
||||||
|
|
||||||
Thanks
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
please take a look in `schema.dbml` and `schema.json`,
|
|
||||||
associate the collection from json file to the table in dbml file
|
|
||||||
|
|
||||||
please modify the `schema.dbml` to align with `schema.json`
|
|
||||||
|
|
||||||
to the collection `QuizLPCategories` align the dbml file in the previous prompt
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
please revise
|
|
||||||
|
|
||||||
please revise
|
|
||||||
`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/types/LpCategory.tsx` `interface LpCategory`
|
|
||||||
|
|
||||||
to the collection `QuizLPCategories` align the dbml file in the previous prompt
|
|
||||||
|
|
||||||
|
|
||||||
please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lp_categories/_constants.tsx`
|
|
||||||
|
|
||||||
to follow the type definition in `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/types/LpCategory.tsx`, the constant `defaultLpCategory`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
the constants file (`@/constants`) was `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/constants.ts`
|
|
||||||
|
|
||||||
please help to fix the `tsx` files in folder `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/QuizMFCategories`,
|
|
||||||
the `COL` constants is wrongly used, it should refer to `COL_QUIZ_MF_CATEGORIES`. thanks
|
|
||||||
|
|
||||||
|
|
||||||
please update the `COL_XXXX` TO COL_MF_CATEGORIES
|
|
@@ -1,66 +0,0 @@
|
|||||||
The software engineer will provide solutions,
|
|
||||||
while QA engineer will feedback the opinion.
|
|
||||||
|
|
||||||
this is now not in debug phase,
|
|
||||||
so, no need to reply me what they are going on or their insight throught the prompt.
|
|
||||||
just reply me "OK" when done
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
clone `GetVisibleCount.tsx` and `GetHiddenCount.tsx` from `LessonTypes` to `LessonCategories` and update it
|
|
||||||
|
|
||||||
please draft `GetHiddenCount.tsx` for COL_LESSON_TYPES and `status = hidden`
|
|
||||||
|
|
||||||
well done !, please proceed to another request
|
|
||||||
|
|
||||||
working directory: `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db`
|
|
||||||
|
|
||||||
according information from `schema.json`, get the collection of `Students`
|
|
||||||
|
|
||||||
pleaes clone the `tsx` files from `LessonTypes` and `LessonCategories` to `Students` and update the content
|
|
||||||
|
|
||||||
when you draft coding, review file and append with `.tsx.draft`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- this is part of react typescript project, with pocketbase
|
|
||||||
- `schema.dbml`, describe the collections(tables)
|
|
||||||
- folder `LessonCategories`, the correct references
|
|
||||||
- folder `LessonTypes`, the correct references
|
|
||||||
- you can find the `schema.dbml` and schema information from `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/001_documentation/Requirements/REQ0006`
|
|
||||||
- do not read root directory, assume it is a fresh copy of nextjs project is ok
|
|
||||||
|
|
||||||
## instruction
|
|
||||||
|
|
||||||
- break the questions into smaller parts
|
|
||||||
- review file append with `.draft`, see if the content aligned with the correct references
|
|
||||||
- read and understand `dbml` file
|
|
||||||
- lookup the every folder
|
|
||||||
|
|
||||||
## tasks
|
|
||||||
|
|
||||||
Thanks
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
please revise
|
|
||||||
|
|
||||||
please revise
|
|
||||||
`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/types/LpCategory.tsx` `interface LpCategory`
|
|
||||||
|
|
||||||
to the collection `QuizLPCategories` align the dbml file in the previous prompt
|
|
||||||
|
|
||||||
please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lp_categories/_constants.tsx`
|
|
||||||
|
|
||||||
to follow the type definition in `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/types/LpCategory.tsx`, the constant `defaultLpCategory`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
the constants file (`@/constants`) was `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/constants.ts`
|
|
||||||
|
|
||||||
please help to fix the `tsx` files in folder `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/QuizMFCategories`,
|
|
||||||
the `COL` constants is wrongly used, it should refer to `COL_QUIZ_MF_CATEGORIES`. thanks
|
|
||||||
|
|
||||||
please update the `COL_XXXX` TO COL_MF_CATEGORIES
|
|
@@ -1,38 +0,0 @@
|
|||||||
# AI GUIDELINE
|
|
||||||
|
|
||||||
## getting started
|
|
||||||
|
|
||||||
Imagine there is a
|
|
||||||
|
|
||||||
1. software developer and a
|
|
||||||
2. QA engineer
|
|
||||||
3. technical writer
|
|
||||||
|
|
||||||
to solve the problems together
|
|
||||||
|
|
||||||
They will:
|
|
||||||
|
|
||||||
no need to reply me what you are going on and your digest in this phase.
|
|
||||||
just reply me "OK" when done
|
|
||||||
|
|
||||||
base_dir=`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project`
|
|
||||||
|
|
||||||
- `schema.dbml`
|
|
||||||
|
|
||||||
- read `<base_dir>/001_documentation/Requirements/REQ0006/schema.dbml`
|
|
||||||
this is file in `dbml` format stating the main database structure
|
|
||||||
|
|
||||||
- `schema.json`
|
|
||||||
|
|
||||||
- read `<base_dir>/002_source/cms/src/db/schema.json`
|
|
||||||
this is the file of current pocketbase schema
|
|
||||||
|
|
||||||
- read `<base_dir>/002_source/cms/src/constants.ts`
|
|
||||||
this is the content of `@/constants`
|
|
||||||
|
|
||||||
- look into the md files in folder `<base_dir>/002_source/cms/_AI_WORKSPACE/001_guideline`
|
|
||||||
|
|
||||||
- directory may contain `repomix-output.xml` file, that is a simple summary of all files inside the directory
|
|
||||||
|
|
||||||
- read, remember and link up the ideas in file stated above,
|
|
||||||
i will tell them the task afterwards
|
|
@@ -1,15 +0,0 @@
|
|||||||
Hi, i need your help.
|
|
||||||
|
|
||||||
## background
|
|
||||||
|
|
||||||
i am working on a nextjs react typescript project
|
|
||||||
|
|
||||||
## task
|
|
||||||
|
|
||||||
i want you to summarize the code, write it to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/cr/categories/_PROMPT.md`
|
|
||||||
|
|
||||||
## steps
|
|
||||||
|
|
||||||
1. list all `tsx` files from directory `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/cr/categories`
|
|
||||||
2. take a look to the `tsx` file listed. try to understand what's going on (e.g. it is a component for `cr-categories`, it is a table)
|
|
||||||
3. write your result to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/cr/categories/_PROMPT.md`
|
|
@@ -1,17 +0,0 @@
|
|||||||
Hi, i need your help.
|
|
||||||
|
|
||||||
## background
|
|
||||||
|
|
||||||
i am working on a nextjs react typescript project
|
|
||||||
|
|
||||||
## task
|
|
||||||
|
|
||||||
i want you to summarize the code, write it to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lp/questions/_SUMMARY.md`
|
|
||||||
|
|
||||||
## steps
|
|
||||||
|
|
||||||
1. list all `tsx` files from directory `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lp/questions`
|
|
||||||
2. read every `tsx` files listed. try to understand what's going on (e.g. it is a component for `cr-questions`, it is a table)
|
|
||||||
3. read file `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/cr/questions/_SUMMARY.md`.
|
|
||||||
4. make use of `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/cr/questions/_SUMMARY.md` as template, update the content and create a new summary
|
|
||||||
5. write your new summary to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lp/questions/_SUMMARY.md`
|
|
@@ -1,17 +0,0 @@
|
|||||||
Hi, i need your help.
|
|
||||||
|
|
||||||
## background
|
|
||||||
|
|
||||||
i am working on a nextjs react typescript project
|
|
||||||
|
|
||||||
## task
|
|
||||||
|
|
||||||
i want you to summarize the code, write it to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lp/categories/_SUMMARY.md`
|
|
||||||
|
|
||||||
## steps
|
|
||||||
|
|
||||||
1. list all `tsx` files from directory `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lp/categories`
|
|
||||||
2. read every `tsx` files listed. try to understand what's going on (e.g. it is a component for `cr-categories`, it is a table)
|
|
||||||
3. read file `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/cr/categories/_SUMMARY.md`.
|
|
||||||
4. make use of `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/cr/categories/_SUMMARY.md` as template, update the content and create a new summary
|
|
||||||
5. write your new summary to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lp/categories/_SUMMARY.md`
|
|
@@ -1,66 +0,0 @@
|
|||||||
The software engineer will provide solutions,
|
|
||||||
while QA engineer will feedback the opinion.
|
|
||||||
|
|
||||||
this is now not in debug phase,
|
|
||||||
so, no need to reply me what they are going on or their insight throught the prompt.
|
|
||||||
just reply me "OK" when done
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
clone `GetVisibleCount.tsx` and `GetHiddenCount.tsx` from `LessonTypes` to `LessonCategories` and update it
|
|
||||||
|
|
||||||
please draft `GetHiddenCount.tsx` for COL_LESSON_TYPES and `status = hidden`
|
|
||||||
|
|
||||||
well done !, please proceed to another request
|
|
||||||
|
|
||||||
working directory: `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db`
|
|
||||||
|
|
||||||
according information from `schema.json`, get the collection of `Students`
|
|
||||||
|
|
||||||
pleaes clone the `tsx` files from `LessonTypes` and `LessonCategories` to `Students` and update the content
|
|
||||||
|
|
||||||
when you draft coding, review file and append with `.tsx.draft`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- this is part of react typescript project, with pocketbase
|
|
||||||
- `schema.dbml`, describe the collections(tables)
|
|
||||||
- folder `LessonCategories`, the correct references
|
|
||||||
- folder `LessonTypes`, the correct references
|
|
||||||
- you can find the `schema.dbml` and schema information from `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/001_documentation/Requirements/REQ0006`
|
|
||||||
- do not read root directory, assume it is a fresh copy of nextjs project is ok
|
|
||||||
|
|
||||||
## instruction
|
|
||||||
|
|
||||||
- break the questions into smaller parts
|
|
||||||
- review file append with `.draft`, see if the content aligned with the correct references
|
|
||||||
- read and understand `dbml` file
|
|
||||||
- lookup the every folder
|
|
||||||
|
|
||||||
## tasks
|
|
||||||
|
|
||||||
Thanks
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
please revise
|
|
||||||
|
|
||||||
please revise
|
|
||||||
`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/types/LpCategory.tsx` `interface LpCategory`
|
|
||||||
|
|
||||||
to the collection `QuizLPCategories` align the dbml file in the previous prompt
|
|
||||||
|
|
||||||
please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lp_categories/_constants.tsx`
|
|
||||||
|
|
||||||
to follow the type definition in `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/types/LpCategory.tsx`, the constant `defaultLpCategory`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
the constants file (`@/constants`) was `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/constants.ts`
|
|
||||||
|
|
||||||
please help to fix the `tsx` files in folder `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/db/QuizMFCategories`,
|
|
||||||
the `COL` constants is wrongly used, it should refer to `COL_QUIZ_MF_CATEGORIES`. thanks
|
|
||||||
|
|
||||||
please update the `COL_XXXX` TO COL_MF_CATEGORIES
|
|
@@ -1 +0,0 @@
|
|||||||
please read, understand and remember `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/_AI_WORKSPACE/code`
|
|
0
002_source/cms/autogen_config.py
Normal file
0
002_source/cms/autogen_config.py
Normal file
14
002_source/cms/cms-letter_soup.code-workspace
Normal file
14
002_source/cms/cms-letter_soup.code-workspace
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./../../001_documentation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../000_AI_WORKSPACE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
@@ -4,7 +4,7 @@
|
|||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": "==22"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
3
002_source/cms/public/locales/de/teachers.json
Normal file
3
002_source/cms/public/locales/de/teachers.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"back-to-list": "Teachers"
|
||||||
|
}
|
@@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"languageChanged": "Sprache geändert"
|
"languageChanged": "Sprache geändert",
|
||||||
|
"teachers": {
|
||||||
|
"back-to-list": "Lehrer"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,39 +0,0 @@
|
|||||||
# PROMPT
|
|
||||||
|
|
||||||
please update `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/public/locales/dev/translation.json`
|
|
||||||
to add below translations, thanks.
|
|
||||||
|
|
||||||
```
|
|
||||||
dashboard.lessonCategories.edit.title
|
|
||||||
dashboard.lessonCategories.edit.avatar
|
|
||||||
dashboard.lessonCategories.edit.avatarRequirements
|
|
||||||
dashboard.lessonCategories.edit.name
|
|
||||||
dashboard.lessonCategories.edit.type
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
please read `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lesson_category/lesson-category-edit-form.tsx`
|
|
||||||
and update `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/public/locales/dev/translation.json` the missing translations, thanks.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
please refactor `common.json` to smaller translation files, thanks
|
|
||||||
e.g. `lessonTypes` -> `lesson_type`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Hi, i want you to help merge two translation files.
|
|
||||||
|
|
||||||
base_dir=`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/public/locales/dev`
|
|
||||||
|
|
||||||
I want you to merge the content
|
|
||||||
|
|
||||||
from `<base_dir>/lesson_type.json` (source file)
|
|
||||||
to `<base_dir>/listening_practice.json` (dest file)
|
|
||||||
|
|
||||||
please extract , link up and remember the document properties
|
|
||||||
(e.g. types, functions, variables, constants, etc)
|
|
||||||
|
|
||||||
update the variables and properties of dest file to reflect `listening practice categories`/`lp_categories`
|
|
@@ -1,135 +1,4 @@
|
|||||||
{
|
{
|
||||||
"languageChanged": "語言已更改",
|
"hello": "world",
|
||||||
"type": "類型",
|
"teachers": "teachers"
|
||||||
"helloworld": "你好,世界",
|
|
||||||
"Add": "新增項目",
|
|
||||||
"visible": "可視",
|
|
||||||
"hidden": "屏閉",
|
|
||||||
"Hidden": "屏閉",
|
|
||||||
"Newest": "最新",
|
|
||||||
"Oldest": "最舊",
|
|
||||||
"Lesson position": "位置",
|
|
||||||
"Lesson type": "種類",
|
|
||||||
"Type": "種類",
|
|
||||||
"Name": "名稱",
|
|
||||||
"Filter by name": "依名稱過濾",
|
|
||||||
"Filter by type": "依類型過濾",
|
|
||||||
"Actions": "動作",
|
|
||||||
"Created at": "建立時間",
|
|
||||||
"Updated at": "更新時間",
|
|
||||||
"Position": "位置",
|
|
||||||
"Visible": "顯示",
|
|
||||||
"Apply": "套用",
|
|
||||||
"Overview": "概觀",
|
|
||||||
"Dashboard": "儀表板",
|
|
||||||
"Tickets": "票券",
|
|
||||||
"Sign ups": "註冊",
|
|
||||||
"Open issues": "開啟問題",
|
|
||||||
"Closed issues": "關閉問題",
|
|
||||||
"increase": "增加",
|
|
||||||
"decrease": "減少",
|
|
||||||
"common.loading": "載入中",
|
|
||||||
"vs last month": "與上個月相比",
|
|
||||||
"Dashboards": "儀表板",
|
|
||||||
"App usage": "應用程式使用情況",
|
|
||||||
"Find your dream job": "找到你的夢想工作",
|
|
||||||
"Need help figuring things out?": "需要幫助嗎?",
|
|
||||||
"Search for jobs that match your skills and apply to them directly": "搜索符合你技能的工作,直接申請",
|
|
||||||
"Find answers to your questions and get in touch with our team.": "找到你的問題並與我們的團隊聯繫",
|
|
||||||
"Explore documentation": "探索文件",
|
|
||||||
"Learn how to get started with our product and make the most of it.": "學習如何開始使用我們的產品並充分利用它",
|
|
||||||
"Search Jobs": "搜索工作",
|
|
||||||
"Help Center": "幫助中心",
|
|
||||||
"Documentation": "文件",
|
|
||||||
"increase_in_app_usage_with": "使用者量增加",
|
|
||||||
"new_products_purchased": "新產品購買",
|
|
||||||
"Our subscriptions": "我們的訂閱",
|
|
||||||
"this_year": "今年",
|
|
||||||
"is_forecasted_to_increase_in_your_traffic_by_the_end_of_the_current_month": "今年預計增加你的流量",
|
|
||||||
"Jan": "一月",
|
|
||||||
"Feb": "二月",
|
|
||||||
"Mar": "三月",
|
|
||||||
"Apr": "四月",
|
|
||||||
"May": "五月",
|
|
||||||
"Jun": "六月",
|
|
||||||
"Jul": "七月",
|
|
||||||
"Aug": "八月",
|
|
||||||
"Sep": "九月",
|
|
||||||
"Oct": "十月",
|
|
||||||
"Nov": "十一月",
|
|
||||||
"Dec": "十二月",
|
|
||||||
"See all subscriptions": "查看所有訂閱",
|
|
||||||
"app_chat": "應用程式聊天",
|
|
||||||
"Upcoming events": "即將發生的事件",
|
|
||||||
"Based on the linked bank accounts": "基於連結的銀行帳戶",
|
|
||||||
"App limits": "應用程式限制",
|
|
||||||
"You have used {percentage} of your available spots.": "您已使用 {percentage} 的可用座位。升級計劃以建立更多專案",
|
|
||||||
"userMessagesUnread": "hello userMessagesUnread",
|
|
||||||
"Upgrade plan to create more projects": "升級計劃以建立更多專案",
|
|
||||||
"You have almost reached your limit": "您已接近您的限制",
|
|
||||||
"Upgrade plan": "升級計劃",
|
|
||||||
"Help center": "幫助中心",
|
|
||||||
"Jobs": "工作",
|
|
||||||
"go_to_chat": "前往聊天",
|
|
||||||
"See all events": "查看所有事件",
|
|
||||||
"You have used": "您已使用",
|
|
||||||
"of your available spots": "的可用座位",
|
|
||||||
"Paid": "已付款",
|
|
||||||
"Canceled": "已取消",
|
|
||||||
"Pending": "待付款",
|
|
||||||
"Expiring": "已過期",
|
|
||||||
"Refunded": "已退款",
|
|
||||||
"Total": "總計",
|
|
||||||
"Total paid": "已付款",
|
|
||||||
"Total cancelled": "已取消",
|
|
||||||
"Total pending": "待付款",
|
|
||||||
"year": "年",
|
|
||||||
"month": "月",
|
|
||||||
"week": "週",
|
|
||||||
"day": "日",
|
|
||||||
"hour": "小時",
|
|
||||||
"minute": "分鐘",
|
|
||||||
"second": "秒",
|
|
||||||
"ago": "前",
|
|
||||||
"remaining": "剩下",
|
|
||||||
"days": "天",
|
|
||||||
"hours": "小時",
|
|
||||||
"minutes": "分鐘",
|
|
||||||
"seconds": "秒",
|
|
||||||
"Clear filters": "清除篩選",
|
|
||||||
"Type information": "輸入資訊",
|
|
||||||
"Cancel": "取消",
|
|
||||||
"Delete": "刪除",
|
|
||||||
"All": "全部",
|
|
||||||
"categories": "課程分類",
|
|
||||||
"loading": "載入中",
|
|
||||||
"Notifications": "通知",
|
|
||||||
"Mark all as read": "標記所有為已讀",
|
|
||||||
"There are no notifications": "沒有通知",
|
|
||||||
"listenings": "聽講 (Listenings)",
|
|
||||||
"question_category": "問題分類",
|
|
||||||
"question_list": "問題",
|
|
||||||
"matching_frenzy": "配對 (Matching Frenzy)",
|
|
||||||
"connective_revision": "連接詞 (Connective Revision)",
|
|
||||||
"settings": "設定",
|
|
||||||
"students": "學生",
|
|
||||||
"dashboard.lessonCategories.title": "課程分類",
|
|
||||||
"dashboard.lessonCategorys.edit.name": "課程分類名稱",
|
|
||||||
"dashboard.lessonCategorys.edit.position": "課程分類順序",
|
|
||||||
"dashboard.lessonCategorys.edit.title": "編輯課程分類",
|
|
||||||
"dashboard.lessonCategorys.edit.visibleToUser": "顯示給使用者",
|
|
||||||
"dashboard.lessonCategorys.edit.visible": "顯示",
|
|
||||||
"dashboard.lessonCategorys.edit.hidden": "隱藏",
|
|
||||||
"dashboard.lessonCategorys.edit.cancelButton": "取消",
|
|
||||||
"dashboard.lessonCategorys.edit.updateButton": "更新",
|
|
||||||
"dashboard.lessonCategorys.list.title": "課堂種類",
|
|
||||||
"word-count": "字數",
|
|
||||||
"unable-to-process-request": "無法處理請求",
|
|
||||||
"detailed-error-information": "詳細錯誤資訊",
|
|
||||||
"error": {
|
|
||||||
"unable-to-process-request": "無法處理您的請求",
|
|
||||||
"detailed-error-information": "詳細錯誤資訊"
|
|
||||||
},
|
|
||||||
"name-is-required": "名稱為必填",
|
|
||||||
"listening-practice": "聽講練習"
|
|
||||||
}
|
}
|
@@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
"add": "新增課程分類",
|
|
||||||
"title": "課程分類",
|
|
||||||
"basic_info": "基本資訊",
|
|
||||||
"error": {
|
|
||||||
"unable-to-process-request": "無法處理您的請求",
|
|
||||||
"detailed-error-information": "詳細錯誤資訊"
|
|
||||||
},
|
|
||||||
"create": {
|
|
||||||
"title": "建立課程類型",
|
|
||||||
"createButton": "建立",
|
|
||||||
"cancelButton": "取消",
|
|
||||||
"message": "請輸入課程類型名稱",
|
|
||||||
"success": "課程類型建立成功",
|
|
||||||
"error": "課程類型建立失敗",
|
|
||||||
"typeInformation": "輸入課程類型資訊",
|
|
||||||
"avatar": "上傳課程類型圖片",
|
|
||||||
"avatar_select": "選擇圖片",
|
|
||||||
"avatarRequirements": "最小 400x400 像素,PNG 或 JPEG",
|
|
||||||
"select": "請選擇",
|
|
||||||
"name": "課程類型名稱",
|
|
||||||
"position": "課程類型順序",
|
|
||||||
"visibleToUser": "顯示給使用者",
|
|
||||||
"detail-information": "詳細資訊",
|
|
||||||
"description": "課程類型描述",
|
|
||||||
"remarks": "備註"
|
|
||||||
},
|
|
||||||
"edit": {
|
|
||||||
"avatar": "上傳圖片",
|
|
||||||
"avatarRequirements": "最小 400x400 像素,PNG 或 JPEG",
|
|
||||||
"cancelButton": "取消",
|
|
||||||
"error": "課程類型更新失敗",
|
|
||||||
"hidden": "隱藏",
|
|
||||||
"name": "課程分類名稱",
|
|
||||||
"position": "課程分類順序",
|
|
||||||
"select": "請選擇",
|
|
||||||
"success": "課程類型更新成功",
|
|
||||||
"title": "編輯課程分類",
|
|
||||||
"basic-info": "基本資訊",
|
|
||||||
"type": "類型",
|
|
||||||
"updateButton": "更新",
|
|
||||||
"visible": "顯示",
|
|
||||||
"visibleToUser": "顯示給使用者",
|
|
||||||
"write-something": "輸入一些內容"
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"title": "刪除課程類型",
|
|
||||||
"message": "確定要刪除課程類型嗎?",
|
|
||||||
"success": "課程類型刪除成功",
|
|
||||||
"error": "課程類型刪除失敗",
|
|
||||||
"cancelButton": "取消",
|
|
||||||
"deleteButton": "刪除"
|
|
||||||
},
|
|
||||||
"list": {
|
|
||||||
"title": "課堂種類",
|
|
||||||
"message": "請選擇課程類型",
|
|
||||||
"empty": {
|
|
||||||
"title": "目前沒有課程類型",
|
|
||||||
"message": "請建立課程類型",
|
|
||||||
"create": "建立課程類型"
|
|
||||||
},
|
|
||||||
"error": "課程類型載入失敗",
|
|
||||||
"add": "新增課程分類"
|
|
||||||
},
|
|
||||||
"view": {
|
|
||||||
"basic-details": "基本資訊"
|
|
||||||
},
|
|
||||||
"Delete Lesson Type ?": "刪除課程類型?",
|
|
||||||
"Are you sure you want to delete lesson type ?": "確定要刪除課程類型嗎?"
|
|
||||||
}
|
|
@@ -1,65 +0,0 @@
|
|||||||
{
|
|
||||||
"add": "新增課程類型",
|
|
||||||
"title": "課程類型",
|
|
||||||
"type": "類型",
|
|
||||||
"error": {
|
|
||||||
"unable-to-process-request": "無法處理您的請求",
|
|
||||||
"detailed-error-information": "詳細錯誤資訊"
|
|
||||||
},
|
|
||||||
"create": {
|
|
||||||
"title": "建立課程類型",
|
|
||||||
"createButton": "建立",
|
|
||||||
"cancelButton": "取消",
|
|
||||||
"message": "請輸入課程類型名稱",
|
|
||||||
"success": "課程類型建立成功",
|
|
||||||
"error": "課程類型建立失敗",
|
|
||||||
"typeInformation": "輸入課程類型資訊",
|
|
||||||
"avatar": "上傳課程類型圖片",
|
|
||||||
"avatarRequirements": "上傳課程類型圖片",
|
|
||||||
"select": "請選擇",
|
|
||||||
"name": "課程類型名稱",
|
|
||||||
"position": "課程類型順序",
|
|
||||||
"visibleToUser": "顯示給使用者"
|
|
||||||
},
|
|
||||||
"edit": {
|
|
||||||
"avatar": "上傳課程類型圖片",
|
|
||||||
"avatarRequirements": "上傳課程類型圖片",
|
|
||||||
"cancelButton": "取消",
|
|
||||||
"error": "課程類型更新失敗",
|
|
||||||
"hidden": "隱藏",
|
|
||||||
"name": "課程類型名稱",
|
|
||||||
"position": "課程類型順序",
|
|
||||||
"select": "請選擇",
|
|
||||||
"success": "課程類型更新成功",
|
|
||||||
"title": "編輯課程類型",
|
|
||||||
"typeInformation": "輸入課程類型資訊",
|
|
||||||
"updateButton": "更新",
|
|
||||||
"visible": "顯示",
|
|
||||||
"visibleToUser": "顯示給使用者"
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"title": "刪除課程類型",
|
|
||||||
"message": "確定要刪除課程類型嗎?",
|
|
||||||
"success": "課程類型刪除成功",
|
|
||||||
"error": "課程類型刪除失敗",
|
|
||||||
"cancelButton": "取消",
|
|
||||||
"deleteButton": "刪除"
|
|
||||||
},
|
|
||||||
"list": {
|
|
||||||
"title": "課程類型列表",
|
|
||||||
"message": "請選擇課程類型",
|
|
||||||
"empty": {
|
|
||||||
"title": "目前沒有課程類型",
|
|
||||||
"message": "請建立課程類型",
|
|
||||||
"create": "建立課程類型"
|
|
||||||
},
|
|
||||||
"error": "課程類型載入失敗"
|
|
||||||
},
|
|
||||||
"view": {
|
|
||||||
"basic-details": "基本資訊"
|
|
||||||
},
|
|
||||||
"Lesson Type": "課程類型",
|
|
||||||
"Lesson Types": "課程類型",
|
|
||||||
"Delete Lesson Type ?": "刪除課程類型?",
|
|
||||||
"Are you sure you want to delete lesson type ?": "確定要刪除課程類型嗎?"
|
|
||||||
}
|
|
@@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"add": "新增",
|
|
||||||
"listening-practice": "聽力練習",
|
|
||||||
"list": {
|
|
||||||
"title": "聽力練習分類列表",
|
|
||||||
"empty": "沒有找到聽力練習分類",
|
|
||||||
"action": "動作?",
|
|
||||||
"basic-details": "基本資料"
|
|
||||||
},
|
|
||||||
"helloworld": "listening_practice",
|
|
||||||
"title": "聽力練習分類",
|
|
||||||
"type": "聽力練習分類",
|
|
||||||
"error": {
|
|
||||||
"invalid": "無效的聽力練習分類",
|
|
||||||
"not_found": "未找到聽力練習分類"
|
|
||||||
},
|
|
||||||
"create": {
|
|
||||||
"title": "創建聽力練習分類",
|
|
||||||
"success": "聽力練習分類創建成功",
|
|
||||||
"error": "創建聽力練習分類時出錯"
|
|
||||||
},
|
|
||||||
"edit": {
|
|
||||||
"title": "編輯聽力練習分類",
|
|
||||||
"success": "聽力練習分類更新成功",
|
|
||||||
"error": "更新聽力練習分類時出錯"
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"title": "刪除聽力練習分類",
|
|
||||||
"confirm": "確定要刪除聽力練習分類嗎?",
|
|
||||||
"success": "聽力練習分類刪除成功",
|
|
||||||
"error": "刪除聽力練習分類時出錯"
|
|
||||||
},
|
|
||||||
"view": {
|
|
||||||
"title": "查看聽力練習分類詳情"
|
|
||||||
}
|
|
||||||
}
|
|
3
002_source/cms/public/locales/dev/teachers.json
Normal file
3
002_source/cms/public/locales/dev/teachers.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"back-to-list": "Teachers"
|
||||||
|
}
|
@@ -1,200 +0,0 @@
|
|||||||
{
|
|
||||||
"languageChanged": "語言已更改",
|
|
||||||
"type": "類型",
|
|
||||||
"helloworld": "你好,世界",
|
|
||||||
"Add": "新增項目",
|
|
||||||
"visible": "可視",
|
|
||||||
"hidden": "屏閉",
|
|
||||||
"Hidden": "屏閉",
|
|
||||||
"Newest": "最新",
|
|
||||||
"Oldest": "最舊",
|
|
||||||
"Lesson position": "位置",
|
|
||||||
"Lesson type": "種類",
|
|
||||||
"Type": "種類",
|
|
||||||
"Name": "名稱",
|
|
||||||
"Filter by name": "依名稱過濾",
|
|
||||||
"Filter by type": "依類型過濾",
|
|
||||||
"dashboard.lessonTypes.add": "新增課程類型",
|
|
||||||
"dashboard.lessonTypes.title": "課程類型",
|
|
||||||
"dashboard.lessonTypes.type": "類型",
|
|
||||||
"dashboard.lessonTypes.create.title": "建立課程類型",
|
|
||||||
"dashboard.lessonTypes.create.createButton": "建立",
|
|
||||||
"dashboard.lessonTypes.create.cancelButton": "取消",
|
|
||||||
"dashboard.lessonTypes.edit.title": "編輯課程類型",
|
|
||||||
"Actions": "動作",
|
|
||||||
"Created at": "建立時間",
|
|
||||||
"Updated at": "更新時間",
|
|
||||||
"dashboard.lessonTypes.edit": "編輯課程類型",
|
|
||||||
"dashboard.lessonTypes.delete": "刪除課程類型",
|
|
||||||
"dashboard.lessonTypes.delete.title": "刪除課程類型",
|
|
||||||
"dashboard.lessonTypes.delete.message": "確定要刪除課程類型嗎?",
|
|
||||||
"Lesson Type": "課程類型",
|
|
||||||
"dashboard.lessonTypes.create": "建立課程類型",
|
|
||||||
"dashboard.lessonTypes.create.message": "請輸入課程類型名稱",
|
|
||||||
"dashboard.lessonTypes.create.success": "課程類型建立成功",
|
|
||||||
"dashboard.lessonTypes.create.error": "課程類型建立失敗",
|
|
||||||
"dashboard.lessonTypes.edit.success": "課程類型更新成功",
|
|
||||||
"dashboard.lessonTypes.edit.error": "課程類型更新失敗",
|
|
||||||
"dashboard.lessonTypes.delete.success": "課程類型刪除成功",
|
|
||||||
"dashboard.lessonTypes.delete.error": "課程類型刪除失敗",
|
|
||||||
"dashboard.lessonTypes.list": "課程類型列表",
|
|
||||||
"dashboard.lessonTypes.list.title": "課程類型列表",
|
|
||||||
"dashboard.lessonTypes.list.message": "請選擇課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty": "目前沒有課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty.title": "目前沒有課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty.message": "請建立課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty.create": "建立課程類型",
|
|
||||||
"Lesson Name": "課程名稱",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.title": "建立課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.message": "請輸入課程名稱",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.success": "課程建立成功",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.error": "課程建立失敗",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.name": "課程名稱",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.type": "課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.type.placeholder": "請選擇課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.type.title": "課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.type.message": "請選擇課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.type.empty": "目前沒有課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.type.empty.title": "目前沒有課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.type.empty.message": "請建立課程類型",
|
|
||||||
"dashboard.lessonTypes.list.empty.create.type.empty.create": "建立課程類型",
|
|
||||||
"Position": "位置",
|
|
||||||
"Visible": "顯示",
|
|
||||||
"Lesson Types": "課程類型",
|
|
||||||
"Apply": "套用",
|
|
||||||
"Overview": "概觀",
|
|
||||||
"Dashboard": "儀表板",
|
|
||||||
"Tickets": "票券",
|
|
||||||
"Sign ups": "註冊",
|
|
||||||
"Open issues": "開啟問題",
|
|
||||||
"Closed issues": "關閉問題",
|
|
||||||
"increase": "增加",
|
|
||||||
"decrease": "減少",
|
|
||||||
"common.loading": "載入中",
|
|
||||||
"vs last month": "與上個月相比",
|
|
||||||
"Dashboards": "儀表板",
|
|
||||||
"App usage": "應用程式使用情況",
|
|
||||||
"Find your dream job": "找到你的夢想工作",
|
|
||||||
"Need help figuring things out?": "需要幫助嗎?",
|
|
||||||
"Search for jobs that match your skills and apply to them directly": "搜索符合你技能的工作,直接申請",
|
|
||||||
"Find answers to your questions and get in touch with our team.": "找到你的問題並與我們的團隊聯繫",
|
|
||||||
"Explore documentation": "探索文件",
|
|
||||||
"Learn how to get started with our product and make the most of it.": "學習如何開始使用我們的產品並充分利用它",
|
|
||||||
"Search Jobs": "搜索工作",
|
|
||||||
"Help Center": "幫助中心",
|
|
||||||
"Documentation": "文件",
|
|
||||||
"increase_in_app_usage_with": "使用者量增加",
|
|
||||||
"new_products_purchased": "新產品購買",
|
|
||||||
"Our subscriptions": "我們的訂閱",
|
|
||||||
"this_year": "今年",
|
|
||||||
"is_forecasted_to_increase_in_your_traffic_by_the_end_of_the_current_month": "今年預計增加你的流量",
|
|
||||||
"Jan": "一月",
|
|
||||||
"Feb": "二月",
|
|
||||||
"Mar": "三月",
|
|
||||||
"Apr": "四月",
|
|
||||||
"May": "五月",
|
|
||||||
"Jun": "六月",
|
|
||||||
"Jul": "七月",
|
|
||||||
"Aug": "八月",
|
|
||||||
"Sep": "九月",
|
|
||||||
"Oct": "十月",
|
|
||||||
"Nov": "十一月",
|
|
||||||
"Dec": "十二月",
|
|
||||||
"See all subscriptions": "查看所有訂閱",
|
|
||||||
"app_chat": "應用程式聊天",
|
|
||||||
"Upcoming events": "即將發生的事件",
|
|
||||||
"Based on the linked bank accounts": "基於連結的銀行帳戶",
|
|
||||||
"App limits": "應用程式限制",
|
|
||||||
"You have used {percentage} of your available spots.": "您已使用 {percentage} 的可用座位。升級計劃以建立更多專案",
|
|
||||||
"userMessagesUnread": "hello userMessagesUnread",
|
|
||||||
"Upgrade plan to create more projects": "升級計劃以建立更多專案",
|
|
||||||
"You have almost reached your limit": "您已接近您的限制",
|
|
||||||
"Upgrade plan": "升級計劃",
|
|
||||||
"Help center": "幫助中心",
|
|
||||||
"Jobs": "工作",
|
|
||||||
"go_to_chat": "前往聊天",
|
|
||||||
"See all events": "查看所有事件",
|
|
||||||
"You have used": "您已使用",
|
|
||||||
"of your available spots": "的可用座位",
|
|
||||||
"Paid": "已付款",
|
|
||||||
"Canceled": "已取消",
|
|
||||||
"Pending": "待付款",
|
|
||||||
"Expiring": "已過期",
|
|
||||||
"Refunded": "已退款",
|
|
||||||
"Total": "總計",
|
|
||||||
"Total paid": "已付款",
|
|
||||||
"Total cancelled": "已取消",
|
|
||||||
"Total pending": "待付款",
|
|
||||||
"year": "年",
|
|
||||||
"month": "月",
|
|
||||||
"week": "週",
|
|
||||||
"day": "日",
|
|
||||||
"hour": "小時",
|
|
||||||
"minute": "分鐘",
|
|
||||||
"second": "秒",
|
|
||||||
"ago": "前",
|
|
||||||
"remaining": "剩下",
|
|
||||||
"days": "天",
|
|
||||||
"hours": "小時",
|
|
||||||
"minutes": "分鐘",
|
|
||||||
"seconds": "秒",
|
|
||||||
"Clear filters": "清除篩選",
|
|
||||||
"Type information": "輸入資訊",
|
|
||||||
"dashboard.lessonTypes.create.typeInformation": "輸入課程類型資訊",
|
|
||||||
"dashboard.lessonTypes.create.avatar": "上傳課程類型圖片",
|
|
||||||
"dashboard.lessonTypes.create.avatarRequirements": "上傳課程類型圖片",
|
|
||||||
"dashboard.lessonTypes.create.select": "請選擇",
|
|
||||||
"dashboard.lessonTypes.create.name": "課程類型名稱",
|
|
||||||
"dashboard.lessonTypes.create.type": "課程類型",
|
|
||||||
"dashboard.lessonTypes.create.position": "課程類型順序",
|
|
||||||
"dashboard.lessonTypes.create.visibleToUser": "顯示給使用者",
|
|
||||||
"dashboard.lessonTypes.edit.typeInformation": "輸入課程類型資訊",
|
|
||||||
"dashboard.lessonTypes.edit.avatar": "上傳課程類型圖片",
|
|
||||||
"dashboard.lessonTypes.edit.avatarRequirements": "上傳課程類型圖片",
|
|
||||||
"dashboard.lessonTypes.edit.select": "請選擇",
|
|
||||||
"dashboard.lessonTypes.edit.name": "課程類型名稱",
|
|
||||||
"dashboard.lessonTypes.edit.type": "課程類型",
|
|
||||||
"dashboard.lessonTypes.edit.position": "課程類型順序",
|
|
||||||
"dashboard.lessonTypes.edit.visibleToUser": "顯示給使用者",
|
|
||||||
"dashboard.lessonTypes.edit.visible": "顯示",
|
|
||||||
"dashboard.lessonTypes.edit.hidden": "隱藏",
|
|
||||||
"dashboard.lessonTypes.edit.cancelButton": "取消",
|
|
||||||
"dashboard.lessonTypes.edit.updateButton": "更新",
|
|
||||||
"Delete Lesson Type ?": "刪除課程類型?",
|
|
||||||
"Are you sure you want to delete lesson type ?": "確定要刪除課程類型嗎?",
|
|
||||||
"Cancel": "取消",
|
|
||||||
"Delete": "刪除",
|
|
||||||
"All": "全部",
|
|
||||||
"categories": "問題分類",
|
|
||||||
"listening-practice": "聽講練習 (LP)",
|
|
||||||
"matching-frenzy": "配對練習 (MF)",
|
|
||||||
"connective-revision": "連接詞練習 (CR)",
|
|
||||||
"teachers": "導師",
|
|
||||||
"questions": "題目",
|
|
||||||
"loading": "載入中",
|
|
||||||
"Notifications": "通知",
|
|
||||||
"Mark all as read": "標記所有為已讀",
|
|
||||||
"There are no notifications": "沒有通知",
|
|
||||||
"listenings": "聽講 (Listenings)",
|
|
||||||
"question_category": "問題分類",
|
|
||||||
"question_list": "問題",
|
|
||||||
"matching_frenzy": "配對 (Matching Frenzy)",
|
|
||||||
"connective_revision": "連接詞 (Connective Revision)",
|
|
||||||
"settings": "設定",
|
|
||||||
"students": "學生",
|
|
||||||
"dashboard.lessonCategories.add": "新增課程分類",
|
|
||||||
"dashboard.lessonCategories.title": "課程分類",
|
|
||||||
"dashboard.lessonCategorys.edit.name": "課程分類名稱",
|
|
||||||
"dashboard.lessonCategorys.edit.position": "課程分類順序",
|
|
||||||
"dashboard.lessonCategorys.edit.title": "編輯課程分類",
|
|
||||||
"dashboard.lessonCategorys.edit.visibleToUser": "顯示給使用者",
|
|
||||||
"dashboard.lessonCategorys.edit.visible": "顯示",
|
|
||||||
"dashboard.lessonCategorys.edit.hidden": "隱藏",
|
|
||||||
"dashboard.lessonCategorys.edit.cancelButton": "取消",
|
|
||||||
"dashboard.lessonCategorys.edit.updateButton": "更新",
|
|
||||||
"dashboard.lessonCategorys.list.title": "課堂種類",
|
|
||||||
"word-count": "字數",
|
|
||||||
"dashboard.lessonTypes.list.error": "課程類型載入失敗",
|
|
||||||
"unable-to-process-request": "無法處理請求",
|
|
||||||
"detailed-error-information": "詳細錯誤資訊"
|
|
||||||
}
|
|
3
002_source/cms/public/locales/en/teachers.json
Normal file
3
002_source/cms/public/locales/en/teachers.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"back-to-list": "Teachers"
|
||||||
|
}
|
@@ -1,3 +1,7 @@
|
|||||||
{
|
{
|
||||||
"languageChanged": "Language changed"
|
"languageChanged": "Language changed",
|
||||||
|
"teachers": {
|
||||||
|
"nav-title": "teachers",
|
||||||
|
"back-to-list": "Teachers"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
3
002_source/cms/public/locales/es/teachers.json
Normal file
3
002_source/cms/public/locales/es/teachers.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"back-to-list": "Teachers"
|
||||||
|
}
|
@@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"languageChanged": "Idioma cambiado"
|
"languageChanged": "Idioma cambiado",
|
||||||
|
"teachers": {
|
||||||
|
"back-to-list": "Profesores"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,5 +2,4 @@
|
|||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
rm -rf .next
|
|
||||||
pnpm run dev
|
pnpm run dev
|
||||||
|
48
002_source/cms/src/app/dashboard/connectives/create/page.tsx
Normal file
48
002_source/cms/src/app/dashboard/connectives/create/page.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
'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 { LessonCategoryCreateForm } from '@/components/dashboard/lesson_category/lesson-category-create-form';
|
||||||
|
|
||||||
|
export default function Page(): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: 'var(--Content-maxWidth)',
|
||||||
|
m: 'var(--Content-margin)',
|
||||||
|
p: 'var(--Content-padding)',
|
||||||
|
width: 'var(--Content-width)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
color="text.primary"
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.dashboard.lesson_categories.list}
|
||||||
|
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
|
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
|
{t('title', { ns: 'lesson_category' })}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography variant="h4">{t('create.title', { ns: 'lesson_category' })}</Typography>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
<LessonCategoryCreateForm />
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
'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 { LessonCategoryEditForm } from '@/components/dashboard/lesson_category/lesson-category-edit-form';
|
||||||
|
|
||||||
|
export default function Page(): React.JSX.Element {
|
||||||
|
const { t } = useTranslation(['lesson_category']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: 'var(--Content-maxWidth)',
|
||||||
|
m: 'var(--Content-margin)',
|
||||||
|
p: 'var(--Content-padding)',
|
||||||
|
width: 'var(--Content-width)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
color="text.primary"
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.dashboard.lesson_categories.list}
|
||||||
|
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
|
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
|
{t('edit.title', { ns: 'lesson_category' })}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography variant="h4">{t('edit.title', { ns: 'lesson_category' })}</Typography>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
<LessonCategoryEditForm />
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@@ -0,0 +1,95 @@
|
|||||||
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
import { LessonCategory } from '@/components/dashboard/lesson_category/type';
|
||||||
|
|
||||||
|
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type';
|
||||||
|
|
||||||
|
// import type { LessonCategory } from '@/components/dashboard/lesson_category/lesson-categories-table';
|
||||||
|
// import type { LessonCategory } from '@/components/dashboard/lesson_category/interfaces';
|
||||||
|
|
||||||
|
export const lessonCategoriesSampleData = [
|
||||||
|
{
|
||||||
|
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(),
|
||||||
|
collectionId: '0000000001',
|
||||||
|
cat_name: '',
|
||||||
|
pos: 99,
|
||||||
|
visible: 'visible',
|
||||||
|
lesson_id: 'lid_00001',
|
||||||
|
description: '',
|
||||||
|
remarks: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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(),
|
||||||
|
collectionId: '0000000001',
|
||||||
|
cat_name: '',
|
||||||
|
pos: 99,
|
||||||
|
visible: 'visible',
|
||||||
|
lesson_id: 'lid_00001',
|
||||||
|
description: '',
|
||||||
|
remarks: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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(),
|
||||||
|
collectionId: '0000000001',
|
||||||
|
cat_name: '',
|
||||||
|
pos: 99,
|
||||||
|
visible: 'visible',
|
||||||
|
lesson_id: 'lid_00001',
|
||||||
|
description: '',
|
||||||
|
remarks: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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(),
|
||||||
|
collectionId: '0000000001',
|
||||||
|
cat_name: '',
|
||||||
|
pos: 99,
|
||||||
|
visible: 'visible',
|
||||||
|
lesson_id: 'lid_00001',
|
||||||
|
description: '',
|
||||||
|
remarks: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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(),
|
||||||
|
collectionId: '0000000001',
|
||||||
|
cat_name: '',
|
||||||
|
pos: 99,
|
||||||
|
visible: 'visible',
|
||||||
|
lesson_id: 'lid_00001',
|
||||||
|
description: '',
|
||||||
|
remarks: '',
|
||||||
|
},
|
||||||
|
] satisfies LessonCategory[];
|
@@ -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
302
002_source/cms/src/app/dashboard/lesson_categories/list/page.tsx
Normal file
302
002_source/cms/src/app/dashboard/lesson_categories/list/page.tsx
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
// RULES:
|
||||||
|
// contains list page for lesson_categories (LessonCategories)
|
||||||
|
// contain definition to collection only
|
||||||
|
//
|
||||||
|
import * as React from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { COL_LESSON_CATEGORIES } 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 { 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 { toast } from '@/components/core/toaster';
|
||||||
|
import ErrorDisplay from '@/components/dashboard/error';
|
||||||
|
import { defaultLessonCategory } from '@/components/dashboard/lesson_category/_constants';
|
||||||
|
import { LessonCategoriesFilters } from '@/components/dashboard/lesson_category/lesson-categories-filters';
|
||||||
|
import type { Filters } from '@/components/dashboard/lesson_category/lesson-categories-filters';
|
||||||
|
import { LessonCategoriesPagination } from '@/components/dashboard/lesson_category/lesson-categories-pagination';
|
||||||
|
import { LessonCategoriesSelectionProvider } from '@/components/dashboard/lesson_category/lesson-categories-selection-context';
|
||||||
|
import { LessonCategoriesTable } from '@/components/dashboard/lesson_category/lesson-categories-table';
|
||||||
|
import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
|
||||||
|
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type';
|
||||||
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
|
// import { lessonCategoriesSampleData } from './lesson-categories-sample-data';
|
||||||
|
|
||||||
|
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||||
|
const { t } = useTranslation(['lesson_category']);
|
||||||
|
const { email, phone, sortDir, status, name, visible, type } = searchParams;
|
||||||
|
const router = useRouter();
|
||||||
|
const [lessonCategoriesData, setLessonCategoriesData] = React.useState<LessonCategory[]>([]);
|
||||||
|
//
|
||||||
|
|
||||||
|
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
||||||
|
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||||
|
const [showError, setShowError] = React.useState({ show: false, detail: '' });
|
||||||
|
//
|
||||||
|
const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
|
||||||
|
//
|
||||||
|
const [f, setF] = React.useState<LessonCategory[]>([]);
|
||||||
|
const [currentPage, setCurrentPage] = React.useState<number>(1);
|
||||||
|
//
|
||||||
|
const [recordCount, setRecordCount] = React.useState<number>(0);
|
||||||
|
const [listOption, setListOption] = React.useState({});
|
||||||
|
const [listSort, setListSort] = React.useState({});
|
||||||
|
|
||||||
|
//
|
||||||
|
const sortedLessonCategories = applySort(lessonCategoriesData, sortDir);
|
||||||
|
const filteredLessonCategories = applyFilters(sortedLessonCategories, { email, phone, status });
|
||||||
|
|
||||||
|
//
|
||||||
|
const reloadRows = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const models: ListResult<RecordModel> = await pb
|
||||||
|
.collection(COL_LESSON_CATEGORIES)
|
||||||
|
.getList(currentPage + 1, rowsPerPage, listOption);
|
||||||
|
const { items, totalItems } = models;
|
||||||
|
const tempLessonTypes: LessonCategory[] = items.map((lt) => {
|
||||||
|
return { ...defaultLessonCategory, ...lt };
|
||||||
|
});
|
||||||
|
|
||||||
|
setLessonCategoriesData(tempLessonTypes);
|
||||||
|
setRecordCount(totalItems);
|
||||||
|
setF(tempLessonTypes);
|
||||||
|
// console.log({ currentPage, f });
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
logger.error(error);
|
||||||
|
setShowError({
|
||||||
|
//
|
||||||
|
show: true,
|
||||||
|
detail: JSON.stringify(error, null, 2),
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setShowLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pb.collection(COL_LESSON_CATEGORIES)
|
||||||
|
// .getList(currentPage, rowsPerPage, listOption)
|
||||||
|
// .then((lessonCategories: ListResult<RecordModel>) => {
|
||||||
|
// // console.log(lessonTypes);
|
||||||
|
// const { items, page, perPage, totalItems, totalPages } = lessonCategories;
|
||||||
|
// const tempLessonCategories: LessonCategory[] = items.map((item) => {
|
||||||
|
// return { ...defaultLessonCategory, ...item };
|
||||||
|
// });
|
||||||
|
|
||||||
|
// setLessonCategoriesData(tempLessonCategories);
|
||||||
|
// setRecordCount(totalItems);
|
||||||
|
// setF(tempLessonCategories);
|
||||||
|
// // console.log({ currentPage, f });
|
||||||
|
// })
|
||||||
|
// .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(() => {
|
||||||
|
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}%"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let preFinalListOption = {};
|
||||||
|
if (tempFilter.length > 0) {
|
||||||
|
preFinalListOption = { filter: tempFilter.join(' && ') };
|
||||||
|
}
|
||||||
|
if (tempSortDir.length > 0) {
|
||||||
|
preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
|
||||||
|
}
|
||||||
|
setListOption(preFinalListOption);
|
||||||
|
// setListOption({
|
||||||
|
// filter: tempFilter.join(' && '),
|
||||||
|
// sort: tempSortDir,
|
||||||
|
// //
|
||||||
|
// });
|
||||||
|
}, [visible, sortDir, name, type]);
|
||||||
|
|
||||||
|
// return <>helloworld</>;
|
||||||
|
|
||||||
|
if (showLoading) return <FormLoading />;
|
||||||
|
|
||||||
|
if (showError.show)
|
||||||
|
return (
|
||||||
|
<ErrorDisplay
|
||||||
|
message={t('error.unable-to-process-request')}
|
||||||
|
code="500"
|
||||||
|
details={showError.detail}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: 'var(--Content-maxWidth)',
|
||||||
|
m: 'var(--Content-margin)',
|
||||||
|
p: 'var(--Content-padding)',
|
||||||
|
width: 'var(--Content-width)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Stack
|
||||||
|
direction={{ xs: 'column', sm: 'row' }}
|
||||||
|
spacing={3}
|
||||||
|
sx={{ alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
|
<Box sx={{ flex: '1 1 auto' }}>
|
||||||
|
<Typography variant="h4">{t('list.title')}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
|
<LoadingButton
|
||||||
|
loading={isLoadingAddPage}
|
||||||
|
onClick={(): void => {
|
||||||
|
setIsLoadingAddPage(true);
|
||||||
|
router.push(paths.dashboard.lesson_categories.create);
|
||||||
|
}}
|
||||||
|
startIcon={<PlusIcon />}
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{/* add new lesson type */}
|
||||||
|
{t('list.add')}
|
||||||
|
</LoadingButton>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
<LessonCategoriesSelectionProvider lessonCategories={f}>
|
||||||
|
<Card>
|
||||||
|
<LessonCategoriesFilters
|
||||||
|
filters={{ email, phone, status, name, visible, type }}
|
||||||
|
fullData={lessonCategoriesData}
|
||||||
|
sortDir={sortDir}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
<Box sx={{ overflowX: 'auto' }}>
|
||||||
|
<LessonCategoriesTable
|
||||||
|
reloadRows={reloadRows}
|
||||||
|
rows={f}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Divider />
|
||||||
|
<LessonCategoriesPagination
|
||||||
|
count={recordCount}
|
||||||
|
page={currentPage}
|
||||||
|
rowsPerPage={rowsPerPage}
|
||||||
|
setPage={setCurrentPage}
|
||||||
|
setRowsPerPage={setRowsPerPage}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</LessonCategoriesSelectionProvider>
|
||||||
|
</Stack>
|
||||||
|
<Box sx={{ display: isDevelopment ? 'block' : 'none' }}>
|
||||||
|
<pre>{JSON.stringify(f, null, 2)}</pre>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorting and filtering has to be done on the server.
|
||||||
|
|
||||||
|
function applySort(row: LessonCategory[], sortDir: 'asc' | 'desc' | undefined): LessonCategory[] {
|
||||||
|
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: LessonCategory[], { email, phone, status, name, visible }: Filters): LessonCategory[] {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
if (!item.name?.toLowerCase().includes(name.toLowerCase())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
if (!item.visible?.toLowerCase().includes(visible.toLowerCase())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
searchParams: {
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
sortDir?: 'asc' | 'desc';
|
||||||
|
status?: string;
|
||||||
|
name?: string;
|
||||||
|
visible?: string;
|
||||||
|
type?: string;
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,383 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
// import type { Metadata } from 'next';
|
||||||
|
import RouterLink from 'next/link';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { COL_LESSON_CATEGORIES } from '@/constants';
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Card from '@mui/material/Card';
|
||||||
|
import CardContent from '@mui/material/CardContent';
|
||||||
|
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 LinearProgress from '@mui/material/LinearProgress';
|
||||||
|
import Link from '@mui/material/Link';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Grid from '@mui/material/Unstable_Grid2';
|
||||||
|
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||||
|
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
|
||||||
|
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
|
||||||
|
import { CreditCard as CreditCardIcon } from '@phosphor-icons/react/dist/ssr/CreditCard';
|
||||||
|
import { House as HouseIcon } from '@phosphor-icons/react/dist/ssr/House';
|
||||||
|
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
|
||||||
|
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
|
||||||
|
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
|
||||||
|
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
|
||||||
|
import { RecordModel } from 'pocketbase';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { paths } from '@/paths';
|
||||||
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
import { logger } from '@/lib/default-logger';
|
||||||
|
import { pb } from '@/lib/pb';
|
||||||
|
import { PropertyItem } from '@/components/core/property-item';
|
||||||
|
import { PropertyList } from '@/components/core/property-list';
|
||||||
|
import { toast } from '@/components/core/toaster';
|
||||||
|
import ErrorDisplay from '@/components/dashboard/error';
|
||||||
|
import { defaultLessonCategory } from '@/components/dashboard/lesson_category/_constants.ts';
|
||||||
|
// import { defaultLessonCategory } from '@/components/dashboard/lesson_category/defaultLessonCategory';
|
||||||
|
import { Notifications } from '@/components/dashboard/lesson_category/notifications';
|
||||||
|
import { Payments } from '@/components/dashboard/lesson_category/payments';
|
||||||
|
import type { Address } from '@/components/dashboard/lesson_category/shipping-address';
|
||||||
|
import { ShippingAddress } from '@/components/dashboard/lesson_category/shipping-address';
|
||||||
|
import { LessonCategory } from '@/components/dashboard/lesson_category/type';
|
||||||
|
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type';
|
||||||
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
|
// export const metadata = { title: `Details | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
|
||||||
|
|
||||||
|
export default function Page(): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const router = useRouter();
|
||||||
|
//
|
||||||
|
const { cat_id: catId } = useParams<{ cat_id: string }>();
|
||||||
|
//
|
||||||
|
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||||
|
const [showError, setShowError] = React.useState<boolean>(false);
|
||||||
|
//
|
||||||
|
const [showLessonCategory, setShowLessonCategory] = React.useState<LessonCategory>(defaultLessonCategory);
|
||||||
|
|
||||||
|
function handleEditClick() {
|
||||||
|
router.push(paths.dashboard.lesson_categories.edit(showLessonCategory.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (catId) {
|
||||||
|
pb.collection(COL_LESSON_CATEGORIES)
|
||||||
|
.getOne(catId)
|
||||||
|
.then((model: RecordModel) => {
|
||||||
|
setShowLessonCategory({ ...defaultLessonCategory, ...model });
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(err);
|
||||||
|
toast(t('dashboard.lessonTypes.list.error'));
|
||||||
|
|
||||||
|
setShowError(true);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setShowLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [catId]);
|
||||||
|
|
||||||
|
if (showLoading) return <FormLoading />;
|
||||||
|
|
||||||
|
if (showError)
|
||||||
|
return (
|
||||||
|
<ErrorDisplay
|
||||||
|
message={t('error.unable-to-process-request', { ns: 'common' })}
|
||||||
|
code="500"
|
||||||
|
details={t('error.detailed-error-information', { ns: 'common' })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: 'var(--Content-maxWidth)',
|
||||||
|
m: 'var(--Content-margin)',
|
||||||
|
p: 'var(--Content-padding)',
|
||||||
|
width: 'var(--Content-width)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
color="text.primary"
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.dashboard.lesson_categories.list}
|
||||||
|
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
|
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
|
{t('dashboard.lessonCategorys.list.title')}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
||||||
|
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}>
|
||||||
|
<Avatar
|
||||||
|
src={`http://127.0.0.1:8090/api/files/${showLessonCategory.collectionId}/${showLessonCategory.id}/${showLessonCategory.cat_image}`}
|
||||||
|
sx={{ '--Avatar-size': '64px' }}
|
||||||
|
variant="rounded"
|
||||||
|
>
|
||||||
|
empty
|
||||||
|
</Avatar>
|
||||||
|
<div>
|
||||||
|
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<Typography variant="h4">{showLessonCategory.name}</Typography>
|
||||||
|
<Chip
|
||||||
|
icon={<CheckCircleIcon color="var(--mui-palette-success-main)" weight="fill" />}
|
||||||
|
label={showLessonCategory.visible}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Typography color="text.secondary" variant="body1">
|
||||||
|
{showLessonCategory.id}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
<div>
|
||||||
|
<Button endIcon={<CaretDownIcon />} variant="contained">
|
||||||
|
Action
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Grid container spacing={4}>
|
||||||
|
<Grid lg={4} xs={12}>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Card>
|
||||||
|
<CardHeader
|
||||||
|
action={
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
handleEditClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PencilSimpleIcon />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
avatar={
|
||||||
|
<Avatar>
|
||||||
|
<UserIcon fontSize="var(--Icon-fontSize)" />
|
||||||
|
</Avatar>
|
||||||
|
}
|
||||||
|
title={t('basic-details', { ns: 'lesson_category' })}
|
||||||
|
/>
|
||||||
|
<PropertyList
|
||||||
|
divider={<Divider />}
|
||||||
|
orientation="vertical"
|
||||||
|
sx={{ '--PropertyItem-padding': '12px 24px' }}
|
||||||
|
>
|
||||||
|
{(
|
||||||
|
[
|
||||||
|
{ key: 'Customer ID', value: <Chip label={showLessonCategory.id} size="small" variant="soft" /> },
|
||||||
|
{ key: 'Name', value: showLessonCategory.name },
|
||||||
|
{ key: 'Pos', value: showLessonCategory.pos },
|
||||||
|
{
|
||||||
|
key: 'Visible',
|
||||||
|
value: (
|
||||||
|
<Chip
|
||||||
|
//
|
||||||
|
label={showLessonCategory.visible}
|
||||||
|
size="small"
|
||||||
|
variant="soft"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Quota',
|
||||||
|
value: (
|
||||||
|
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
|
||||||
|
<LinearProgress sx={{ flex: '1 1 auto' }} value={50} variant="determinate" />
|
||||||
|
<Typography color="text.secondary" variant="body2">
|
||||||
|
50%
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] satisfies { key: string; value: React.ReactNode }[]
|
||||||
|
).map(
|
||||||
|
(item): React.JSX.Element => (
|
||||||
|
<PropertyItem key={item.key} name={item.key} value={item.value} />
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</PropertyList>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader
|
||||||
|
avatar={
|
||||||
|
<Avatar>
|
||||||
|
<ShieldWarningIcon fontSize="var(--Icon-fontSize)" />
|
||||||
|
</Avatar>
|
||||||
|
}
|
||||||
|
title={t('security', { ns: 'lesson_category' })}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<div>
|
||||||
|
<Button color="error" variant="contained">
|
||||||
|
Delete account
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Typography color="text.secondary" variant="body2">
|
||||||
|
A deleted lesson category cannot be restored. All data will be permanently removed.
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid lg={8} xs={12}>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Payments
|
||||||
|
ordersValue={2069.48}
|
||||||
|
payments={[
|
||||||
|
{
|
||||||
|
currency: 'USD',
|
||||||
|
amount: 500,
|
||||||
|
invoiceId: 'INV-005',
|
||||||
|
status: 'completed',
|
||||||
|
createdAt: dayjs().subtract(5, 'minute').subtract(1, 'hour').toDate(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency: 'USD',
|
||||||
|
amount: 324.5,
|
||||||
|
invoiceId: 'INV-004',
|
||||||
|
status: 'refunded',
|
||||||
|
createdAt: dayjs().subtract(21, 'minute').subtract(2, 'hour').toDate(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency: 'USD',
|
||||||
|
amount: 746.5,
|
||||||
|
invoiceId: 'INV-003',
|
||||||
|
status: 'completed',
|
||||||
|
createdAt: dayjs().subtract(7, 'minute').subtract(3, 'hour').toDate(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency: 'USD',
|
||||||
|
amount: 56.89,
|
||||||
|
invoiceId: 'INV-002',
|
||||||
|
status: 'completed',
|
||||||
|
createdAt: dayjs().subtract(48, 'minute').subtract(4, 'hour').toDate(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency: 'USD',
|
||||||
|
amount: 541.59,
|
||||||
|
invoiceId: 'INV-001',
|
||||||
|
status: 'completed',
|
||||||
|
createdAt: dayjs().subtract(31, 'minute').subtract(5, 'hour').toDate(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
refundsValue={324.5}
|
||||||
|
totalOrders={5}
|
||||||
|
/>
|
||||||
|
<Card>
|
||||||
|
<CardHeader
|
||||||
|
action={
|
||||||
|
<Button color="secondary" startIcon={<PencilSimpleIcon />}>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
avatar={
|
||||||
|
<Avatar>
|
||||||
|
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
|
||||||
|
</Avatar>
|
||||||
|
}
|
||||||
|
title={t('billing-details', { ns: 'lesson_category' })}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<Card sx={{ borderRadius: 1 }} variant="outlined">
|
||||||
|
<PropertyList divider={<Divider />} sx={{ '--PropertyItem-padding': '16px' }}>
|
||||||
|
{(
|
||||||
|
[
|
||||||
|
{ key: 'Credit card', value: '**** 4142' },
|
||||||
|
{ key: 'Country', value: 'United States' },
|
||||||
|
{ key: 'State', value: 'Michigan' },
|
||||||
|
{ key: 'City', value: 'Southfield' },
|
||||||
|
{ key: 'Address', value: '1721 Bartlett Avenue, 48034' },
|
||||||
|
{ key: 'Tax ID', value: 'EU87956621' },
|
||||||
|
] satisfies { key: string; value: React.ReactNode }[]
|
||||||
|
).map(
|
||||||
|
(item): React.JSX.Element => (
|
||||||
|
<PropertyItem key={item.key} name={item.key} value={item.value} />
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</PropertyList>
|
||||||
|
</Card>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader
|
||||||
|
action={
|
||||||
|
<Button color="secondary" startIcon={<PlusIcon />}>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
avatar={
|
||||||
|
<Avatar>
|
||||||
|
<HouseIcon fontSize="var(--Icon-fontSize)" />
|
||||||
|
</Avatar>
|
||||||
|
}
|
||||||
|
title={t('shipping-addresses', { ns: 'lesson_category' })}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
{(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'ADR-001',
|
||||||
|
country: 'United States',
|
||||||
|
state: 'Michigan',
|
||||||
|
city: 'Lansing',
|
||||||
|
zipCode: '48933',
|
||||||
|
street: '480 Haven Lane',
|
||||||
|
primary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ADR-002',
|
||||||
|
country: 'United States',
|
||||||
|
state: 'Missouri',
|
||||||
|
city: 'Springfield',
|
||||||
|
zipCode: '65804',
|
||||||
|
street: '4807 Lighthouse Drive',
|
||||||
|
},
|
||||||
|
] satisfies Address[]
|
||||||
|
).map((address) => (
|
||||||
|
<Grid key={address.id} md={6} xs={12}>
|
||||||
|
<ShippingAddress address={address} />
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Notifications
|
||||||
|
notifications={[
|
||||||
|
{
|
||||||
|
id: 'EV-002',
|
||||||
|
type: 'Refund request approved',
|
||||||
|
status: 'pending',
|
||||||
|
createdAt: dayjs().subtract(34, 'minute').subtract(5, 'hour').subtract(3, 'day').toDate(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'EV-001',
|
||||||
|
type: 'Order confirmation',
|
||||||
|
status: 'delivered',
|
||||||
|
createdAt: dayjs().subtract(49, 'minute').subtract(11, 'hour').subtract(4, 'day').toDate(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@@ -135,7 +135,11 @@ export default function Layout({ children, params }: LayoutProps): React.JSX.Ele
|
|||||||
const filteredThreads = filterThreads(threads, labelId);
|
const filteredThreads = filterThreads(threads, labelId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MailProvider currentLabelId={labelId} labels={labels} threads={filteredThreads}>
|
<MailProvider
|
||||||
|
currentLabelId={labelId}
|
||||||
|
labels={labels}
|
||||||
|
threads={filteredThreads}
|
||||||
|
>
|
||||||
<MailView>{children}</MailView>
|
<MailView>{children}</MailView>
|
||||||
</MailProvider>
|
</MailProvider>
|
||||||
);
|
);
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import GetAllLessonCategoriesCount from '@/db/LessonCategories/GetAllCount';
|
import GetAllLessonCategoriesCount from '@/db/LessonCategories/GetAllCount';
|
||||||
import GetAllLessonTypesCount from '@/db/LessonTypes/GetAllCount';
|
import GetAllLessonTypesCount from '@/db/LessonTypes/GetAllCount';
|
||||||
import GetAllUsersCount from '@/db/Users/GetAllCount';
|
import { GetAllUsersCount } from '@/db/Users/GetAllCount';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
@@ -59,21 +59,37 @@ export default function Page(): React.JSX.Element {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
<Stack
|
||||||
|
direction={{ xs: 'column', sm: 'row' }}
|
||||||
|
spacing={3}
|
||||||
|
sx={{ alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<Box sx={{ flex: '1 1 auto' }}>
|
<Box sx={{ flex: '1 1 auto' }}>
|
||||||
<Typography variant="h4">{t('Overview')}</Typography>
|
<Typography variant="h4">{t('Overview')}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<div>
|
<div>
|
||||||
<Button startIcon={<PlusIcon />} variant="contained">
|
<Button
|
||||||
|
startIcon={<PlusIcon />}
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
{t('Dashboard')}
|
{t('Dashboard')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Grid container spacing={4}>
|
<Grid
|
||||||
<Grid md={4} xs={12}>
|
container
|
||||||
|
spacing={4}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
md={4}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
<ActiveUserCount />
|
<ActiveUserCount />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid md={4} xs={12}>
|
<Grid
|
||||||
|
md={4}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
<Summary
|
<Summary
|
||||||
amount={lessonCategoriesCount}
|
amount={lessonCategoriesCount}
|
||||||
diff={15}
|
diff={15}
|
||||||
@@ -82,7 +98,10 @@ export default function Page(): React.JSX.Element {
|
|||||||
trend="up"
|
trend="up"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid md={4} xs={12}>
|
<Grid
|
||||||
|
md={4}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
<Summary
|
<Summary
|
||||||
amount={lessonTypesCount}
|
amount={lessonTypesCount}
|
||||||
diff={15}
|
diff={15}
|
||||||
@@ -91,7 +110,10 @@ export default function Page(): React.JSX.Element {
|
|||||||
trend="up"
|
trend="up"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid md={8} xs={12}>
|
<Grid
|
||||||
|
md={8}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
<AppUsage
|
<AppUsage
|
||||||
data={[
|
data={[
|
||||||
{ name: 'Jan', v1: 36, v2: 19 },
|
{ name: 'Jan', v1: 36, v2: 19 },
|
||||||
@@ -109,22 +131,41 @@ export default function Page(): React.JSX.Element {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid md={4} xs={12}>
|
<Grid
|
||||||
|
md={4}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
<Subscriptions subscriptions={SamplesubScriptions} />
|
<Subscriptions subscriptions={SamplesubScriptions} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid md={4} xs={12}>
|
<Grid
|
||||||
|
md={4}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
<AppChat messages={SampleMessages} />
|
<AppChat messages={SampleMessages} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid md={4} xs={12}>
|
<Grid
|
||||||
|
md={4}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
<Events events={SampleEvents} />
|
<Events events={SampleEvents} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid md={4} xs={12}>
|
<Grid
|
||||||
|
md={4}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
<AppLimits usage={80} />
|
<AppLimits usage={80} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid md={4} xs={12}>
|
<Grid
|
||||||
|
md={4}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
<HelperWidget
|
<HelperWidget
|
||||||
action={
|
action={
|
||||||
<Button color="secondary" endIcon={<ArrowRightIcon />} size="small">
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
endIcon={<ArrowRightIcon />}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
{t('Search jobs')}
|
{t('Search jobs')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@@ -134,10 +175,17 @@ export default function Page(): React.JSX.Element {
|
|||||||
title={t('Find your dream job')}
|
title={t('Find your dream job')}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid md={4} xs={12}>
|
<Grid
|
||||||
|
md={4}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
<HelperWidget
|
<HelperWidget
|
||||||
action={
|
action={
|
||||||
<Button color="secondary" endIcon={<ArrowRightIcon />} size="small">
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
endIcon={<ArrowRightIcon />}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
{t('Help center')}
|
{t('Help center')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@@ -147,10 +195,17 @@ export default function Page(): React.JSX.Element {
|
|||||||
title={t('Need help figuring things out?')}
|
title={t('Need help figuring things out?')}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid md={4} xs={12}>
|
<Grid
|
||||||
|
md={4}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
<HelperWidget
|
<HelperWidget
|
||||||
action={
|
action={
|
||||||
<Button color="secondary" endIcon={<ArrowRightIcon />} size="small">
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
endIcon={<ArrowRightIcon />}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
{t('Documentation')}
|
{t('Documentation')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
57
002_source/cms/src/app/dashboard/students/SampleStudents.tsx
Normal file
57
002_source/cms/src/app/dashboard/students/SampleStudents.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// src/app/dashboard/students/page.tsx
|
||||||
|
'use client';
|
||||||
|
import type { Student } from '@/db/Students/type.d';
|
||||||
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
|
||||||
|
export const SampleStudents = [
|
||||||
|
{
|
||||||
|
id: 'STU-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: 'STU-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: 'STU-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: 'STU-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: 'STU-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 Student[];
|
@@ -1,11 +1,11 @@
|
|||||||
# GUIDELINES
|
# GUIDELINES
|
||||||
|
|
||||||
this folder is part of nextjs typescript project and containing page definition for `Customer` / `Customers` record:
|
this folder is part of nextjs typescript project and containing page definition for `Student` / `Students` record:
|
||||||
|
|
||||||
- list (./page.tsx)
|
- list (./page.tsx)
|
||||||
- view (./[customerId]/page.tsx)
|
- view (./[studentId]/page.tsx)
|
||||||
- create (./create/page.tsx)
|
- create (./create/page.tsx)
|
||||||
- edit (./[customerId]/page.tsx)
|
- edit (./[studentId]/page.tsx)
|
||||||
- translation provided by react-i18next
|
- translation provided by react-i18next
|
||||||
|
|
||||||
the `@` sign refer to `<base_dir>/002_source/002_source/cms/src`
|
the `@` sign refer to `<base_dir>/002_source/002_source/cms/src`
|
||||||
@@ -13,17 +13,17 @@ the `@` sign refer to `<base_dir>/002_source/002_source/cms/src`
|
|||||||
## Assumption and Requirements
|
## Assumption and Requirements
|
||||||
|
|
||||||
- let one file contains one component only.
|
- let one file contains one component only.
|
||||||
- type information defined in `<base_dir>/002_source/cms/src/db/Customers/type.d.tsx`
|
- type information defined in `<base_dir>/002_source/cms/src/db/Students/type.d.tsx`
|
||||||
- it mainly consume the db drivers `Customres` in `<base_dir>/002_source/cms/src/db/Customers`
|
- it mainly consume the db drivers `Students` in `<base_dir>/002_source/cms/src/db/Students`
|
||||||
|
|
||||||
simple template:
|
simple template:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// src/app/dashboard/customers/page.tsx
|
// src/app/dashboard/students/page.tsx
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
// RULES:
|
// RULES:
|
||||||
// contains list page for customers (Customers)
|
// contains list page for students (Students)
|
||||||
// contain definition to collection only
|
// contain definition to collection only
|
||||||
//
|
//
|
||||||
import statements here ...
|
import statements here ...
|
||||||
@@ -46,4 +46,3 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
interface PageProps {
|
interface PageProps {
|
||||||
searchParams: { email?: string; phone?: string; sortDir?: 'asc' | 'desc'; status?: string };
|
searchParams: { email?: string; phone?: string; sortDir?: 'asc' | 'desc'; status?: string };
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
this `tsx` file is clone from elsewhere, please understand, modify and update the content of `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/students/create/page.tsx.draft` to handle `Student` record thanks, modify comments/variables/paths/functions name please
|
||||||
|
|
||||||
|
e.g. why `lessonCategories` still exist ?
|
@@ -1,11 +1 @@
|
|||||||
# task
|
this `tsx` file is clone from elsewhere, please understand, modify and update the content of `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/students/edit/[customerId]/page.tsx.draft` to handle `Student` record thanks, modify comments/variables/paths/functions name please
|
||||||
|
|
||||||
## 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,
|
|
@@ -0,0 +1,3 @@
|
|||||||
|
this `tsx` file is clone from elsewhere, please understand, modify and update the content of `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/students/list/page.tsx.draft` to handle `Student` record thanks, modify comments/variables/paths/functions name please
|
||||||
|
|
||||||
|
e.g. why `lessonCategories` still exist ?
|
@@ -1,13 +1,12 @@
|
|||||||
// src/app/dashboard/customers/page.tsx
|
// src/app/dashboard/students/list/page.tsx
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
// RULES:
|
// RULES:
|
||||||
// contains list page for customers (Customers)
|
// contains list page for students (Students)
|
||||||
// contain definition to collection only
|
|
||||||
//
|
//
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { COL_CUSTOMERS } from '@/constants';
|
import { COL_USER_METAS } from '@/constants';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
@@ -33,13 +32,12 @@ import { defaultStudent } from '@/components/dashboard/student/_constants';
|
|||||||
import FormLoading from '@/components/loading';
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||||
const { t } = useTranslation(['customers']);
|
const { t } = useTranslation(['students']);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { email, phone, sortDir, status } = searchParams;
|
const { email, phone, sortDir, status } = searchParams;
|
||||||
|
|
||||||
const [lessonCategoriesData, setLessonCategoriesData] = React.useState<Student[]>([]);
|
const [studentsData, setStudentsData] = React.useState<Student[]>([]);
|
||||||
//
|
|
||||||
|
|
||||||
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
||||||
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||||
@@ -51,24 +49,31 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
const [currentPage, setCurrentPage] = React.useState<number>(0);
|
const [currentPage, setCurrentPage] = React.useState<number>(0);
|
||||||
//
|
//
|
||||||
const [recordCount, setRecordCount] = React.useState<number>(0);
|
const [recordCount, setRecordCount] = React.useState<number>(0);
|
||||||
const [listOption, setListOption] = React.useState({});
|
const [listOption, setListOption] = React.useState({ filter: '' });
|
||||||
|
const [listSort, setListSort] = React.useState({});
|
||||||
|
|
||||||
|
function isListOptionChanged(): boolean {
|
||||||
|
return JSON.stringify(listOption) === JSON.stringify({ filter: '' });
|
||||||
|
}
|
||||||
//
|
//
|
||||||
const reloadRows = async (): Promise<void> => {
|
const reloadRows = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
const listOptionTeacherOnly = isListOptionChanged()
|
||||||
|
? { filter: `role = "student"` }
|
||||||
|
: { filter: [listOption.filter, `role = "student"`].join(' && ') };
|
||||||
|
|
||||||
const models: ListResult<RecordModel> = await pb
|
const models: ListResult<RecordModel> = await pb
|
||||||
.collection(COL_CUSTOMERS)
|
.collection(COL_USER_METAS)
|
||||||
.getList(currentPage + 1, rowsPerPage, listOption);
|
.getList(currentPage + 1, rowsPerPage, listOptionTeacherOnly);
|
||||||
const { items, totalItems } = models;
|
const { items, totalItems } = models;
|
||||||
const tempLessonTypes: Student[] = items.map((lt) => {
|
const tempStudents: Student[] = items.map((lt) => {
|
||||||
return { ...defaultStudent, ...lt };
|
return { ...defaultStudent, ...lt };
|
||||||
});
|
});
|
||||||
|
|
||||||
setLessonCategoriesData(tempLessonTypes);
|
setStudentsData(tempStudents);
|
||||||
setRecordCount(totalItems);
|
setRecordCount(totalItems);
|
||||||
setF(tempLessonTypes);
|
setF(tempStudents);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
setShowError({
|
setShowError({
|
||||||
//
|
//
|
||||||
@@ -110,11 +115,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
if (email) {
|
if (email) {
|
||||||
tempFilter.push(`email ~ "%${email}%"`);
|
tempFilter.push(`email ~ "%${email}%"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (phone) {
|
if (phone) {
|
||||||
tempFilter.push(`phone ~ "%${phone}%"`);
|
tempFilter.push(`phone ~ "%${phone}%"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let preFinalListOption = {};
|
let preFinalListOption = { filter: '' };
|
||||||
if (tempFilter.length > 0) {
|
if (tempFilter.length > 0) {
|
||||||
preFinalListOption = { filter: tempFilter.join(' && ') };
|
preFinalListOption = { filter: tempFilter.join(' && ') };
|
||||||
}
|
}
|
||||||
@@ -158,7 +164,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
loading={isLoadingAddPage}
|
loading={isLoadingAddPage}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
setIsLoadingAddPage(true);
|
setIsLoadingAddPage(true);
|
||||||
router.push(paths.dashboard.customers.create);
|
router.push(paths.dashboard.students.create);
|
||||||
}}
|
}}
|
||||||
startIcon={<PlusIcon />}
|
startIcon={<PlusIcon />}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -167,11 +173,11 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
<StudentsSelectionProvider customers={f}>
|
<StudentsSelectionProvider students={f}>
|
||||||
<Card>
|
<Card>
|
||||||
<StudentsFilters
|
<StudentsFilters
|
||||||
filters={{ email, phone, status }}
|
filters={{ email, phone, status }}
|
||||||
fullData={lessonCategoriesData}
|
fullData={studentsData}
|
||||||
sortDir={sortDir}
|
sortDir={sortDir}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
@@ -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({
|
||||||
|
studentRecord: model,
|
||||||
|
handleEditClick,
|
||||||
|
}: {
|
||||||
|
studentRecord: Student;
|
||||||
|
handleEditClick: () => void;
|
||||||
|
}): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader
|
||||||
|
action={
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
handleEditClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PencilSimpleIcon />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
avatar={
|
||||||
|
<Avatar>
|
||||||
|
<UserIcon fontSize="var(--Icon-fontSize)" />
|
||||||
|
</Avatar>
|
||||||
|
}
|
||||||
|
title={t('list.basic-details')}
|
||||||
|
/>
|
||||||
|
<PropertyList
|
||||||
|
divider={<Divider />}
|
||||||
|
orientation="vertical"
|
||||||
|
sx={{ '--PropertyItem-padding': '12px 24px' }}
|
||||||
|
>
|
||||||
|
{(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: 'Customer ID',
|
||||||
|
value: (
|
||||||
|
<Chip
|
||||||
|
label={model.id}
|
||||||
|
size="small"
|
||||||
|
variant="soft"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ 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 => (
|
||||||
|
<PropertyItem
|
||||||
|
key={item.key}
|
||||||
|
name={item.key}
|
||||||
|
value={item.value}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</PropertyList>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
@@ -0,0 +1,68 @@
|
|||||||
|
'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 getImageUrlForStudent(student: Student): string {
|
||||||
|
return `http://127.0.0.1:8090/api/files/${student.collectionId}/${student.id}/${student.avatar}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SampleTitleCard({ studentRecord }: { studentRecord: Student }): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={2}
|
||||||
|
sx={{ alignItems: 'center', flex: '1 1 auto' }}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
variant="rounded"
|
||||||
|
src={getImageUrlForStudent(studentRecord)}
|
||||||
|
sx={{ '--Avatar-size': '64px' }}
|
||||||
|
>
|
||||||
|
{t('empty')}
|
||||||
|
</Avatar>
|
||||||
|
<div>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={2}
|
||||||
|
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
||||||
|
>
|
||||||
|
<Typography variant="h4">{studentRecord.name || studentRecord.email}</Typography>
|
||||||
|
<Chip
|
||||||
|
icon={
|
||||||
|
<CheckCircleIcon
|
||||||
|
color="var(--mui-palette-success-main)"
|
||||||
|
weight="fill"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={studentRecord.status}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
endIcon={<CaretDownIcon />}
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{t('list.action')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@@ -0,0 +1,3 @@
|
|||||||
|
this `tsx` file is clone from elsewhere, please understand, modify and update the content of `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/students/view/[customerId]/page.tsx.draft` to handle `Student` record thanks, modify comments/variables/paths/functions name please
|
||||||
|
|
||||||
|
e.g. update `lpModel` names
|
142
002_source/cms/src/app/dashboard/students/view/[id]/page.tsx
Normal file
142
002_source/cms/src/app/dashboard/students/view/[id]/page.tsx
Normal file
@@ -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_USER_METAS } from '@/constants';
|
||||||
|
|
||||||
|
export default function Page(): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const router = useRouter();
|
||||||
|
//
|
||||||
|
const { id: studentId } = useParams<{ id: string }>();
|
||||||
|
//
|
||||||
|
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||||
|
const [showError, setShowError] = React.useState({ show: false, detail: '' });
|
||||||
|
//
|
||||||
|
const [studentRecord, setStudentRecord] = React.useState<Student>(defaultStudent);
|
||||||
|
|
||||||
|
function handleEditClick(): void {
|
||||||
|
router.push(paths.dashboard.students.edit(studentRecord.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (studentId) {
|
||||||
|
pb.collection(COL_USER_METAS)
|
||||||
|
.getOne(studentId)
|
||||||
|
.then((model: RecordModel) => {
|
||||||
|
setStudentRecord({ ...defaultStudent, ...model });
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(err);
|
||||||
|
toast(t('list.error'));
|
||||||
|
|
||||||
|
setShowError({ show: true, detail: JSON.stringify(err) });
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setShowLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [studentId]);
|
||||||
|
|
||||||
|
// return <>{JSON.stringify({ showError, studentRecord }, null, 2)}</>;
|
||||||
|
|
||||||
|
if (showLoading) return <FormLoading />;
|
||||||
|
if (showError.show)
|
||||||
|
return (
|
||||||
|
<ErrorDisplay
|
||||||
|
message={t('error.unable-to-process-request')}
|
||||||
|
code="500"
|
||||||
|
details={showError.detail}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: 'var(--Content-maxWidth)',
|
||||||
|
m: 'var(--Content-margin)',
|
||||||
|
p: 'var(--Content-padding)',
|
||||||
|
width: 'var(--Content-width)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
color="text.primary"
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.dashboard.students.list}
|
||||||
|
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
|
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
|
Students
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<Stack
|
||||||
|
direction={{ xs: 'column', sm: 'row' }}
|
||||||
|
spacing={3}
|
||||||
|
sx={{ alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
|
<TitleCard studentRecord={studentRecord} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={4}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
lg={4}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<BasicDetailCard
|
||||||
|
studentRecord={studentRecord}
|
||||||
|
handleEditClick={handleEditClick}
|
||||||
|
/>
|
||||||
|
<SampleSecurityCard />
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
lg={8}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<SamplePaymentCard />
|
||||||
|
<SampleAddressCard />
|
||||||
|
<Notifications notifications={SampleNotifications} />
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@@ -1,11 +0,0 @@
|
|||||||
# 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,
|
|
@@ -0,0 +1,3 @@
|
|||||||
|
this `tsx` file is clone from elsewhere, please understand, modify and update the content of `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/teachers/list/page.tsx.draft` to handle `Teacher` record thanks, modify comments/variables/paths/functions name please
|
||||||
|
|
||||||
|
e.g. why `lessonCategories` still exist ?
|
@@ -1,16 +1,14 @@
|
|||||||
// src/app/dashboard/teachers/page.tsx
|
// src/app/dashboard/teachers/list/page.tsx
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
// RULES:
|
// RULES:
|
||||||
// contains list page for teachers (Teachers)
|
// contains list page for teachers (Teachers)
|
||||||
// contain definition to collection only
|
|
||||||
//
|
//
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { COL_TEACHERS } from '@/constants';
|
import { COL_USER_METAS } from '@/constants';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
@@ -18,21 +16,17 @@ import Typography from '@mui/material/Typography';
|
|||||||
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
|
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
|
||||||
import type { ListResult, RecordModel } from 'pocketbase';
|
import type { ListResult, RecordModel } from 'pocketbase';
|
||||||
|
|
||||||
import { config } from '@/config';
|
|
||||||
import { TeachersFilters } from '@/components/dashboard/teacher/teachers-filters';
|
import { TeachersFilters } from '@/components/dashboard/teacher/teachers-filters';
|
||||||
// import type { Filters } from '@/components/dashboard/teacher/customers-filters';
|
|
||||||
import { TeachersPagination } from '@/components/dashboard/teacher/teachers-pagination';
|
import { TeachersPagination } from '@/components/dashboard/teacher/teachers-pagination';
|
||||||
import { TeachersSelectionProvider } from '@/components/dashboard/teacher/teachers-selection-context';
|
import { TeachersSelectionProvider } from '@/components/dashboard/teacher/teachers-selection-context';
|
||||||
import { TeachersTable } from '@/components/dashboard/teacher/teachers-table';
|
import { TeachersTable } from '@/components/dashboard/teacher/teachers-table';
|
||||||
import type { Teacher, Filters } from '@/components/dashboard/teacher/type.d';
|
import type { Teacher } from '@/components/dashboard/teacher/type.d';
|
||||||
import { SampleTeachers } from './SampleTeachers';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
import isDevelopment from '@/lib/check-is-development';
|
import isDevelopment from '@/lib/check-is-development';
|
||||||
import { logger } from '@/lib/default-logger';
|
import { logger } from '@/lib/default-logger';
|
||||||
import { pb } from '@/lib/pb';
|
import { pb } from '@/lib/pb';
|
||||||
import { toast } from '@/components/core/toaster';
|
|
||||||
import ErrorDisplay from '@/components/dashboard/error';
|
import ErrorDisplay from '@/components/dashboard/error';
|
||||||
import { defaultTeacher } from '@/components/dashboard/teacher/_constants';
|
import { defaultTeacher } from '@/components/dashboard/teacher/_constants';
|
||||||
import FormLoading from '@/components/loading';
|
import FormLoading from '@/components/loading';
|
||||||
@@ -43,8 +37,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
|
|
||||||
const { email, phone, sortDir, status } = searchParams;
|
const { email, phone, sortDir, status } = searchParams;
|
||||||
|
|
||||||
const [lessonCategoriesData, setLessonCategoriesData] = React.useState<Teacher[]>([]);
|
const [teacherData, setTeacherData] = React.useState<Teacher[]>([]);
|
||||||
//
|
|
||||||
|
|
||||||
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
||||||
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||||
@@ -56,26 +49,33 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
const [currentPage, setCurrentPage] = React.useState<number>(0);
|
const [currentPage, setCurrentPage] = React.useState<number>(0);
|
||||||
//
|
//
|
||||||
const [recordCount, setRecordCount] = React.useState<number>(0);
|
const [recordCount, setRecordCount] = React.useState<number>(0);
|
||||||
const [listOption, setListOption] = React.useState({});
|
const [listOption, setListOption] = React.useState({ filter: '' });
|
||||||
const [listSort, setListSort] = React.useState({});
|
const [listSort, setListSort] = React.useState({});
|
||||||
|
|
||||||
|
function isListOptionChanged() {
|
||||||
|
return JSON.stringify(listOption) === JSON.stringify({ filter: '' });
|
||||||
|
}
|
||||||
//
|
//
|
||||||
const reloadRows = async (): Promise<void> => {
|
const reloadRows = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
const listOptionTeacherOnly = isListOptionChanged()
|
||||||
|
? { filter: `role = "teacher"` }
|
||||||
|
: { filter: [listOption.filter, `role = "teacher"`].join(' && ') };
|
||||||
|
|
||||||
|
console.log({ listOptionTeacherOnly });
|
||||||
|
|
||||||
const models: ListResult<RecordModel> = await pb
|
const models: ListResult<RecordModel> = await pb
|
||||||
.collection(COL_TEACHERS)
|
.collection(COL_USER_METAS)
|
||||||
.getList(currentPage + 1, rowsPerPage, listOption);
|
.getList(currentPage + 1, rowsPerPage, listOptionTeacherOnly);
|
||||||
const { items, totalItems } = models;
|
const { items, totalItems } = models;
|
||||||
const tempLessonTypes: Teacher[] = items.map((lt) => {
|
const tempTeacher: Teacher[] = items.map((lt) => {
|
||||||
return { ...defaultTeacher, ...lt };
|
return { ...defaultTeacher, ...lt };
|
||||||
});
|
});
|
||||||
|
|
||||||
setLessonCategoriesData(tempLessonTypes);
|
setTeacherData(tempTeacher);
|
||||||
setRecordCount(totalItems);
|
setRecordCount(totalItems);
|
||||||
setF(tempLessonTypes);
|
setF(tempTeacher);
|
||||||
// console.log({ currentPage, f });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
setShowError({
|
setShowError({
|
||||||
//
|
//
|
||||||
@@ -103,8 +103,8 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
}, [currentPage, rowsPerPage, listOption]);
|
}, [currentPage, rowsPerPage, listOption]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let tempFilter = [],
|
let tempFilter = [];
|
||||||
tempSortDir = '';
|
let tempSortDir = '';
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
tempFilter.push(`status = "${status}"`);
|
tempFilter.push(`status = "${status}"`);
|
||||||
@@ -117,11 +117,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
if (email) {
|
if (email) {
|
||||||
tempFilter.push(`email ~ "%${email}%"`);
|
tempFilter.push(`email ~ "%${email}%"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (phone) {
|
if (phone) {
|
||||||
tempFilter.push(`phone ~ "%${phone}%"`);
|
tempFilter.push(`phone ~ "%${phone}%"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let preFinalListOption = {};
|
let preFinalListOption = { filter: '' };
|
||||||
if (tempFilter.length > 0) {
|
if (tempFilter.length > 0) {
|
||||||
preFinalListOption = { filter: tempFilter.join(' && ') };
|
preFinalListOption = { filter: tempFilter.join(' && ') };
|
||||||
}
|
}
|
||||||
@@ -129,11 +130,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
|
preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
|
||||||
}
|
}
|
||||||
setListOption(preFinalListOption);
|
setListOption(preFinalListOption);
|
||||||
// setListOption({
|
|
||||||
// filter: tempFilter.join(' && '),
|
|
||||||
// sort: tempSortDir,
|
|
||||||
// //
|
|
||||||
// });
|
|
||||||
}, [sortDir, email, phone, status]);
|
}, [sortDir, email, phone, status]);
|
||||||
|
|
||||||
if (showLoading) return <FormLoading />;
|
if (showLoading) return <FormLoading />;
|
||||||
@@ -183,7 +179,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
<Card>
|
<Card>
|
||||||
<TeachersFilters
|
<TeachersFilters
|
||||||
filters={{ email, phone, status }}
|
filters={{ email, phone, status }}
|
||||||
fullData={lessonCategoriesData}
|
fullData={teacherData}
|
||||||
sortDir={sortDir}
|
sortDir={sortDir}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
@@ -211,42 +207,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorting and filtering has to be done on the server.
|
|
||||||
|
|
||||||
function applySort(row: Teacher[], sortDir: 'asc' | 'desc' | undefined): Teacher[] {
|
|
||||||
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: Teacher[], { email, phone, status }: Filters): Teacher[] {
|
|
||||||
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 {
|
interface PageProps {
|
||||||
searchParams: { email?: string; phone?: string; sortDir?: 'asc' | 'desc'; status?: string };
|
searchParams: {
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
sortDir?: 'asc' | 'desc';
|
||||||
|
status?: string;
|
||||||
|
//
|
||||||
|
};
|
||||||
}
|
}
|
@@ -0,0 +1,216 @@
|
|||||||
|
// src/app/dashboard/teachers/list/page.tsx
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
// RULES:
|
||||||
|
// contains list page for teachers (Teachers)
|
||||||
|
//
|
||||||
|
import * as React from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { COL_USER_METAS } 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 { TeachersFilters } from '@/components/dashboard/teacher/teachers-filters';
|
||||||
|
import { TeachersPagination } from '@/components/dashboard/teacher/teachers-pagination';
|
||||||
|
import { TeachersSelectionProvider } from '@/components/dashboard/teacher/teachers-selection-context';
|
||||||
|
import { TeachersTable } from '@/components/dashboard/teacher/teachers-table';
|
||||||
|
import type { Teacher } from '@/components/dashboard/teacher/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 { defaultTeacher } from '@/components/dashboard/teacher/_constants';
|
||||||
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
|
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||||
|
const { t } = useTranslation(['teachers']);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { email, phone, sortDir, status } = searchParams;
|
||||||
|
|
||||||
|
const [teacherData, setTeacherData] = React.useState<Teacher[]>([]);
|
||||||
|
|
||||||
|
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
||||||
|
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||||
|
const [showError, setShowError] = React.useState({ show: false, detail: '' });
|
||||||
|
//
|
||||||
|
const [rowsPerPage, setRowsPerPage] = React.useState<number>(5);
|
||||||
|
//
|
||||||
|
const [f, setF] = React.useState<Teacher[]>([]);
|
||||||
|
const [currentPage, setCurrentPage] = React.useState<number>(0);
|
||||||
|
//
|
||||||
|
const [recordCount, setRecordCount] = React.useState<number>(0);
|
||||||
|
const [listOption, setListOption] = React.useState({ filter: '' });
|
||||||
|
const [listSort, setListSort] = React.useState({});
|
||||||
|
|
||||||
|
function isListOptionChanged() {
|
||||||
|
return JSON.stringify(listOption) === '{}';
|
||||||
|
}
|
||||||
|
//
|
||||||
|
const reloadRows = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const listOptionTeacherOnly = isListOptionChanged()
|
||||||
|
? { filter: `role = "teacher"` }
|
||||||
|
: { filter: [listOption.filter, `role = "teacher"`].join(' && ') };
|
||||||
|
|
||||||
|
const models: ListResult<RecordModel> = await pb
|
||||||
|
.collection(COL_USER_METAS)
|
||||||
|
.getList(currentPage + 1, rowsPerPage, listOptionTeacherOnly);
|
||||||
|
const { items, totalItems } = models;
|
||||||
|
const tempTeacher: Teacher[] = items.map((lt) => {
|
||||||
|
return { ...defaultTeacher, ...lt };
|
||||||
|
});
|
||||||
|
|
||||||
|
setTeacherData(tempTeacher);
|
||||||
|
setRecordCount(totalItems);
|
||||||
|
setF(tempTeacher);
|
||||||
|
} 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 = { filter: '' };
|
||||||
|
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 <FormLoading />;
|
||||||
|
|
||||||
|
if (showError.show)
|
||||||
|
return (
|
||||||
|
<ErrorDisplay
|
||||||
|
message={t('error.unable-to-process-request')}
|
||||||
|
code={-1}
|
||||||
|
details={showError.detail}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: 'var(--Content-maxWidth)',
|
||||||
|
m: 'var(--Content-margin)',
|
||||||
|
p: 'var(--Content-padding)',
|
||||||
|
width: 'var(--Content-width)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Stack
|
||||||
|
direction={{ xs: 'column', sm: 'row' }}
|
||||||
|
spacing={3}
|
||||||
|
sx={{ alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
|
<Box sx={{ flex: '1 1 auto' }}>
|
||||||
|
<Typography variant="h4">{t('list.title')}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
|
<LoadingButton
|
||||||
|
loading={isLoadingAddPage}
|
||||||
|
onClick={(): void => {
|
||||||
|
setIsLoadingAddPage(true);
|
||||||
|
router.push(paths.dashboard.teachers.create);
|
||||||
|
}}
|
||||||
|
startIcon={<PlusIcon />}
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{t('list.add')}
|
||||||
|
</LoadingButton>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
<TeachersSelectionProvider teachers={f}>
|
||||||
|
<Card>
|
||||||
|
<TeachersFilters
|
||||||
|
filters={{ email, phone, status }}
|
||||||
|
fullData={teacherData}
|
||||||
|
sortDir={sortDir}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
<Box sx={{ overflowX: 'auto' }}>
|
||||||
|
<TeachersTable
|
||||||
|
reloadRows={reloadRows}
|
||||||
|
rows={f}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Divider />
|
||||||
|
<TeachersPagination
|
||||||
|
count={recordCount}
|
||||||
|
page={currentPage}
|
||||||
|
rowsPerPage={rowsPerPage}
|
||||||
|
setPage={setCurrentPage}
|
||||||
|
setRowsPerPage={setRowsPerPage}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</TeachersSelectionProvider>
|
||||||
|
</Stack>
|
||||||
|
<Box sx={{ display: isDevelopment ? 'block' : 'none' }}>
|
||||||
|
<pre>{JSON.stringify(f, null, 2)}</pre>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
searchParams: {
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
sortDir?: 'asc' | 'desc';
|
||||||
|
status?: string;
|
||||||
|
//
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,86 @@
|
|||||||
|
import type { Thread } from '@/components/dashboard/teacher/mail/types';
|
||||||
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
|
||||||
|
export const SampleThreads = [
|
||||||
|
{
|
||||||
|
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[];
|
@@ -0,0 +1,17 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
import { config } from '@/config';
|
||||||
|
import { ThreadView } from '@/components/dashboard/teacher/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} />;
|
||||||
|
}
|
@@ -0,0 +1,62 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { MailProvider } from '@/components/dashboard/teacher/mail/mail-context';
|
||||||
|
import { MailView } from '@/components/dashboard/teacher/mail/mail-view';
|
||||||
|
import type { Label, Thread } from '@/components/dashboard/teacher/mail/types';
|
||||||
|
import { SampleThreads } from './SampleThreads';
|
||||||
|
|
||||||
|
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[];
|
||||||
|
|
||||||
|
interface LayoutProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: { labelId: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Layout({ children, params }: LayoutProps): React.JSX.Element {
|
||||||
|
const { labelId } = params;
|
||||||
|
|
||||||
|
const filteredThreads = filterThreads(SampleThreads, labelId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MailProvider
|
||||||
|
currentLabelId={labelId}
|
||||||
|
labels={labels}
|
||||||
|
threads={filteredThreads}
|
||||||
|
>
|
||||||
|
<MailView>{children}</MailView>
|
||||||
|
</MailProvider>
|
||||||
|
);
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
import { config } from '@/config';
|
||||||
|
import { ThreadsView } from '@/components/dashboard/teacher/mail/threads-view';
|
||||||
|
|
||||||
|
export const metadata = { title: `Mail | Dashboard | ${config.site.name}` } satisfies Metadata;
|
||||||
|
|
||||||
|
export default function Page(): React.JSX.Element {
|
||||||
|
return <ThreadsView />;
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
import { config } from '@/config';
|
||||||
|
import { ThreadsView } from '@/components/dashboard/teacher/mail/threads-view';
|
||||||
|
|
||||||
|
export const metadata = { title: `Mail | Dashboard | ${config.site.name}` } satisfies Metadata;
|
||||||
|
|
||||||
|
export default function Page(): React.JSX.Element {
|
||||||
|
return <ThreadsView />;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user