Compare commits

...

137 Commits

Author SHA1 Message Date
50fc385c2b ``update Add todo-tree filter exclusions for node_modules, bundles, Next.js build folders, and distribution artifacts`` 2025-05-17 18:12:32 +08:00
91b3f53abe ``add Add database guidelines document outlining db driver structure and collection mappings`` 2025-05-17 18:12:19 +08:00
cf0ec8bd64 ``update Change logout behavior back to route navigation after sign-out instead of full page reload `` 2025-05-17 10:02:35 +00:00
59cdf7257b ```
update Implement standardized documentation for all database modules, including purpose, rules, and requirements for each CRUD operation; refactor existing comments to follow new documentation standard
```
2025-05-17 10:02:05 +00:00
b8309596be update example for ai, 2025-05-17 11:20:33 +08:00
688e102f2c ``delete Remove database schema definitions and hidden field count retrieval functionality`` 2025-05-17 09:41:36 +08:00
4d57d0a5f3 ``update Change logout behavior from route navigation to full page reload after sign-out `` 2025-05-16 23:41:22 +08:00
720838f137 Merge branch 'develop/ionic_mobile/i18n/trunk' into develop/ionic_mobile/login-flow/trunk 2025-05-16 23:38:56 +08:00
c92ac33ade ```
replace setting tab button with conditional rendering based on user authentication status, showing avatar if available
```
2025-05-16 23:37:39 +08:00
72e478937d ``update Add QR code generation feature with dynamic sizing and styling, implement screen-width-based conditional rendering, update i18n translations, and adjust context providers structure`` 2025-05-16 22:51:33 +08:00
62d8519da5 ``delete Remove i18n configuration file and English translation resources; likely replaced by a JSON-based or externalized translation management approach`` 2025-05-16 22:50:24 +08:00
8d746f3aa9 ``update Refactor navigation URLs by replacing hardcoded LESSON_LINK with Paths.LESSON_LINK across App, RouteConfig, and multiple components; remove redundant LESSON_LINK from constants`` 2025-05-16 22:00:04 +08:00
69b2ef59e5 ``update Upgrade dependencies including i18next, react-i18next, eslint, and testing-library; adjust peer dependencies and add new ESLint plugins`` 2025-05-16 21:59:52 +08:00
f9c0deb2e9 ``add Add CMS project code-workspace file to define multi-root workspace including documentation and AI workspace directories`` 2025-05-16 21:57:52 +08:00
47760fa7b2 ``add Add code-workspace files for documentation, ionic_mobile, and test projects to define multi-root workspace configurations`` 2025-05-16 21:57:41 +08:00
83bd86cc9b ``update Add .env.example file with PocketBase URL configuration for development environment `` 2025-05-16 17:47:33 +08:00
49189a532e ```
update Add development environment configuration, I18n support, route adjustments, and various hooks refactoring
```
2025-05-16 17:47:05 +08:00
6b917c9fb9 ``update Add error constant for empty PocketBase URL and validate it during app initialization `` 2025-05-16 15:56:56 +08:00
aa834a43c9 ``update Add platform-specific back button for Android, replace header layout, use LoadingSpinner, and refactor imports in Lesson page component`` 2025-05-16 15:56:46 +08:00
1775d8c5a3 ``update Remove unused components, imports, and refactor file URL generation in WordPage component`` 2025-05-16 15:56:24 +08:00
779062e247 ``update Replace getStudentAvatar with getStudentAvatarUrl for avatar URL generation in student profile`` 2025-05-16 15:56:19 +08:00
60df47fb8d ``update Extract PocketBase URL to constant and refactor related functions to use it for dynamic file URL generation`` 2025-05-16 15:55:47 +08:00
c87357ff24 ``update Remove unused import path suffix and comment out unused useRequireAuth hook`` 2025-05-16 15:55:30 +08:00
34a7ff7ac9 ``refactor Rename LoadingScreen component to LoadingSpinner and refactor structure for better reusability`` 2025-05-16 15:55:07 +08:00
3556e77a7c ```
add Implement custom sign-out functionality with loading state and error handling
```
2025-05-16 13:26:17 +08:00
57e25ef65f update fix eslint, 2025-05-16 12:51:32 +08:00
af5040ac6c update to use, 2025-05-16 12:07:58 +08:00
7264d643d0 remove unused code, 2025-05-16 11:51:11 +08:00
de47ceea0b ``update Add test section and GIF image to REQ0016 and REQ0020 documentation files`` 2025-05-16 11:16:29 +08:00
7295b46c06 ```
init Add default VS Code workspace configuration with current and test folders
```
2025-05-16 11:16:21 +08:00
471175d08d ``refactor Update tsconfig.json to include trailing comments in compilerOptions paths and exclude patterns `` 2025-05-16 11:16:10 +08:00
dae97f52ae ``refactor Update PocketBase URL in development environment to use local network IP address `` 2025-05-16 11:14:45 +08:00
6e16b2e87d ```
refactor Add sign-in form component with OAuth integration, Zod validation, i18n support, and password visibility toggle following REQ0016 requirements
```
2025-05-16 11:14:38 +08:00
2371e40ad2 init 003_test, 2025-05-16 11:13:50 +08:00
3e3e23aa68 ``refactor Update docker-compose configurations, adjust PocketBase Dockerfile, modify seed scripts to use 'state' instead of 'status', and fix paths in development scripts `` 2025-05-16 11:13:14 +08:00
a8b0a63648 ```refactor Add reset password form component with Zod validation, email input handling, and internationalization support for custom auth flow
```
2025-05-16 11:07:13 +08:00
bf059147c7 ``refactor Implement custom sign-up form component with Zod validation, OAuth integration, and i18n support following REQ0016 requirements`` 2025-05-16 11:03:58 +08:00
4b750b8146 ``refactor Add explicit type annotation to isDevelopment variable in check-is-development utility function`` 2025-05-16 11:03:05 +08:00
11b1d21472 ``refactor Update CMS site name from 'Devias Kit Pro' to 'demo cms' in configuration`` 2025-05-16 11:02:54 +08:00
3d38af8100 ``refactor Enhance error logging in UserProvider by including full error details in development environment for debugging purposes`` 2025-05-16 11:02:45 +08:00
c5eb7100ea ``refactor Standardize status/state naming across students, teachers, and user metas pages, update filters and tabs to use 'state' parameter consistently `` 2025-05-16 11:02:27 +08:00
louiscklaw
f44539bf63 ``refactor Update .gitignore to exclude files with 'old' suffix in any directory`` 2025-05-15 11:35:47 +08:00
louiscklaw
407f622f24 ``refactor Prettier config to add comment placeholder for potential future plugin additions `` 2025-05-15 11:35:40 +08:00
louiscklaw
7e2844dd74 ```
refactor Student and Teacher create/edit forms to implement i18n support, update UI components, and standardize API calls
```
2025-05-15 11:35:29 +08:00
louiscklaw
097918340c ```
refactor Teachers list page to remove outdated implementation, simplifying component structure and improving maintainability
```
2025-05-15 11:31:03 +08:00
louiscklaw
3620837a6a ``refactor UserMeta creation and authentication client to improve documentation and type consistency`` 2025-05-15 11:27:02 +08:00
louiscklaw
c83e8c1b6e ```
refactor Student and UserMeta type definitions to deprecate obsolete fields, standardize structure, and update billing address format
```
2025-05-15 11:13:15 +08:00
louiscklaw
af15a6bce0 ```
refactor Student type definitions to deprecate obsolete fields, standardize structure, and update billing address format
```
2025-05-15 11:12:56 +08:00
louiscklaw
d0215cf23f ```
refactor GetById APIs for Students, Teachers, and UserMetas to use consistent type definitions and expand parameters
```
2025-05-15 11:12:29 +08:00
louiscklaw
e523f80123 ```
refactor UserMeta type definitions to deprecate obsolete fields and standardize structure
```
2025-05-15 11:10:40 +08:00
louiscklaw
7f8f8824a7 ```
refactor getImageUrlFromFile to use Pocketbase records and correct path in teachers routes
```
2025-05-15 11:10:12 +08:00
louiscklaw
2aa96eec62 ```
refactor student and teacher APIs to use COL_USER_METAS collection, standardize role assignment, and update type definitions
```
2025-05-15 09:27:38 +08:00
louiscklaw
ecab41abbf `` fix GetUserById API to include requestKey option for Pocketbase compatibility `` 2025-05-15 09:27:20 +08:00
louiscklaw
5a1832ca89 ```
remove obsolete GetAllCount API and related guidelines from Users.old module
```
2025-05-15 09:27:08 +08:00
louiscklaw
160c93b83d `` refactor GetUserById function and add Create/UpdateUser APIs with type definitions `` 2025-05-15 09:26:36 +08:00
louiscklaw
d4fcc1dd8f ```
add UserActivationEditForm component for user activation management
```
2025-05-15 09:24:41 +08:00
louiscklaw
8e3d463f78 ```
use dynamic route parameter for user ID in UserActivationEditForm
```
2025-05-15 09:24:27 +08:00
louiscklaw
fbf79b040f `` remove placeholder _PROMPT.md file and reference to external draft for UserMeta editing page `` 2025-05-15 09:24:21 +08:00
louiscklaw
e34782844e ```
add user profile navigation logic and update button in SettingContainer
```
2025-05-15 09:19:59 +08:00
louiscklaw
ba8e9cca69 fix translation, 2025-05-15 08:35:33 +08:00
louiscklaw
cc9fe057c1 ```
add abbreviations section and clarify guideline reading requirement
```
2025-05-14 18:45:23 +08:00
louiscklaw
cdd95faa89 ```
add new student menu route and component, update login default values and redirect logic
```
2025-05-14 18:31:04 +08:00
louiscklaw
030fc1a808 update login requirement for mobile, 2025-05-14 18:13:15 +08:00
louiscklaw
05c69481b5 update check session working, 2025-05-14 18:10:22 +08:00
louiscklaw
0fcc194860 in the middle, working for login and logout test, 2025-05-14 17:19:48 +08:00
louiscklaw
56f0f30ffb ```
replace inline loading text with LoadingScreen component in multiple pages
```
2025-05-14 16:27:30 +08:00
louiscklaw
0aefbfaeae fix typo, 2025-05-14 16:25:57 +08:00
louiscklaw
628c72190b fix typo, 2025-05-14 16:18:39 +08:00
louiscklaw
886a314df7 update settings pages in the middle, 2025-05-14 16:18:04 +08:00
louiscklaw
efc2d31f7c ```
add new student info route and related components, update auth guard implementation and signup success redirect
```
2025-05-14 15:40:59 +08:00
louiscklaw
1938e95948 ```
use async/await for authClient.signInWithPassword to ensure proper execution order
```
2025-05-14 15:20:02 +08:00
louiscklaw
8d37fba393 update login flow, in the middle, 2025-05-14 15:17:04 +08:00
louiscklaw
af160edd42 ```
update .gitignore to add api_ts and dist directories to exclusion list
```
2025-05-14 15:16:21 +08:00
louiscklaw
d880420a28 ``add configuration files for CrossNote, VSCode extensions, and documentation for system architecture and design requirements`` 2025-05-14 15:15:45 +08:00
louiscklaw
5bebc1f40e ```
add COL_BILLING_ADDRESS constant and update exports
```
2025-05-13 13:28:21 +08:00
louiscklaw
f4e5f94e17 ``update .gitignore to modify exclusion pattern for _del files from '**/_del' to '**/*del'`` 2025-05-13 13:28:13 +08:00
louiscklaw
2d022cb613 ```
remove Docker Compose configuration files for CMS, Doc, Ionic Mobile, API_TS, and PocketBase services
```
2025-05-13 13:27:56 +08:00
louiscklaw
3560ea79fc "``update teacher and student seed scripts to dynamically loop through row_array and um_row_array instead of fixed iterations``" 2025-05-13 13:27:48 +08:00
louiscklaw
a441e3e52d ```
refactor teacher and user meta management UI by updating form components, replacing COL_TEACHERS with COL_USER_METAS where applicable, adding development environment checks, improving type definitions for user meta including billing address, and fixing parameter naming inconsistencies in form handlers
```
2025-05-13 13:27:41 +08:00
louiscklaw
09ded06dd2 ``update student database operations to use COL_USER_METAS instead of COL_STUDENTS, refactor getStudentById to include expanded billing address data, and add/update related types and functions for student and user meta management`` 2025-05-13 13:27:27 +08:00
louiscklaw
7ecacd0692 ```
refactor student management UI by updating create form component name and adding new edit page with translation support and form integration
```
2025-05-13 13:27:17 +08:00
louiscklaw
8a094afdd2 "``refactor student create and edit forms with translation support, update schema validation, and use new database operations for student management``" 2025-05-13 13:26:58 +08:00
louiscklaw
64ca29cf60 ``add new database operations for billing address module including create, delete, get, update functions and related type definitions`` 2025-05-13 13:26:41 +08:00
louiscklaw
1aa0502edc ``fix inconsistent quotes in code and update schema with additional fields and rules`` 2025-05-13 13:26:22 +08:00
louiscklaw
3e1f2e1057 ```
add billing address seed data and update user seed scripts with teacher and student roles
```
2025-05-13 12:35:05 +08:00
louiscklaw
9be33f641f ```
add 'visible', 'state', 'company', 'taxId', 'timezone', 'language', 'currency' fields and update 'billingAddress' and 't1' collection rules in PocketBase seed schema
```
2025-05-13 12:34:51 +08:00
louiscklaw
30e4c69343 ```
add Docker Compose configuration files for CMS, Doc, Ionic Mobile, API_TS, and PocketBase services, along with .env file for environment variables and related scripts
```
2025-05-13 06:58:49 +08:00
louiscklaw
04634b5c65 ``update backup script to change archive directory name and add script to recursively remove node_modules directories`` 2025-05-12 19:25:19 +08:00
louiscklaw
f659020d89 ``add TODO list file for Ionic mobile project`` 2025-05-12 19:24:45 +08:00
louiscklaw
184aaa1b0a ``fix HTML doctype declaration in index.html for Ionic mobile project`` 2025-05-12 19:24:39 +08:00
louiscklaw
a6170778cd ```
add new hooks for fetching QuizCRQuestions and categories, update related components to use the new hooks, and refactor SelectCategory page to use the new API
```
2025-05-12 19:24:25 +08:00
louiscklaw
650127821b ```
update nodemon config to fix watch path syntax and upgrade dependencies including sass, parcel watcher, and chokidar
```
2025-05-12 19:23:44 +08:00
louiscklaw
5440f8ea14 ``add .env to .gitignore and update ignore patterns for log, temp, and backup files`` 2025-05-12 19:23:22 +08:00
louiscklaw
f4c9dbcc34 ``add Helloworld component in Ionic mobile project`` 2025-05-12 19:23:00 +08:00
louiscklaw
f756fb8527 ``` "update tsconfig.json: adjust lib array format, simplify include syntax, and refine exclude patterns """ 2025-05-12 19:22:46 +08:00
louiscklaw
3f10a0728c ```
add authentication routes, components, and pages including AuthHome, Login, and SignUp, and implement form fields utility functions
```
2025-05-12 19:22:00 +08:00
louiscklaw
ee0aa0353b ``add development and backup scripts for Ionic mobile project, including dev script, rsync backup script, and express commit script`` 2025-05-12 19:19:39 +08:00
louiscklaw
2c7316786c ``update dc_dev.sh to remove commented logs command and adjust startup sequence for pocketbase and api_ts`` 2025-05-12 14:03:52 +08:00
louiscklaw
89f91ec2a0 ``update Notifications table schema by removing 'author' text field, adding 'author' relation, 'content', 'company', and 'link' fields, and sync changes in schema.dbml, schema.json, and pb_hooks/seed/schema.json`` 2025-05-12 14:03:35 +08:00
louiscklaw
1441863dcd ``remove obsolete UserMetas migrations and related user collection modifications`` 2025-05-12 14:01:52 +08:00
louiscklaw
1835caee68 ``update prettier config with printWidth and quoteProps, add new routes for connective revision, and update guidelines for hooks and types`` 2025-05-12 13:49:11 +08:00
louiscklaw
bac8c70d4b ``fix typos and update base URL quotes in code files`` 2025-05-12 13:48:32 +08:00
louiscklaw
975a528b49 `` update prettier config to enforce LF line endings and ES5 trailing commas `` 2025-05-12 13:29:40 +08:00
louiscklaw
749fef7e28 `` update prettier config for Ionic mobile with new endOfLine and trailingComma settings `` 2025-05-12 13:26:39 +08:00
louiscklaw
cf34833d42 init project-wise prettier config, 2025-05-12 13:22:43 +08:00
louiscklaw
7bb45316af update dependencies, 2025-05-12 13:21:28 +08:00
louiscklaw
02771185af ```
add ionic mobile workspace configuration with linked folders and git/port settings
```
2025-05-12 13:20:37 +08:00
louiscklaw
cf70e2af21 ```
refactor notifications popover to include unread count, mark all as read button, and loading state
```
2025-05-12 13:10:19 +08:00
louiscklaw
1a77c3a5e8 update helloworld template for component, 2025-05-12 11:34:06 +08:00
louiscklaw
af446aed59 update seed file, 2025-05-12 11:33:13 +08:00
louiscklaw
c7f1f544ec ```
refactor notifications popover to use new hooks and improve functionality
```
2025-05-12 11:32:53 +08:00
louiscklaw
99ee2f9fc3 ```
add admin user ID to seed config and update notification relations
```
2025-05-11 17:29:19 +08:00
louiscklaw
a4cdb4b1cc ```
add phone field to User model and update notification relations
```
2025-05-11 16:59:02 +08:00
louiscklaw
b35b77557e ```
enable prettier-plugin-sort-imports for consistent import ordering
```
2025-05-11 16:32:27 +08:00
louiscklaw
6842459499 ```
remove deprecated repomix.md file used for AI analysis
```
2025-05-11 16:32:01 +08:00
louiscklaw
ba7682e7cb "update tsconfig.json exclusion patterns for build optimization" 2025-05-11 16:31:50 +08:00
louiscklaw
1003fa699c "refactor settings pages, add translation support and implement side navigation component" 2025-05-11 16:31:32 +08:00
louiscklaw
ec12ca3bdf "make avatar mandatory and add collectionId to User interface" 2025-05-11 16:30:50 +08:00
louiscklaw
7ece1c814b update order of import, 2025-05-11 16:07:06 +08:00
louiscklaw
39a7d32fcd update, 2025-05-11 15:47:49 +08:00
louiscklaw
85d1ecdc90 update, 2025-05-11 15:44:18 +08:00
louiscklaw
b26e1ff167 update user-button, 2025-05-11 13:47:14 +08:00
louiscklaw
de415a37bc update main-nav and side-nav, 2025-05-11 13:39:33 +08:00
louiscklaw
01a8d2ca02 update main-nav, 2025-05-11 13:24:52 +08:00
louiscklaw
e5b136b8b5 update main-nav refactoring, working, 2025-05-11 13:06:58 +08:00
louiscklaw
031dbed6a9 "implement responsive main navigation bar with dropdown menus and mobile view support" 2025-05-11 13:00:30 +08:00
louiscklaw
f20dfa00c2 "add CMS workspace configuration with linked documentation and AI workspace folders" 2025-05-11 13:00:05 +08:00
louiscklaw
24c91cb6f0 "update gitignore to exclude temporary and backup files" 2025-05-11 10:48:45 +08:00
louiscklaw
abca91c26a "introduce horizontal main navigation component with dynamic styling and interactive elements" 2025-05-11 10:39:09 +08:00
louiscklaw
3321eafffa "update gitignore to exclude draft files" 2025-05-11 10:36:46 +08:00
louiscklaw
adc9275d3f "update prettier config, add TODO list, and create workspace configuration" 2025-05-11 10:35:04 +08:00
louiscklaw
60eed00cb2 "add admin user seed script and refactor common seed utilities" 2025-05-11 10:34:48 +08:00
louiscklaw
c29ab4b920 update project, 2025-05-11 08:02:50 +08:00
louiscklaw
9d46de56c3 update util scripts, 2025-05-11 08:02:07 +08:00
louiscklaw
3f9d88e733 init req0018, family photo of frameworks, 2025-05-11 08:00:31 +08:00
louiscklaw
6e576919ab init docker cofig, 2025-05-11 07:59:35 +08:00
louiscklaw
9739583f43 update ide config, remove yaml and python, 2025-05-11 07:59:11 +08:00
609 changed files with 14603 additions and 41060 deletions

15
.crossnote/config.js Normal file
View File

@@ -0,0 +1,15 @@
({
katexConfig: {
"macros": {}
},
mathjaxConfig: {
"tex": {},
"options": {},
"loader": {}
},
mermaidConfig: {
"startOnLoad": false
},
})

19
.crossnote/head.html Normal file
View File

@@ -0,0 +1,19 @@
<!-- The content below will be included at the end of the <head> element. -->
<script type="text/javascript">
const configureMermaidIconPacks = () => {
window['mermaid'].registerIconPacks([
{
name: 'logos',
loader: () => fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
},
]);
};
if (document.readyState !== 'loading') {
configureMermaidIconPacks();
} else {
document.addEventListener('DOMContentLoaded', () => {
configureMermaidIconPacks();
});
}
</script>

12
.crossnote/parser.js Normal file
View File

@@ -0,0 +1,12 @@
({
// Please visit the URL below for more information:
// https://shd101wyy.github.io/markdown-preview-enhanced/#/extend-parser
onWillParseMarkdown: async function(markdown) {
return markdown;
},
onDidParseMarkdown: async function(html) {
return html;
},
})

8
.crossnote/style.less Normal file
View File

@@ -0,0 +1,8 @@
/* Please visit the URL below for more information: */
/* https://shd101wyy.github.io/markdown-preview-enhanced/#/customize-css */
.markdown-preview.markdown-preview {
// modify your style here
// eg: background-color: blue;
}

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=lf

11
.gitignore vendored
View File

@@ -1,9 +1,18 @@
# api_ts
dist
.next .next
node_modules node_modules
005_references/ 005_references/
_archive/ _archive/
_del _del
*.bak *.bak
*.log *.log
*.del *.del
**/_del **/*del
**/*old
**/volumes/**
006_lab
**/*.draft

10
.prettierrc Normal file
View File

@@ -0,0 +1,10 @@
{
"endOfLine": "lf",
"printWidth": 120,
"quoteProps": "consistent",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"plugins": []
}

15
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"recommendations": [
"christian-kohler.path-intellisense",
"esbenp.prettier-vscode",
"humao.rest-client",
//
"matt-meyers.vscode-dbml",
"aflalo.dbml-formatter",
"nicolas-liger.dbml-viewer",
//
"bierner.markdown-mermaid",
"yzhang.markdown-all-in-one",
"shd101wyy.markdown-preview-enhanced"
]
}

23
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"workbench.iconTheme": "material-icon-theme",
"workbench.colorTheme": "Mermaid Dark",
"editor.formatOnSave": true,
"git.ignoreLimitWarning": true,
//
"markdown.styles": ["https://use.fontawesome.com/releases/v5.7.1/css/all.css"]
}

View File

@@ -1,11 +1,14 @@
```markdown
# Greetings # Greetings
Hi, Hi,
Imaging you are a software engineer and i will send you the guideline.
Imagine you are a software engineer and i will send you the guideline.
plesae read it, prepare yourself and i will tell you the task afterwards plesae read it, prepare yourself and i will tell you the task afterwards
please read and understand the markdown files in directory please read and understand the markdown files in directory
`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/000_AI_WORKSPACE/software_engineer/greetings`, `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/000_AI_WORKSPACE/software-engineer/greetings`,
it provides background information of project i want you to help. it provides background information of project i want you to help.
thanks thanks
```

View File

@@ -28,5 +28,10 @@
- `006_lab` my test (POC) of this project - `006_lab` my test (POC) of this project
- `README.md` Readme of this project - `README.md` Readme of this project
- `TODO.md` todo list of this project - `TODO.md` todo list of this project
- `001_documentation/Requirements/REQ0019/index.md` describes updated system architecture
- if the directory contains `_GUIDELINES.md`, please read it before operation - if the directory contains `_GUIDELINES.md`, please read it before operation
## Abbreviations
T.B.A.

View File

@@ -1,6 +1,5 @@
{ {
"recommendations": [ "recommendations": [
"redhat.vscode-yaml",
"yzhang.markdown-all-in-one", "yzhang.markdown-all-in-one",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"ms-python.python", "ms-python.python",
@@ -8,7 +7,6 @@
"ms-python.debugpy", "ms-python.debugpy",
"ms-python.black-formatter", "ms-python.black-formatter",
"ms-python.isort", "ms-python.isort",
"ms-python.pylint",
"bierner.markdown-mermaid", "bierner.markdown-mermaid",
"shd101wyy.markdown-preview-enhanced", "shd101wyy.markdown-preview-enhanced",
"yzhang.markdown-all-in-one", "yzhang.markdown-all-in-one",

View File

@@ -1,11 +1,11 @@
import { faker } from "@faker-js/faker"; import { faker } from '@faker-js/faker';
const getId = (id) => id.padStart(15, "0"); const getId = (id) => id.padStart(15, '0');
const row_array = Array.from({ length: 10 }, (_, i) => [ const row_array = Array.from({ length: 10 }, (_, i) => [
getId(String(i + 1)), getId(String(i + 1)),
faker.person.firstName(), faker.person.firstName(),
"", '',
faker.internet.email(), faker.internet.email(),
faker.phone.number(), faker.phone.number(),
faker.company.name(), faker.company.name(),
@@ -19,13 +19,11 @@ const row_array = Array.from({ length: 10 }, (_, i) => [
}, },
Math.floor(Math.random() * (100 - 0 + 1)) + 0, Math.floor(Math.random() * (100 - 0 + 1)) + 0,
faker.location.timeZone(), faker.location.timeZone(),
["en", "de", "es", "fr", "ja", "ko", "zh-CN"].sort( ['en', 'de', 'es', 'fr', 'ja', 'ko', 'zh-CN'].sort(() => Math.random() - 0.5)[0],
() => Math.random() - 0.5
)[0],
faker.finance.currencyCode(), faker.finance.currencyCode(),
]); ]);
import fs from "fs"; import fs from 'fs';
const filePath = "output.json"; const filePath = 'output.json';
fs.writeFileSync(filePath, JSON.stringify(row_array, null, 2)); fs.writeFileSync(filePath, JSON.stringify(row_array, null, 2));
console.log(`Wrote ${row_array.length} records to ${filePath}`); console.log(`Wrote ${row_array.length} records to ${filePath}`);

View File

@@ -1,4 +1,4 @@
// Generated at: 2025-05-08T05:00:49.862Z // Generated at: 2025-05-13T05:24:33.962Z
// //
@@ -32,6 +32,7 @@ Table users {
created datetime created datetime
updated datetime updated datetime
visible text visible text
phone text
} }
// //
@@ -113,12 +114,17 @@ Table Notifications {
id text [pk, not null] id text [pk, not null]
read boolean read boolean
type text type text
author text
job text job text
description text description text
NOTI_ID text NOTI_ID text
created datetime created datetime
updated datetime updated datetime
to_user_id integer [ref: > UserMetas.id] // relation704048736
from_user_id integer [ref: > UserMetas.id] // relation556806202
author integer [ref: > UserMetas.id] // relation3182418120
content text
company text
link varchar
} }
// //
@@ -200,9 +206,9 @@ Table QuizLPCategories {
cat_image file cat_image file
pos integer pos integer
init_answer text init_answer text
visible text
created datetime created datetime
updated datetime updated datetime
visible text
slug text slug text
remarks text remarks text
description text description text
@@ -217,8 +223,6 @@ Table QuizLPQuestions {
word text word text
sound file sound file
cat_id integer [ref: > QuizLPCategories.id] // relation3870140739 cat_id integer [ref: > QuizLPCategories.id] // relation3870140739
created datetime
updated datetime
cat_name text cat_name text
cat_image file cat_image file
pos integer pos integer
@@ -227,6 +231,8 @@ Table QuizLPQuestions {
slug text slug text
remarks text remarks text
description text description text
created datetime
updated datetime
} }
// //
@@ -252,9 +258,9 @@ Table QuizMFCategories {
cat_image file cat_image file
pos integer pos integer
init_answer text init_answer text
visible text
created datetime created datetime
updated datetime updated datetime
visible text
} }
// //
@@ -335,18 +341,23 @@ Table Teachers {
// collection type: base // collection type: base
Table UserMetas { Table UserMetas {
id text [pk, not null] id text [pk, not null]
helloworld text address text
meta text meta text
user_id integer [ref: > users.id] // relation2809058197 user_id integer [ref: > users.id] // relation2809058197
state text
created datetime created datetime
updated datetime updated datetime
status text
avatar file avatar file
role text role text
name text name text
email text email text
phone text phone text
avatar_file file company text
taxId text
timezone text
language text
currency text
billingAddress integer [ref: > billingAddress.id] // relation2115670734
} }
// //

View File

@@ -318,6 +318,20 @@
"required": false, "required": false,
"system": false, "system": false,
"type": "text" "type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1146066909",
"max": 0,
"min": 0,
"name": "phone",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
} }
], ],
"indexes": [ "indexes": [
@@ -1043,16 +1057,6 @@
"system": false, "system": false,
"type": "text" "type": "text"
}, },
{
"hidden": false,
"id": "json3182418120",
"maxSize": 0,
"name": "author",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{ {
"hidden": false, "hidden": false,
"id": "json4225294584", "id": "json4225294584",
@@ -1110,6 +1114,77 @@
"presentable": false, "presentable": false,
"system": false, "system": false,
"type": "autodate" "type": "autodate"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1305841361",
"hidden": false,
"id": "relation704048736",
"maxSelect": 1,
"minSelect": 0,
"name": "to_user_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1305841361",
"hidden": false,
"id": "relation556806202",
"maxSelect": 1,
"minSelect": 0,
"name": "from_user_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1305841361",
"hidden": false,
"id": "relation3182418120",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"convertURLs": false,
"hidden": false,
"id": "editor4274335913",
"maxSize": 0,
"name": "content",
"presentable": false,
"required": false,
"system": false,
"type": "editor"
},
{
"hidden": false,
"id": "json1337919823",
"maxSize": 0,
"name": "company",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url917281265",
"name": "link",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
} }
], ],
"indexes": [], "indexes": [],
@@ -1675,6 +1750,20 @@
"system": false, "system": false,
"type": "json" "type": "json"
}, },
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2058414169",
"max": 0,
"min": 0,
"name": "visible",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{ {
"hidden": false, "hidden": false,
"id": "autodate2990389176", "id": "autodate2990389176",
@@ -1695,20 +1784,6 @@
"system": false, "system": false,
"type": "autodate" "type": "autodate"
}, },
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2058414169",
"max": 0,
"min": 0,
"name": "visible",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{ {
"autogeneratePattern": "", "autogeneratePattern": "",
"hidden": false, "hidden": false,
@@ -1817,26 +1892,6 @@
"system": false, "system": false,
"type": "relation" "type": "relation"
}, },
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
},
{ {
"autogeneratePattern": "", "autogeneratePattern": "",
"hidden": false, "hidden": false,
@@ -1939,6 +1994,26 @@
"required": false, "required": false,
"system": false, "system": false,
"type": "editor" "type": "editor"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
} }
], ],
"indexes": [], "indexes": [],
@@ -2107,6 +2182,20 @@
"system": false, "system": false,
"type": "json" "type": "json"
}, },
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2058414169",
"max": 0,
"min": 0,
"name": "visible",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{ {
"hidden": false, "hidden": false,
"id": "autodate2990389176", "id": "autodate2990389176",
@@ -2126,20 +2215,6 @@
"presentable": false, "presentable": false,
"system": false, "system": false,
"type": "autodate" "type": "autodate"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2058414169",
"max": 0,
"min": 0,
"name": "visible",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
} }
], ],
"indexes": [], "indexes": [],
@@ -2795,7 +2870,7 @@
"id": "text4192936109", "id": "text4192936109",
"max": 0, "max": 0,
"min": 0, "min": 0,
"name": "helloworld", "name": "address",
"pattern": "", "pattern": "",
"presentable": false, "presentable": false,
"primaryKey": false, "primaryKey": false,
@@ -2826,6 +2901,20 @@
"system": false, "system": false,
"type": "relation" "type": "relation"
}, },
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2744374011",
"max": 0,
"min": 0,
"name": "state",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{ {
"hidden": false, "hidden": false,
"id": "autodate2990389176", "id": "autodate2990389176",
@@ -2846,20 +2935,6 @@
"system": false, "system": false,
"type": "autodate" "type": "autodate"
}, },
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2744374011",
"max": 0,
"min": 0,
"name": "status",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{ {
"hidden": false, "hidden": false,
"id": "file376926767", "id": "file376926767",
@@ -2903,18 +2978,15 @@
"type": "text" "type": "text"
}, },
{ {
"autogeneratePattern": "", "exceptDomains": null,
"hidden": false, "hidden": false,
"id": "text3885137012", "id": "email3885137012",
"max": 0,
"min": 0,
"name": "email", "name": "email",
"pattern": "", "onlyDomains": null,
"presentable": false, "presentable": false,
"primaryKey": false,
"required": false, "required": false,
"system": false, "system": false,
"type": "text" "type": "email"
}, },
{ {
"autogeneratePattern": "", "autogeneratePattern": "",
@@ -2931,18 +3003,87 @@
"type": "text" "type": "text"
}, },
{ {
"autogeneratePattern": "",
"hidden": false, "hidden": false,
"id": "file507207115", "id": "text1337919823",
"maxSelect": 1, "max": 0,
"maxSize": 0, "min": 0,
"mimeTypes": [], "name": "company",
"name": "avatar_file", "pattern": "",
"presentable": false, "presentable": false,
"protected": false, "primaryKey": false,
"required": false, "required": false,
"system": false, "system": false,
"thumbs": [], "type": "text"
"type": "file" },
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2020362641",
"max": 0,
"min": 0,
"name": "taxId",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text922858135",
"max": 0,
"min": 0,
"name": "timezone",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3571151285",
"max": 0,
"min": 0,
"name": "language",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1767278655",
"max": 0,
"min": 0,
"name": "currency",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1509025625",
"hidden": false,
"id": "relation2115670734",
"maxSelect": 999,
"minSelect": 0,
"name": "billingAddress",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
} }
], ],
"indexes": [], "indexes": [],
@@ -3546,11 +3687,11 @@
}, },
{ {
"id": "pbc_1509025625", "id": "pbc_1509025625",
"listRule": null, "listRule": "",
"viewRule": null, "viewRule": "",
"createRule": null, "createRule": "",
"updateRule": null, "updateRule": "",
"deleteRule": null, "deleteRule": "",
"name": "billingAddress", "name": "billingAddress",
"type": "base", "type": "base",
"fields": [ "fields": [
@@ -3740,11 +3881,11 @@
}, },
{ {
"id": "pbc_2109205374", "id": "pbc_2109205374",
"listRule": null, "listRule": "",
"viewRule": null, "viewRule": "",
"createRule": null, "createRule": "",
"updateRule": null, "updateRule": "",
"deleteRule": null, "deleteRule": "",
"name": "t1", "name": "t1",
"type": "base", "type": "base",
"fields": [ "fields": [

View File

@@ -2,7 +2,7 @@
tags: cms, login-flow tags: cms, login-flow
--- ---
# login flow # CMS login flow
## description ## description
@@ -31,3 +31,9 @@ graph TD;
Start((start)); Start((start));
End((end)) End((end))
``` ```
## test
![alt text](test.gif)
## relation

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -0,0 +1,7 @@
---
tags: mobile, cms, db
---
# family photo of frameworks
it should have a family photo of used framework

View File

@@ -0,0 +1,43 @@
---
tags: architecture,mobile, cms, db
---
# System architecture
## Description
it should have a family photo of used framework
## Diagram
```mermaid {align="center"}
architecture-beta
group running_config(logos:aws-opsworks)[running_config]
service db(database)[pocketbase] in running_config
service tra1(internet)[incoming traffic 3000] in running_config
service cms(logos:nextjs)[next app] in running_config
service tra2(internet)[incoming traffic 5173] in running_config
service ionic(logos:ionic)[ionic app] in running_config
tra1:R --> L:cms
cms:R --> L:db
tra2:R --> L:ionic
ionic:R --> B:db
%% group planning(logos:aws-lambda)[planning]
%% service api_ts(logos:aws-lambda)[api_ts] in planning
%% service pg_db(logos:postgresql)[pg_db] in planning
%% ui:R --> L:api_ts
%% ionic:R --> L:api_ts
%% api_ts:B --> T:pg_db
%% service task_server(logos:aws-lambda)[task_server] in planning
%% api_ts:R --> L:task_server
%% service marketing(logos:wordpress-icon)[marketing] in planning
```

View File

@@ -0,0 +1,41 @@
---
tags: mobile, login-flow
---
# Mobile login flow
## description
```mermaid
graph TD;
Start-->A;
A-->B;
B-->C;
B-->D;
D-->E;
E-->F;
C-->G;
G-->A
F-->End;
A[greeting, asking username and password]
B[check if username and password is valid]
C[pasword failed]
D[pasword ok]
E[login success]
F[redirect to '/dashboard']
G[prompt user wrong username and password]
Start((start));
End((end))
```
## test
![alt text](test.gif)
### relations
[REQ0016](../REQ0016/index.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -17,5 +17,8 @@
- [REQ0013: cms dashboard](./REQ0013/index.md) - [REQ0013: cms dashboard](./REQ0013/index.md)
- [REQ0014: mobile client](./REQ0014/index.md) - [REQ0014: mobile client](./REQ0014/index.md)
- [REQ0015: pocketbase json schema to dbml converter](./REQ0015/index.md) - [REQ0015: pocketbase json schema to dbml converter](./REQ0015/index.md)
- [REQ0016: login flow](./REQ0016/index.md) - [REQ0016: CMS login flow](./REQ0016/index.md)
- [REQ0017: lesson page documentation](./REQ0017/index.md) - [REQ0017: lesson page documentation](./REQ0017/index.md)
- [REQ0018: family photo of frameworks](./REQ0018/index.md)
- [REQ0019: System architecture](./REQ0019/index.md)
- [REQ0020: Mobile login flow](./REQ0020/index.md)

View File

@@ -0,0 +1,11 @@
{
"folders": [
{
"path": "."
},
{
"path": "../003_test"
}
],
"settings": {}
}

Binary file not shown.

14
002_source/.env Normal file
View File

@@ -0,0 +1,14 @@
# THIS IS env file for use with docker-compose.yml
# cms
# doc
# ionic_mobile
# api_ts
# pocketbase
PB_HOSTNAME=pocketbase
PB_USERNAME=admin@123.com
PB_PASSWORD=Aa12345678

4
002_source/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
**/*.del
**/*.log
**/_archive
**/_del

74
002_source/README.md Normal file
View File

@@ -0,0 +1,74 @@
# README
## to start production
```bash
# start docker before hand
$ ./dc_build.sh
$ ./dc_up.sh
```
## to start develop
```bash
#
$ ./dc_build.sh
$ ./dc_dev.sh
$ docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d pocketbase api_ts --remove-orphans
$ docker compose logs -f pocketbase api_ts
```
## test api endpoint
```bash
# browse to http://localhost:3000/helloworld
```
```bash
# start docker before hand
$ cd 002_source
$ ./dev.sh
# docker containers up
```
---
## deprecated
```bash
# mobile
$ cd 002_source/mobile
$ pnpm run dev
# cms
$ cd 002_source/cms
$ npm run dev
```
ideation
prototyping
testing
production deployment
evaluation
monitoring
## addresses
```
mobile:
http://localhost:5173
pocketbase:
http://localhost:8090/_
cms:
http://localhost:3000
documentation
http://localhost:3001
```
extend to vocabularies

View File

@@ -58,4 +58,4 @@ NEXT_PUBLIC_MAPBOX_API_KEY=
# Google Tag Manager # Google Tag Manager
NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID= NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID=
NEXT_PUBLIC_POCKETBASE_URL=http://localhost:8090 NEXT_PUBLIC_POCKETBASE_URL=http://192.168.222.199:8090

View File

@@ -82,7 +82,13 @@ module.exports = {
'eslintreact/jsx-sort-props': 'off', 'eslintreact/jsx-sort-props': 'off',
'react/jsx-sort-props': 'off', 'react/jsx-sort-props': 'off',
}, },
ignorePatterns: ['**/*del', '**/*bak', '**/*copy.*', '**/*copy*.*'], ignorePatterns: [
'**/*.del',
'**/*.bak',
'**/*copy.*',
'**/*copy*.*',
//
],
overrides: [ overrides: [
{ {
// override to ignore no-def for `describe`, `it`, and `expect` // override to ignore no-def for `describe`, `it`, and `expect`

View File

@@ -7,7 +7,7 @@
"node": "==22" "node": "==22"
}, },
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev -H 0.0.0.0",
"build": "next build", "build": "next build",
"build:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 15 --exec \"pnpm run build\"", "build:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 15 --exec \"pnpm run build\"",
"start": "next start", "start": "next start",

View File

@@ -29,7 +29,8 @@ const config = {
'^[./]', '^[./]',
], ],
plugins: [ plugins: [
// '@ianvs/prettier-plugin-sort-imports' '@ianvs/prettier-plugin-sort-imports',
//
], ],
overrides: [ overrides: [
{ {

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="currentColor" d="M8 0c4.42 0 8 3.58 8 8a8.01 8.01 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38c0-.27.01-1.13.01-2.2c0-.75-.25-1.23-.54-1.48c1.78-.2 3.65-.88 3.65-3.95c0-.88-.31-1.59-.82-2.15c.08-.2.36-1.02-.08-2.12c0 0-.67-.22-2.2.82c-.64-.18-1.32-.27-2-.27s-1.36.09-2 .27c-1.53-1.03-2.2-.82-2.2-.82c-.44 1.1-.16 1.92-.08 2.12c-.51.56-.82 1.28-.82 2.15c0 3.06 1.86 3.75 3.64 3.95c-.23.2-.44.55-.51 1.07c-.46.21-1.61.55-2.33-.66c-.15-.24-.6-.83-1.23-.82c-.67.01-.27.38.01.53c.34.19.73.9.82 1.13c.16.45.68 1.31 2.69.94c0 .67.01 1.3.01 1.49c0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8"/></svg>

After

Width:  |  Height:  |  Size: 695 B

View File

@@ -0,0 +1,31 @@
{
"sign-in": "Sign in / 登入",
"dont-have-an-account": "Don't have an account ?",
"continue-with_fh": "以",
"continue-with_sh": "繼續",
"forgot-password": "Forgot password",
"user": "用戶名稱",
"password": "密碼",
"email-address": "用戶電郵",
"first-name-is-required": "名字是必填项",
"last-name-is-required": "姓氏不能为空",
"email-is-required": "邮箱不能为空",
"password-should-be-at-least-6-characters": "密码至少需要6个字符",
"you-must-accept-the-terms-and-conditions": "您必须接受条款和条件",
"sign-up": "注册",
"already-have-an-account": "已有账号?",
"or": "或",
"create-account": "创建账号",
"created-users-are-not-persisted": "已创建的用户不会被保存",
"i-have-read-the": "我已阅读",
"terms-and-conditions": "用户协议和隐私政策",
"e.g.": "例如:",
"first-name": "名",
"last-name": "姓",
"hello": "world",
"welcome-title": "Welcome to devias kit pro",
"welcome-notes": "A professional template that comes with ready-to-use MUI components developed with one common goal in mind, help you build faster & beautiful applications.",
"reset-password": "重置密码",
"back-to-login": "返回登录",
"send-recovery-link": "发送恢复链接"
}

View File

@@ -1,3 +1,10 @@
{ {
"git.ignoreLimitWarning": true "git.ignoreLimitWarning": true,
"todo-tree.filtering.excludeGlobs": [
"**/node_modules/*/**",
"**/*.bundle.*",
"**/.next/*/**",
"**/dist/*/**",
"**/build/*/**",
],
} }

View File

@@ -1,21 +0,0 @@
/// <reference types="jest" />
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import Page from '@/app/_helloworld/page';
// Mock the translation hook
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
describe('Page', () => {
it('renders a heading', () => {
render(<Page hello={'world'} />);
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toBeInTheDocument();
});
});

View File

@@ -1,9 +0,0 @@
import { render } from '@testing-library/react';
// CUT = Component Under Test
import CUT from '../components/_helloworld';
it('renders homepage unchanged', () => {
const { container } = render(<CUT />);
expect(container).toMatchSnapshot();
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,558 +0,0 @@
This file is a merged representation of the entire codebase, combined into a single document by Repomix.
<file_summary>
This section contains a summary of this file.
<purpose>
This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.
</purpose>
<file_format>
The content is organized as follows:
1. This summary section
2. Repository information
3. Directory structure
4. Repository files, each consisting of:
- File path as an attribute
- Full contents of the file
</file_format>
<usage_guidelines>
- This file should be treated as read-only. Any changes should be made to the
original repository files, not this packed version.
- When processing this file, use the file path to distinguish
between different files in the repository.
- Be aware that this file may contain sensitive information. Handle it with
the same level of security as you would the original repository.
</usage_guidelines>
<notes>
- Some files may have been excluded based on .gitignore rules and Repomix's configuration
- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
- Files matching patterns in .gitignore are excluded
- Files matching default ignore patterns are excluded
- Files are sorted by Git change count (files with more changes are at the bottom)
</notes>
<additional_info>
</additional_info>
</file_summary>
<directory_structure>
AddressCard/
index.tsx
SampleAddresses.tsx
BasicDetailCard/
index.tsx
Notifications/
index.tsx
type.d.ts
PaymentCard/
index.tsx
SamplePayments.tsx
SecurityCard/
index.tsx
TitleCard/
index.tsx
Helloworld.tsx
</directory_structure>
<files>
This section contains the contents of the repository's files.
<file path="AddressCard/index.tsx">
'use client';
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
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 Grid from '@mui/material/Unstable_Grid2';
import { House as HouseIcon } from '@phosphor-icons/react/dist/ssr/House';
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { useTranslation } from 'react-i18next';
import type { Address } from '@/types/Address';
import { ShippingAddress } from '@/components/dashboard/lp/categories/shipping-address';
import { SampleAddresses } from './SampleAddresses';
export default function SampleAddressCard(): React.JSX.Element {
const { t } = useTranslation();
return (
<Card>
<CardHeader
action={
<Button
color="secondary"
startIcon={<PlusIcon />}
>
{t('list.add')}
</Button>
}
avatar={
<Avatar>
<HouseIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title={t('list.shipping-addresses')}
/>
<CardContent>
<Grid
container
spacing={3}
>
{(SampleAddresses satisfies Address[]).map((address) => (
<Grid
key={address.id}
md={6}
xs={12}
>
<ShippingAddress address={address} />
</Grid>
))}
</Grid>
</CardContent>
</Card>
);
}
</file>
<file path="AddressCard/SampleAddresses.tsx">
'use client';
import type { Address } from '@/types/Address';
export const SampleAddresses: Address[] = [
{
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',
},
];
</file>
<file path="BasicDetailCard/index.tsx">
'use client';
import * as React from 'react';
import { useRouter } from 'next/navigation';
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 LinearProgress from '@mui/material/LinearProgress';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
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';
export default function BasicDetailCard({
lpCatId,
handleEditClick,
}: {
lpCatId: string;
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="USR-001"
size="small"
variant="soft"
/>
),
},
{ key: 'Name', value: 'Miron Vitold' },
{ key: 'Email', value: 'miron.vitold@domain.com' },
{ key: 'Phone', value: '(425) 434-5535' },
{ key: 'Company', value: 'Devias IO' },
{
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>
);
}
</file>
<file path="Notifications/index.tsx">
'use client';
import { dayjs } from '@/lib/dayjs';
import type { Notification } from './type';
export const SampleNotifications: Notification[] = [
{
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(),
},
];
</file>
<file path="Notifications/type.d.ts">
export interface Notification {
id: string;
type: string;
status: 'delivered' | 'pending' | 'failed';
createdAt: Date;
}
</file>
<file path="PaymentCard/index.tsx">
'use client';
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
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 Divider from '@mui/material/Divider';
import { CreditCard as CreditCardIcon } from '@phosphor-icons/react/dist/ssr/CreditCard';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { useTranslation } from 'react-i18next';
import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list';
import { Payments } from '@/components/dashboard/lp/categories/payments';
import { SamplePayments } from './SamplePayments';
export default function SamplePaymentCard(): React.JSX.Element {
const { t } = useTranslation();
return (
<>
<Payments
ordersValue={2069.48}
payments={SamplePayments}
refundsValue={324.5}
totalOrders={5}
/>
<Card>
<CardHeader
action={
<Button
color="secondary"
startIcon={<PencilSimpleIcon />}
>
{t('list.edit')}
</Button>
}
avatar={
<Avatar>
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title={t('list.billing-details')}
/>
<CardContent>
<Card
sx={{ borderRadius: 1 }}
variant="outlined"
>
<PropertyList
divider={<Divider />}
sx={{ '--PropertyItem-padding': '16px' }}
>
{(
[
{ key: t('Credit card'), value: '**** 4142' },
{ key: t('Country'), value: t('United States') },
{ key: t('State'), value: t('Michigan') },
{ key: t('City'), value: t('Southfield') },
{ key: t('Address'), value: t('Address') },
{ key: t('Tax ID'), value: t('Tax ID') },
] 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>
</>
);
}
</file>
<file path="PaymentCard/SamplePayments.tsx">
'use client';
// import { dayjs } from 'dayjs';
import type { Payment } from '@/types/Payment';
import { dayjs } from '@/lib/dayjs';
export const SamplePayments: Payment[] = [
{
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(),
},
];
</file>
<file path="SecurityCard/index.tsx">
'use client';
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
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 Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
import { useTranslation } from 'react-i18next';
export default function SampleSecurityCard(): React.JSX.Element {
const { t } = useTranslation();
return (
<Card>
<CardHeader
avatar={
<Avatar>
<ShieldWarningIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title={t('list.security')}
/>
<CardContent>
<Stack spacing={1}>
<div>
<Button
color="error"
variant="contained"
>
{t('Delete account')}
</Button>
</div>
<Typography
color="text.secondary"
variant="body2"
>
{t('a-deleted-customer-cannot-be-restored-all-data-will-be-permanently-removed')}
</Typography>
</Stack>
</CardContent>
</Card>
);
}
</file>
<file path="TitleCard/index.tsx">
'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';
export default function SampleTitleCard(): React.JSX.Element {
const { t } = useTranslation();
return (
<>
<Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flex: '1 1 auto' }}
>
<Avatar
src="/assets/avatar-1.png"
sx={{ '--Avatar-size': '64px' }}
>
empty
</Avatar>
<div>
<Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
>
<Typography variant="h4">{t('list.customer-name')}</Typography>
<Chip
icon={
<CheckCircleIcon
color="var(--mui-palette-success-main)"
weight="fill"
/>
}
label={t('list.active')}
size="small"
variant="outlined"
/>
</Stack>
<Typography
color="text.secondary"
variant="body1"
>
{t('list.customer-email')}
</Typography>
</div>
</Stack>
<div>
<Button
endIcon={<CaretDownIcon />}
variant="contained"
>
{t('list.action')}
</Button>
</div>
</>
);
}
</file>
<file path="Helloworld.tsx">
'use client';
import * as React from 'react';
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
function Page(): React.JSX.Element {
React.useLayoutEffect(() => {
console.log('helloworld');
}, []);
return <>helloworld</>;
}
export default Page;
</file>
</files>

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type'; // import type { LessonCategory } from '@/components/dashboard/lp_categories/type';

View File

@@ -28,12 +28,13 @@ import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus'; import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning'; import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User'; import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import { RecordModel } from 'pocketbase'; import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { logger } from '@/lib/default-logger'; import { logger } from '@/lib/default-logger';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import { pb } from '@/lib/pb'; import { pb } from '@/lib/pb';
import { PropertyItem } from '@/components/core/property-item'; import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list'; import { PropertyList } from '@/components/core/property-list';
@@ -45,7 +46,7 @@ import { Notifications } from '@/components/dashboard/lesson_category/notificati
import { Payments } from '@/components/dashboard/lesson_category/payments'; import { Payments } from '@/components/dashboard/lesson_category/payments';
import type { Address } from '@/components/dashboard/lesson_category/shipping-address'; import type { Address } from '@/components/dashboard/lesson_category/shipping-address';
import { ShippingAddress } 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/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type'; // import type { LessonCategory } from '@/components/dashboard/lp_categories/type';
import FormLoading from '@/components/loading'; import FormLoading from '@/components/loading';
@@ -119,39 +120,72 @@ export default function Page(): React.JSX.Element {
{t('dashboard.lessonCategorys.list.title')} {t('dashboard.lessonCategorys.list.title')}
</Link> </Link>
</div> </div>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}> <Stack
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}> direction={{ xs: 'column', sm: 'row' }}
spacing={3}
sx={{ alignItems: 'flex-start' }}
>
<Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flex: '1 1 auto' }}
>
<Avatar <Avatar
src={`http://127.0.0.1:8090/api/files/${showLessonCategory.collectionId}/${showLessonCategory.id}/${showLessonCategory.cat_image}`} src={getImageUrlFromFile(
showLessonCategory.collectionId,
showLessonCategory.id,
showLessonCategory.cat_image
)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
variant="rounded" variant="rounded"
> >
empty empty
</Avatar> </Avatar>
<div> <div>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flexWrap: 'wrap' }}> <Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
>
<Typography variant="h4">{showLessonCategory.name}</Typography> <Typography variant="h4">{showLessonCategory.name}</Typography>
<Chip <Chip
icon={<CheckCircleIcon color="var(--mui-palette-success-main)" weight="fill" />} icon={
<CheckCircleIcon
color="var(--mui-palette-success-main)"
weight="fill"
/>
}
label={showLessonCategory.visible} label={showLessonCategory.visible}
size="small" size="small"
variant="outlined" variant="outlined"
/> />
</Stack> </Stack>
<Typography color="text.secondary" variant="body1"> <Typography
color="text.secondary"
variant="body1"
>
{showLessonCategory.id} {showLessonCategory.id}
</Typography> </Typography>
</div> </div>
</Stack> </Stack>
<div> <div>
<Button endIcon={<CaretDownIcon />} variant="contained"> <Button
endIcon={<CaretDownIcon />}
variant="contained"
>
Action Action
</Button> </Button>
</div> </div>
</Stack> </Stack>
</Stack> </Stack>
<Grid container spacing={4}> <Grid
<Grid lg={4} xs={12}> container
spacing={4}
>
<Grid
lg={4}
xs={12}
>
<Stack spacing={4}> <Stack spacing={4}>
<Card> <Card>
<CardHeader <CardHeader
@@ -178,7 +212,16 @@ export default function Page(): React.JSX.Element {
> >
{( {(
[ [
{ key: 'Customer ID', value: <Chip label={showLessonCategory.id} size="small" variant="soft" /> }, {
key: 'Customer ID',
value: (
<Chip
label={showLessonCategory.id}
size="small"
variant="soft"
/>
),
},
{ key: 'Name', value: showLessonCategory.name }, { key: 'Name', value: showLessonCategory.name },
{ key: 'Pos', value: showLessonCategory.pos }, { key: 'Pos', value: showLessonCategory.pos },
{ {
@@ -195,9 +238,20 @@ export default function Page(): React.JSX.Element {
{ {
key: 'Quota', key: 'Quota',
value: ( value: (
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}> <Stack
<LinearProgress sx={{ flex: '1 1 auto' }} value={50} variant="determinate" /> direction="row"
<Typography color="text.secondary" variant="body2"> spacing={2}
sx={{ alignItems: 'center' }}
>
<LinearProgress
sx={{ flex: '1 1 auto' }}
value={50}
variant="determinate"
/>
<Typography
color="text.secondary"
variant="body2"
>
50% 50%
</Typography> </Typography>
</Stack> </Stack>
@@ -206,7 +260,11 @@ export default function Page(): React.JSX.Element {
] satisfies { key: string; value: React.ReactNode }[] ] satisfies { key: string; value: React.ReactNode }[]
).map( ).map(
(item): React.JSX.Element => ( (item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} /> <PropertyItem
key={item.key}
name={item.key}
value={item.value}
/>
) )
)} )}
</PropertyList> </PropertyList>
@@ -223,11 +281,17 @@ export default function Page(): React.JSX.Element {
<CardContent> <CardContent>
<Stack spacing={1}> <Stack spacing={1}>
<div> <div>
<Button color="error" variant="contained"> <Button
color="error"
variant="contained"
>
Delete account Delete account
</Button> </Button>
</div> </div>
<Typography color="text.secondary" variant="body2"> <Typography
color="text.secondary"
variant="body2"
>
A deleted lesson category cannot be restored. All data will be permanently removed. A deleted lesson category cannot be restored. All data will be permanently removed.
</Typography> </Typography>
</Stack> </Stack>
@@ -235,7 +299,10 @@ export default function Page(): React.JSX.Element {
</Card> </Card>
</Stack> </Stack>
</Grid> </Grid>
<Grid lg={8} xs={12}> <Grid
lg={8}
xs={12}
>
<Stack spacing={4}> <Stack spacing={4}>
<Payments <Payments
ordersValue={2069.48} ordersValue={2069.48}
@@ -282,7 +349,10 @@ export default function Page(): React.JSX.Element {
<Card> <Card>
<CardHeader <CardHeader
action={ action={
<Button color="secondary" startIcon={<PencilSimpleIcon />}> <Button
color="secondary"
startIcon={<PencilSimpleIcon />}
>
Edit Edit
</Button> </Button>
} }
@@ -294,8 +364,14 @@ export default function Page(): React.JSX.Element {
title={t('billing-details', { ns: 'lesson_category' })} title={t('billing-details', { ns: 'lesson_category' })}
/> />
<CardContent> <CardContent>
<Card sx={{ borderRadius: 1 }} variant="outlined"> <Card
<PropertyList divider={<Divider />} sx={{ '--PropertyItem-padding': '16px' }}> sx={{ borderRadius: 1 }}
variant="outlined"
>
<PropertyList
divider={<Divider />}
sx={{ '--PropertyItem-padding': '16px' }}
>
{( {(
[ [
{ key: 'Credit card', value: '**** 4142' }, { key: 'Credit card', value: '**** 4142' },
@@ -307,7 +383,11 @@ export default function Page(): React.JSX.Element {
] satisfies { key: string; value: React.ReactNode }[] ] satisfies { key: string; value: React.ReactNode }[]
).map( ).map(
(item): React.JSX.Element => ( (item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} /> <PropertyItem
key={item.key}
name={item.key}
value={item.value}
/>
) )
)} )}
</PropertyList> </PropertyList>
@@ -317,7 +397,10 @@ export default function Page(): React.JSX.Element {
<Card> <Card>
<CardHeader <CardHeader
action={ action={
<Button color="secondary" startIcon={<PlusIcon />}> <Button
color="secondary"
startIcon={<PlusIcon />}
>
Add Add
</Button> </Button>
} }
@@ -329,7 +412,10 @@ export default function Page(): React.JSX.Element {
title={t('shipping-addresses', { ns: 'lesson_category' })} title={t('shipping-addresses', { ns: 'lesson_category' })}
/> />
<CardContent> <CardContent>
<Grid container spacing={3}> <Grid
container
spacing={3}
>
{( {(
[ [
{ {
@@ -351,7 +437,11 @@ export default function Page(): React.JSX.Element {
}, },
] satisfies Address[] ] satisfies Address[]
).map((address) => ( ).map((address) => (
<Grid key={address.id} md={6} xs={12}> <Grid
key={address.id}
md={6}
xs={12}
>
<ShippingAddress address={address} /> <ShippingAddress address={address} />
</Grid> </Grid>
))} ))}

View File

@@ -90,8 +90,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
React.useEffect(() => { React.useEffect(() => {
if (!isFirstRun.current) { if (!isFirstRun.current) {
isFirstRun.current = true; isFirstRun.current = true;
} else { } else if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
// reset page number as tab changes // reset page number as tab changes
setLastListOption(listOption); setLastListOption(listOption);
setCurrentPage(0); setCurrentPage(0);
@@ -99,7 +98,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
} else { } else {
void reloadRows(); void reloadRows();
} }
}
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const LpCategoriesSampleData = [ export const LpCategoriesSampleData = [
{ {

View File

@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
import { PropertyItem } from '@/components/core/property-item'; import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list'; import { PropertyList } from '@/components/core/property-list';
import { CrCategory } from '@/components/dashboard/cr/categories/type'; import type { CrCategory } from '@/components/dashboard/cr/categories/type';
export default function BasicDetailCard({ export default function BasicDetailCard({
lpModel: model, lpModel: model,

View File

@@ -10,12 +10,9 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { CrCategory } from '@/components/dashboard/cr/categories/type'; import type { CrCategory } from '@/components/dashboard/cr/categories/type';
function getImageUrlFrRecord(record: CrCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: CrCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: CrCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: CrCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
import { PropertyItem } from '@/components/core/property-item'; import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list'; import { PropertyList } from '@/components/core/property-list';
import { LpCategory } from '@/components/dashboard/lp/categories/type'; import type { LpCategory } from '@/components/dashboard/lp/categories/type';
export default function BasicDetailCard({ export default function BasicDetailCard({
lpModel: model, lpModel: model,

View File

@@ -10,11 +10,8 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { LpCategory } from '@/components/dashboard/lp/categories/type'; import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { LpCategory } from '@/components/dashboard/lp/categories/type';
function getImageUrlFrRecord(record: LpCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const CrCategoriesSampleData = [ export const CrCategoriesSampleData = [
{ {

View File

@@ -9,14 +9,14 @@ import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { Customer } from '@/components/dashboard/customer/type.d'; import type { Customer } from '@/components/dashboard/customer/type.d';
// import type { CrCategory } from '@/components/dashboard/cr/categories/type'; // import type { CrCategory } from '@/components/dashboard/cr/categories/type';
function getImageUrlFrRecord(record: Customer): string { function getImageUrlFrRecord(record: Customer): string {
// TODO: fix this // TODO: implement getImageUrlFrRecord
// `http://127.0.0.1:8090/api/files/${'record.collectionId'}/${'record.id'}/${'record.cat_image'}`; return 'not implemented';
return 'getImageUrlFrRecord(helloworld)';
} }
export default function SampleTitleCard({ lpModel }: { lpModel: Customer }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: Customer }): React.JSX.Element {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type'; // import type { LessonCategory } from '@/components/dashboard/lp_categories/type';

View File

@@ -28,12 +28,13 @@ import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus'; import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning'; import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User'; import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import { RecordModel } from 'pocketbase'; import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { logger } from '@/lib/default-logger'; import { logger } from '@/lib/default-logger';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import { pb } from '@/lib/pb'; import { pb } from '@/lib/pb';
import { PropertyItem } from '@/components/core/property-item'; import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list'; import { PropertyList } from '@/components/core/property-list';
@@ -45,7 +46,7 @@ import { Notifications } from '@/components/dashboard/lesson_category/notificati
import { Payments } from '@/components/dashboard/lesson_category/payments'; import { Payments } from '@/components/dashboard/lesson_category/payments';
import type { Address } from '@/components/dashboard/lesson_category/shipping-address'; import type { Address } from '@/components/dashboard/lesson_category/shipping-address';
import { ShippingAddress } 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/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type'; // import type { LessonCategory } from '@/components/dashboard/lp_categories/type';
import FormLoading from '@/components/loading'; import FormLoading from '@/components/loading';
@@ -119,39 +120,72 @@ export default function Page(): React.JSX.Element {
{t('dashboard.lessonCategorys.list.title')} {t('dashboard.lessonCategorys.list.title')}
</Link> </Link>
</div> </div>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}> <Stack
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}> direction={{ xs: 'column', sm: 'row' }}
spacing={3}
sx={{ alignItems: 'flex-start' }}
>
<Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flex: '1 1 auto' }}
>
<Avatar <Avatar
src={`http://127.0.0.1:8090/api/files/${showLessonCategory.collectionId}/${showLessonCategory.id}/${showLessonCategory.cat_image}`} src={getImageUrlFromFile(
showLessonCategory.collectionId,
showLessonCategory.id,
showLessonCategory.cat_image
)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
variant="rounded" variant="rounded"
> >
empty empty
</Avatar> </Avatar>
<div> <div>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flexWrap: 'wrap' }}> <Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
>
<Typography variant="h4">{showLessonCategory.name}</Typography> <Typography variant="h4">{showLessonCategory.name}</Typography>
<Chip <Chip
icon={<CheckCircleIcon color="var(--mui-palette-success-main)" weight="fill" />} icon={
<CheckCircleIcon
color="var(--mui-palette-success-main)"
weight="fill"
/>
}
label={showLessonCategory.visible} label={showLessonCategory.visible}
size="small" size="small"
variant="outlined" variant="outlined"
/> />
</Stack> </Stack>
<Typography color="text.secondary" variant="body1"> <Typography
color="text.secondary"
variant="body1"
>
{showLessonCategory.id} {showLessonCategory.id}
</Typography> </Typography>
</div> </div>
</Stack> </Stack>
<div> <div>
<Button endIcon={<CaretDownIcon />} variant="contained"> <Button
endIcon={<CaretDownIcon />}
variant="contained"
>
Action Action
</Button> </Button>
</div> </div>
</Stack> </Stack>
</Stack> </Stack>
<Grid container spacing={4}> <Grid
<Grid lg={4} xs={12}> container
spacing={4}
>
<Grid
lg={4}
xs={12}
>
<Stack spacing={4}> <Stack spacing={4}>
<Card> <Card>
<CardHeader <CardHeader
@@ -178,7 +212,16 @@ export default function Page(): React.JSX.Element {
> >
{( {(
[ [
{ key: 'Customer ID', value: <Chip label={showLessonCategory.id} size="small" variant="soft" /> }, {
key: 'Customer ID',
value: (
<Chip
label={showLessonCategory.id}
size="small"
variant="soft"
/>
),
},
{ key: 'Name', value: showLessonCategory.name }, { key: 'Name', value: showLessonCategory.name },
{ key: 'Pos', value: showLessonCategory.pos }, { key: 'Pos', value: showLessonCategory.pos },
{ {
@@ -195,9 +238,20 @@ export default function Page(): React.JSX.Element {
{ {
key: 'Quota', key: 'Quota',
value: ( value: (
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}> <Stack
<LinearProgress sx={{ flex: '1 1 auto' }} value={50} variant="determinate" /> direction="row"
<Typography color="text.secondary" variant="body2"> spacing={2}
sx={{ alignItems: 'center' }}
>
<LinearProgress
sx={{ flex: '1 1 auto' }}
value={50}
variant="determinate"
/>
<Typography
color="text.secondary"
variant="body2"
>
50% 50%
</Typography> </Typography>
</Stack> </Stack>
@@ -206,7 +260,11 @@ export default function Page(): React.JSX.Element {
] satisfies { key: string; value: React.ReactNode }[] ] satisfies { key: string; value: React.ReactNode }[]
).map( ).map(
(item): React.JSX.Element => ( (item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} /> <PropertyItem
key={item.key}
name={item.key}
value={item.value}
/>
) )
)} )}
</PropertyList> </PropertyList>
@@ -223,11 +281,17 @@ export default function Page(): React.JSX.Element {
<CardContent> <CardContent>
<Stack spacing={1}> <Stack spacing={1}>
<div> <div>
<Button color="error" variant="contained"> <Button
color="error"
variant="contained"
>
Delete account Delete account
</Button> </Button>
</div> </div>
<Typography color="text.secondary" variant="body2"> <Typography
color="text.secondary"
variant="body2"
>
A deleted lesson category cannot be restored. All data will be permanently removed. A deleted lesson category cannot be restored. All data will be permanently removed.
</Typography> </Typography>
</Stack> </Stack>
@@ -235,7 +299,10 @@ export default function Page(): React.JSX.Element {
</Card> </Card>
</Stack> </Stack>
</Grid> </Grid>
<Grid lg={8} xs={12}> <Grid
lg={8}
xs={12}
>
<Stack spacing={4}> <Stack spacing={4}>
<Payments <Payments
ordersValue={2069.48} ordersValue={2069.48}
@@ -282,7 +349,10 @@ export default function Page(): React.JSX.Element {
<Card> <Card>
<CardHeader <CardHeader
action={ action={
<Button color="secondary" startIcon={<PencilSimpleIcon />}> <Button
color="secondary"
startIcon={<PencilSimpleIcon />}
>
Edit Edit
</Button> </Button>
} }
@@ -294,8 +364,14 @@ export default function Page(): React.JSX.Element {
title={t('billing-details', { ns: 'lesson_category' })} title={t('billing-details', { ns: 'lesson_category' })}
/> />
<CardContent> <CardContent>
<Card sx={{ borderRadius: 1 }} variant="outlined"> <Card
<PropertyList divider={<Divider />} sx={{ '--PropertyItem-padding': '16px' }}> sx={{ borderRadius: 1 }}
variant="outlined"
>
<PropertyList
divider={<Divider />}
sx={{ '--PropertyItem-padding': '16px' }}
>
{( {(
[ [
{ key: 'Credit card', value: '**** 4142' }, { key: 'Credit card', value: '**** 4142' },
@@ -307,7 +383,11 @@ export default function Page(): React.JSX.Element {
] satisfies { key: string; value: React.ReactNode }[] ] satisfies { key: string; value: React.ReactNode }[]
).map( ).map(
(item): React.JSX.Element => ( (item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} /> <PropertyItem
key={item.key}
name={item.key}
value={item.value}
/>
) )
)} )}
</PropertyList> </PropertyList>
@@ -317,7 +397,10 @@ export default function Page(): React.JSX.Element {
<Card> <Card>
<CardHeader <CardHeader
action={ action={
<Button color="secondary" startIcon={<PlusIcon />}> <Button
color="secondary"
startIcon={<PlusIcon />}
>
Add Add
</Button> </Button>
} }
@@ -329,7 +412,10 @@ export default function Page(): React.JSX.Element {
title={t('shipping-addresses', { ns: 'lesson_category' })} title={t('shipping-addresses', { ns: 'lesson_category' })}
/> />
<CardContent> <CardContent>
<Grid container spacing={3}> <Grid
container
spacing={3}
>
{( {(
[ [
{ {
@@ -351,7 +437,11 @@ export default function Page(): React.JSX.Element {
}, },
] satisfies Address[] ] satisfies Address[]
).map((address) => ( ).map((address) => (
<Grid key={address.id} md={6} xs={12}> <Grid
key={address.id}
md={6}
xs={12}
>
<ShippingAddress address={address} /> <ShippingAddress address={address} />
</Grid> </Grid>
))} ))}

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonType } from '@/components/dashboard/lesson_type/lesson-type'; import type { LessonType } from '@/components/dashboard/lesson_type/lesson-type';
// import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType'; // import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonType } from '@/components/dashboard/lesson_type/lesson-type'; import type { LessonType } from '@/components/dashboard/lesson_type/lesson-type';
// import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType'; // import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';

File diff suppressed because it is too large Load Diff

View File

@@ -88,8 +88,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
React.useEffect(() => { React.useEffect(() => {
if (!isFirstRun.current) { if (!isFirstRun.current) {
isFirstRun.current = true; isFirstRun.current = true;
} else { } else if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
// reset page number as tab changes // reset page number as tab changes
setLastListOption(listOption); setLastListOption(listOption);
setCurrentPage(0); setCurrentPage(0);
@@ -97,7 +96,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
} else { } else {
void reloadRows(); void reloadRows();
} }
}
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const LpCategoriesSampleData = [ export const LpCategoriesSampleData = [
{ {

View File

@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
import { PropertyItem } from '@/components/core/property-item'; import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list'; import { PropertyList } from '@/components/core/property-list';
import { LpCategory } from '@/components/dashboard/lp/categories/type'; import type { LpCategory } from '@/components/dashboard/lp/categories/type';
export default function BasicDetailCard({ export default function BasicDetailCard({
lpModel: model, lpModel: model,

View File

@@ -10,11 +10,8 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { LpCategory } from '@/components/dashboard/lp/categories/type'; import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { LpCategory } from '@/components/dashboard/lp/categories/type';
function getImageUrlFrRecord(record: LpCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -88,8 +88,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
React.useEffect(() => { React.useEffect(() => {
if (!isFirstRun.current) { if (!isFirstRun.current) {
isFirstRun.current = true; isFirstRun.current = true;
} else { } else if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
// reset page number as tab changes // reset page number as tab changes
setLastListOption(listOption); setLastListOption(listOption);
setCurrentPage(0); setCurrentPage(0);
@@ -97,7 +96,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
} else { } else {
void reloadRows(); void reloadRows();
} }
}
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const LpCategoriesSampleData = [ export const LpCategoriesSampleData = [
{ {

View File

@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
import { PropertyItem } from '@/components/core/property-item'; import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list'; import { PropertyList } from '@/components/core/property-list';
import { LpCategory } from '@/components/dashboard/lp/categories/type'; import type { LpCategory } from '@/components/dashboard/lp/categories/type';
export default function BasicDetailCard({ export default function BasicDetailCard({
lpModel: model, lpModel: model,

View File

@@ -10,11 +10,8 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { LpCategory } from '@/components/dashboard/lp/categories/type'; import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { LpCategory } from '@/components/dashboard/lp/categories/type';
function getImageUrlFrRecord(record: LpCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

File diff suppressed because it is too large Load Diff

View File

@@ -86,8 +86,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
React.useEffect(() => { React.useEffect(() => {
if (!isFirstRun.current) { if (!isFirstRun.current) {
isFirstRun.current = true; isFirstRun.current = true;
} else { } else if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
// reset page number as tab changes // reset page number as tab changes
setLastListOption(listOption); setLastListOption(listOption);
setCurrentPage(0); setCurrentPage(0);
@@ -95,7 +94,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
} else { } else {
void reloadRows(); void reloadRows();
} }
}
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const LpCategoriesSampleData = [ export const LpCategoriesSampleData = [
{ {

View File

@@ -10,12 +10,9 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { MfCategory } from '@/components/dashboard/mf/categories/type'; import type { MfCategory } from '@/components/dashboard/mf/categories/type';
function getImageUrlFrRecord(record: MfCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -86,8 +86,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
React.useEffect(() => { React.useEffect(() => {
if (!isFirstRun.current) { if (!isFirstRun.current) {
isFirstRun.current = true; isFirstRun.current = true;
} else { } else if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
// reset page number as tab changes // reset page number as tab changes
setLastListOption(listOption); setLastListOption(listOption);
setCurrentPage(0); setCurrentPage(0);
@@ -95,7 +94,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
} else { } else {
void reloadRows(); void reloadRows();
} }
}
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const LpCategoriesSampleData = [ export const LpCategoriesSampleData = [
{ {

View File

@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
import { PropertyItem } from '@/components/core/property-item'; import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list'; import { PropertyList } from '@/components/core/property-list';
import { MfCategory } from '@/components/dashboard/mf/categories/type'; import type { MfCategory } from '@/components/dashboard/mf/categories/type';
export default function BasicDetailCard({ export default function BasicDetailCard({
lpModel: model, lpModel: model,

View File

@@ -10,12 +10,9 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { MfCategory } from '@/components/dashboard/mf/categories/type'; import type { MfCategory } from '@/components/dashboard/mf/categories/type';
function getImageUrlFrRecord(record: MfCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -1,7 +1,10 @@
'use client';
import * as React from 'react'; import * as React from 'react';
import type { Metadata } from 'next'; // import type { Metadata } from 'next';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import { config } from '@/config'; import { config } from '@/config';
import { AccountDetails } from '@/components/dashboard/settings/account-details'; import { AccountDetails } from '@/components/dashboard/settings/account-details';
@@ -9,13 +12,15 @@ import { DeleteAccount } from '@/components/dashboard/settings/delete-account';
import { Privacy } from '@/components/dashboard/settings/privacy'; import { Privacy } from '@/components/dashboard/settings/privacy';
import { ThemeSwitch } from '@/components/dashboard/settings/theme-switch'; import { ThemeSwitch } from '@/components/dashboard/settings/theme-switch';
export const metadata = { title: `Account | Settings | Dashboard | ${config.site.name}` } satisfies Metadata; // export const metadata = { title: `Account | Settings | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element { export default function Page(): React.JSX.Element {
const { t } = useTranslation();
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<div> <div>
<Typography variant="h4">Account</Typography> <Typography variant="h4">{t('account')}</Typography>
</div> </div>
<Stack spacing={4}> <Stack spacing={4}>
<AccountDetails /> <AccountDetails />

View File

@@ -18,7 +18,11 @@ export default function Layout({ children }: LayoutProps): React.JSX.Element {
width: 'var(--Content-width)', width: 'var(--Content-width)',
}} }}
> >
<Stack direction={{ xs: 'column', md: 'row' }} spacing={4} sx={{ position: 'relative' }}> <Stack
direction={{ xs: 'column', md: 'row' }}
spacing={4}
sx={{ position: 'relative' }}
>
<SideNav /> <SideNav />
<Box sx={{ flex: '1 1 auto', minWidth: 0 }}>{children}</Box> <Box sx={{ flex: '1 1 auto', minWidth: 0 }}>{children}</Box>
</Stack> </Stack>

View File

@@ -14,10 +14,11 @@ import { PhoneNotifications } from '@/components/dashboard/settings/phone-notifi
export default function Page(): React.JSX.Element { export default function Page(): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<div> <div>
<Typography variant="h4">{t('Notifications')}</Typography> <Typography variant="h4">{t('notifications')}</Typography>
</div> </div>
<Stack spacing={4}> <Stack spacing={4}>
<EmailNotifications /> <EmailNotifications />

View File

@@ -1,7 +1,10 @@
'use client';
import * as React from 'react'; import * as React from 'react';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import { config } from '@/config'; import { config } from '@/config';
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
@@ -9,13 +12,15 @@ import { LoginHistory } from '@/components/dashboard/settings/login-history';
import { MultiFactor } from '@/components/dashboard/settings/multi-factor'; import { MultiFactor } from '@/components/dashboard/settings/multi-factor';
import { PasswordForm } from '@/components/dashboard/settings/password-form'; import { PasswordForm } from '@/components/dashboard/settings/password-form';
export const metadata = { title: `Security | Settings | Dashboard | ${config.site.name}` } satisfies Metadata; // export const metadata = { title: `Security | Settings | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element { export default function Page(): React.JSX.Element {
const { t } = useTranslation();
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<div> <div>
<Typography variant="h4">Security</Typography> <Typography variant="h4">{t('security')}</Typography>
</div> </div>
<Stack spacing={4}> <Stack spacing={4}>
<PasswordForm /> <PasswordForm />

View File

@@ -1,33 +1,38 @@
'use client';
import * as React from 'react'; import * as React from 'react';
import type { Metadata } from 'next'; // import type { Metadata } from 'next';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import { config } from '@/config'; import { config } from '@/config';
import { Members } from '@/components/dashboard/settings/members'; import { Members } from '@/components/dashboard/settings/members';
export const metadata = { title: `Team | Settings | Dashboard | ${config.site.name}` } satisfies Metadata; // export const metadata = { title: `Team | Settings | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element { export default function Page(): React.JSX.Element {
const { t } = useTranslation();
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<div> <div>
<Typography variant="h4">Team</Typography> <Typography variant="h4">{t('team')}</Typography>
</div> </div>
<Members <Members
members={[ members={[
{ {
id: 'USR-000', id: 'USR-000',
name: 'Sofia Rivers', name: 'team member1',
avatar: '/assets/avatar.png', avatar: '/assets/avatar.png',
email: 'sofia@devias.io', email: 'teamMember1@devias.io',
role: 'Owner', role: 'Owner',
}, },
{ {
id: 'USR-002', id: 'USR-002',
name: 'Siegbert Gottfried', name: 'team member2',
avatar: '/assets/avatar-2.png', avatar: '/assets/avatar-2.png',
email: 'siegbert.gottfried@domain.com', email: 'teamMember2@domain.com',
role: 'Standard', role: 'Standard',
}, },
]} ]}

View File

@@ -1,19 +1,25 @@
'use client';
// src/app/dashboard/students/create/page.tsx
// PURPOSE
// T.B.A.
//
import * as React from 'react'; import * as React from 'react';
import type { Metadata } from 'next';
import RouterLink from 'next/link'; import RouterLink from 'next/link';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Link from '@mui/material/Link'; import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft'; import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';
import { config } from '@/config'; import { config } from '@/config';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { CustomerCreateForm } from '@/components/dashboard/student/student-create-form'; import { StudentCreateForm } from '@/components/dashboard/student/student-create-form';
export const metadata = { title: `Create | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element { export default function Page(): React.JSX.Element {
const { t } = useTranslation(['students']);
return ( return (
<Box <Box
sx={{ sx={{
@@ -29,19 +35,22 @@ export default function Page(): React.JSX.Element {
<Link <Link
color="text.primary" color="text.primary"
component={RouterLink} component={RouterLink}
href={paths.dashboard.customers.list} href={paths.dashboard.students.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }} sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2" variant="subtitle2"
> >
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" /> <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Customers {t('students')}
</Link> </Link>
</div> </div>
<div> <div>
<Typography variant="h4">Create customer</Typography> <Typography variant="h4">
{t('create-student')}
{/* */}
</Typography>
</div> </div>
</Stack> </Stack>
<CustomerCreateForm /> <StudentCreateForm />
</Stack> </Stack>
</Box> </Box>
); );

View File

@@ -1 +0,0 @@
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

View File

@@ -0,0 +1,6 @@
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

View File

@@ -1,6 +1,9 @@
'use client'; 'use client';
// src/app/dashboard/students/edit/[customerId]/page.tsx
// src/app/dashboard/students/edit/[id]/page.tsx
// PURPOSE
// T.B.A.
//
import * as React from 'react'; import * as React from 'react';
import RouterLink from 'next/link'; import RouterLink from 'next/link';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
@@ -11,7 +14,8 @@ import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/Arrow
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { CrCategoryEditForm } from '@/components/dashboard/cr/categories/cr-category-edit-form'; // TODO: remove me
// import { CrCategoryEditForm } from '@/components/dashboard/cr/categories/cr-category-edit-form';
import { StudentEditForm } from '@/components/dashboard/student/student-edit-form'; import { StudentEditForm } from '@/components/dashboard/student/student-edit-form';
export default function Page(): React.JSX.Element { export default function Page(): React.JSX.Element {

View File

@@ -1,6 +1,7 @@
// src/app/dashboard/students/list/page.tsx
'use client'; 'use client';
// src/app/dashboard/students/list/page.tsx
//
// RULES: // RULES:
// contains list page for students (Students) // contains list page for students (Students)
// //
@@ -15,12 +16,6 @@ import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography'; 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 { StudentsFilters } from '@/components/dashboard/student/students-filters';
import { StudentsPagination } from '@/components/dashboard/student/students-pagination';
import { StudentsSelectionProvider } from '@/components/dashboard/student/students-selection-context';
import { StudentsTable } from '@/components/dashboard/student/students-table';
import type { Student } from '@/components/dashboard/student/type.d';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
@@ -29,13 +24,18 @@ import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb'; import { pb } from '@/lib/pb';
import ErrorDisplay from '@/components/dashboard/error'; import ErrorDisplay from '@/components/dashboard/error';
import { defaultStudent } from '@/components/dashboard/student/_constants'; import { defaultStudent } from '@/components/dashboard/student/_constants';
import { StudentsFilters } from '@/components/dashboard/student/students-filters';
import { StudentsPagination } from '@/components/dashboard/student/students-pagination';
import { StudentsSelectionProvider } from '@/components/dashboard/student/students-selection-context';
import { StudentsTable } from '@/components/dashboard/student/students-table';
import type { Student } from '@/components/dashboard/student/type.d';
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(['students']); const { t } = useTranslation(['students']);
const router = useRouter(); const router = useRouter();
const { email, phone, sortDir, status } = searchParams; const { email, phone, sortDir, state } = searchParams;
const [studentsData, setStudentsData] = React.useState<Student[]>([]); const [studentsData, setStudentsData] = React.useState<Student[]>([]);
@@ -104,8 +104,8 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
const tempFilter = []; const tempFilter = [];
let tempSortDir = ''; let tempSortDir = '';
if (status) { if (state) {
tempFilter.push(`status = "${status}"`); tempFilter.push(`state = "${state}"`);
} }
if (sortDir) { if (sortDir) {
@@ -128,7 +128,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
preFinalListOption = { ...preFinalListOption, sort: tempSortDir }; preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
} }
setListOption(preFinalListOption); setListOption(preFinalListOption);
}, [sortDir, email, phone, status]); }, [sortDir, email, phone, state]);
if (showLoading) return <FormLoading />; if (showLoading) return <FormLoading />;
@@ -176,7 +176,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
<StudentsSelectionProvider students={f}> <StudentsSelectionProvider students={f}>
<Card> <Card>
<StudentsFilters <StudentsFilters
filters={{ email, phone, status }} filters={{ email, phone, status: state }}
fullData={studentsData} fullData={studentsData}
sortDir={sortDir} sortDir={sortDir}
/> />
@@ -210,7 +210,7 @@ interface PageProps {
email?: string; email?: string;
phone?: string; phone?: string;
sortDir?: 'asc' | 'desc'; sortDir?: 'asc' | 'desc';
status?: string; state?: string;
// //
}; };
} }

View File

@@ -9,14 +9,10 @@ import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { Student } from '@/components/dashboard/student/type.d'; 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 { export default function SampleTitleCard({ studentRecord }: { studentRecord: Student }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -29,7 +25,7 @@ export default function SampleTitleCard({ studentRecord }: { studentRecord: Stud
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlForStudent(studentRecord)} src={getImageUrlFromFile(studentRecord.collectionId, studentRecord.id, studentRecord.avatar)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -1,80 +0,0 @@
'use client';
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import { useTranslation } from 'react-i18next';
import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list';
// import { CrCategory } from '@/components/dashboard/cr/categories/type';
import type { Student } from '@/components/dashboard/student/type.d';
export default function BasicDetailCard({
lpModel: model,
handleEditClick,
}: {
lpModel: Student;
handleEditClick: () => void;
}): React.JSX.Element {
const { t } = useTranslation();
return (
<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>
);
}

View File

@@ -1,76 +0,0 @@
'use client';
import * as React from 'react';
import { Button } from '@mui/material';
import Avatar from '@mui/material/Avatar';
import Chip from '@mui/material/Chip';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next';
import type { Student } from '@/components/dashboard/student/type.d';
// import type { CrCategory } from '@/components/dashboard/cr/categories/type';
function getImageUrlFrRecord(record: Student): string {
// TODO: fix this
// `http://127.0.0.1:8090/api/files/${'record.collectionId'}/${'record.id'}/${'record.cat_image'}`;
return 'getImageUrlFrRecord(helloworld)';
}
export default function SampleTitleCard({ lpModel }: { lpModel: Student }): React.JSX.Element {
const { t } = useTranslation();
return (
<>
<Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flex: '1 1 auto' }}
>
<Avatar
variant="rounded"
src={getImageUrlFrRecord(lpModel)}
sx={{ '--Avatar-size': '64px' }}
>
{t('empty')}
</Avatar>
<div>
<Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
>
<Typography variant="h4">{lpModel.email}</Typography>
<Chip
icon={
<CheckCircleIcon
color="var(--mui-palette-success-main)"
weight="fill"
/>
}
label={lpModel.quota}
size="small"
variant="outlined"
/>
</Stack>
<Typography
color="text.secondary"
variant="body1"
>
{lpModel.status}
</Typography>
</div>
</Stack>
<div>
<Button
endIcon={<CaretDownIcon />}
variant="contained"
>
{t('list.action')}
</Button>
</div>
</>
);
}

View File

@@ -1,142 +0,0 @@
'use client';
import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import SampleAddressCard from '@/app/dashboard/Sample/AddressCard';
import { SampleNotifications } from '@/app/dashboard/Sample/Notifications';
import SamplePaymentCard from '@/app/dashboard/Sample/PaymentCard';
import SampleSecurityCard from '@/app/dashboard/Sample/SecurityCard';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Grid from '@mui/material/Unstable_Grid2';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next';
import { config } from '@/config';
import { paths } from '@/paths';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import { Notifications } from '@/components/dashboard/student/notifications';
import FormLoading from '@/components/loading';
import BasicDetailCard from './BasicDetailCard';
import TitleCard from './TitleCard';
import { defaultStudent } from '@/components/dashboard/student/_constants';
import type { Student } from '@/components/dashboard/student/type.d';
import { COL_STUDENTS } from '@/constants';
export default function Page(): React.JSX.Element {
const { t } = useTranslation();
const router = useRouter();
//
const { customerId } = useParams<{ customerId: string }>();
//
const [showLoading, setShowLoading] = React.useState<boolean>(true);
const [showError, setShowError] = React.useState({ show: false, detail: '' });
//
const [showLessonCategory, setShowLessonCategory] = React.useState<Student>(defaultStudent);
function handleEditClick(): void {
router.push(paths.dashboard.students.edit(showLessonCategory.id));
}
React.useEffect(() => {
if (customerId) {
pb.collection(COL_STUDENTS)
.getOne(customerId)
.then((model: RecordModel) => {
setShowLessonCategory({ ...defaultStudent, ...model });
})
.catch((err) => {
logger.error(err);
toast(t('list.error'));
setShowError({ show: true, detail: JSON.stringify(err) });
})
.finally(() => {
setShowLoading(false);
});
}
}, [customerId]);
// return <>{JSON.stringify({ showError, showLessonCategory }, null, 2)}</>;
if (showLoading) return <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 lpModel={showLessonCategory} />
</Stack>
</Stack>
<Grid
container
spacing={4}
>
<Grid
lg={4}
xs={12}
>
<Stack spacing={4}>
<BasicDetailCard
lpModel={showLessonCategory}
handleEditClick={handleEditClick}
/>
<SampleSecurityCard />
</Stack>
</Grid>
<Grid
lg={8}
xs={12}
>
<Stack spacing={4}>
<SamplePaymentCard />
<SampleAddressCard />
<Notifications notifications={SampleNotifications} />
</Stack>
</Grid>
</Grid>
</Stack>
</Box>
);
}

View File

@@ -1,4 +1,9 @@
'use client'; 'use client';
// src/app/dashboard/teachers/create/page.tsx
// PURPOSE
// T.B.A.
//
import * as React from 'react'; import * as React from 'react';
import RouterLink from 'next/link'; import RouterLink from 'next/link';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
@@ -6,12 +11,15 @@ import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft'; import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';
import { config } from '@/config'; import { config } from '@/config';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { TeacherCreateForm } from '@/components/dashboard/teacher/teacher-create-form'; import { TeacherCreateForm } from '@/components/dashboard/teacher/teacher-create-form';
export default function Page(): React.JSX.Element { export default function Page(): React.JSX.Element {
const { t } = useTranslation(['teachers']);
return ( return (
<Box <Box
sx={{ sx={{
@@ -32,11 +40,14 @@ export default function Page(): React.JSX.Element {
variant="subtitle2" variant="subtitle2"
> >
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" /> <ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
Teachers {t('teachers')}
</Link> </Link>
</div> </div>
<div> <div>
<Typography variant="h4">Create teacher</Typography> <Typography variant="h4">
{t('create-teacher')}
{/* */}
</Typography>
</div> </div>
</Stack> </Stack>
<TeacherCreateForm /> <TeacherCreateForm />

View File

@@ -1,5 +1,9 @@
'use client'; 'use client';
// src/app/dashboard/teachers/edit/[id]/page.tsx
// PURPOSE
// T.B.A.
//
import * as React from 'react'; import * as React from 'react';
import RouterLink from 'next/link'; import RouterLink from 'next/link';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
@@ -10,7 +14,8 @@ import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/Arrow
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { CrCategoryEditForm } from '@/components/dashboard/cr/categories/cr-category-edit-form'; // TODO: remove me
// import { CrCategoryEditForm } from '@/components/dashboard/cr/categories/cr-category-edit-form';
import { TeacherEditForm } from '@/components/dashboard/teacher/teacher-edit-form'; import { TeacherEditForm } from '@/components/dashboard/teacher/teacher-edit-form';
export default function Page(): React.JSX.Element { export default function Page(): React.JSX.Element {

View File

@@ -1,6 +1,7 @@
// src/app/dashboard/teachers/list/page.tsx
'use client'; 'use client';
// src/app/dashboard/teachers/list/page.tsx
//
// RULES: // RULES:
// contains list page for teachers (Teachers) // contains list page for teachers (Teachers)
// //
@@ -15,12 +16,6 @@ import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography'; 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 { 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 { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
@@ -29,13 +24,18 @@ import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb'; import { pb } from '@/lib/pb';
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 { 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 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(['teachers']); const { t } = useTranslation(['teachers']);
const router = useRouter(); const router = useRouter();
const { email, phone, sortDir, status } = searchParams; const { email, phone, sortDir, state } = searchParams;
const [teacherData, setTeacherData] = React.useState<Teacher[]>([]); const [teacherData, setTeacherData] = React.useState<Teacher[]>([]);
@@ -103,11 +103,11 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {
let tempFilter = []; const tempFilter = [];
let tempSortDir = ''; let tempSortDir = '';
if (status) { if (state) {
tempFilter.push(`status = "${status}"`); tempFilter.push(`state = "${state}"`);
} }
if (sortDir) { if (sortDir) {
@@ -130,7 +130,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
preFinalListOption = { ...preFinalListOption, sort: tempSortDir }; preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
} }
setListOption(preFinalListOption); setListOption(preFinalListOption);
}, [sortDir, email, phone, status]); }, [sortDir, email, phone, state]);
if (showLoading) return <FormLoading />; if (showLoading) return <FormLoading />;
@@ -178,7 +178,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
<TeachersSelectionProvider teachers={f}> <TeachersSelectionProvider teachers={f}>
<Card> <Card>
<TeachersFilters <TeachersFilters
filters={{ email, phone, status }} filters={{ email, phone, state }}
fullData={teacherData} fullData={teacherData}
sortDir={sortDir} sortDir={sortDir}
/> />
@@ -212,7 +212,7 @@ interface PageProps {
email?: string; email?: string;
phone?: string; phone?: string;
sortDir?: 'asc' | 'desc'; sortDir?: 'asc' | 'desc';
status?: string; state?: string;
// //
}; };
} }

View File

@@ -1,216 +0,0 @@
// 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;
//
};
}

View File

@@ -9,13 +9,9 @@ import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Teacher } from '@/components/dashboard/teacher/type.d';
// import type { CrCategory } from '@/components/dashboard/cr/categories/type'; import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { Teacher } from '@/components/dashboard/teacher/type.d';
function getImageUrlFrRecord(teacher: Teacher): string {
return `http://127.0.0.1:8090/api/files/${teacher.collectionId}/${teacher.id}/${teacher.avatar}`;
}
export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teacher }): React.JSX.Element { export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teacher }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -29,7 +25,7 @@ export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teac
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(teacherRecord)} src={getImageUrlFromFile(teacherRecord.collectionId, teacherRecord.id, teacherRecord.avatar)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -1,3 +0,0 @@
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/user_metas/edit/[id]/page.tsx.draft` to handle `UserMeta` record thanks, modify comments/variables/paths/functions name please
e.g. why `lessonCategories` still exist ?

View File

@@ -1,8 +1,9 @@
'use client'; 'use client';
// src/app/dashboard/user_metas/edit/[id]/page.tsx
// src/app/dashboard/user_metas/edit/[id]/page.tsx
import * as React from 'react'; import * as React from 'react';
import RouterLink from 'next/link'; import RouterLink from 'next/link';
import { useParams } from 'next/navigation';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Link from '@mui/material/Link'; import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
@@ -11,11 +12,12 @@ import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/Arrow
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { CrCategoryEditForm } from '@/components/dashboard/cr/categories/cr-category-edit-form'; import { UserActivationEditForm } from '@/components/dashboard/user_meta/user-activation-edit-form';
import { UserMetaEditForm } from '@/components/dashboard/user_meta/user-meta-edit-form'; import { UserMetaEditForm } from '@/components/dashboard/user_meta/user-meta-edit-form';
export default function Page(): React.JSX.Element { export default function Page(): React.JSX.Element {
const { t } = useTranslation(['lp_categories']); const { t } = useTranslation(['user_metas']);
const { id: userId } = useParams<{ id: string }>();
React.useEffect(() => { React.useEffect(() => {
// console.log('helloworld'); // console.log('helloworld');
@@ -49,6 +51,7 @@ export default function Page(): React.JSX.Element {
</div> </div>
</Stack> </Stack>
<UserMetaEditForm /> <UserMetaEditForm />
<UserActivationEditForm userId={userId} />
</Stack> </Stack>
</Box> </Box>
); );

View File

@@ -15,12 +15,6 @@ import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography'; 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 { UserMetasFilters } from '@/components/dashboard/user_meta/user-metas-filters';
import { UserMetasPagination } from '@/components/dashboard/user_meta/user-metas-pagination';
import { UserMetasSelectionProvider } from '@/components/dashboard/user_meta/user-metas-selection-context';
import { UserMetasTable } from '@/components/dashboard/user_meta/user-metas-table';
import type { UserMeta } from '@/components/dashboard/user_meta/type.d';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
@@ -29,13 +23,18 @@ import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb'; import { pb } from '@/lib/pb';
import ErrorDisplay from '@/components/dashboard/error'; import ErrorDisplay from '@/components/dashboard/error';
import { defaultUserMeta } from '@/components/dashboard/user_meta/_constants'; import { defaultUserMeta } from '@/components/dashboard/user_meta/_constants';
import type { UserMeta } from '@/components/dashboard/user_meta/type.d';
import { UserMetasFilters } from '@/components/dashboard/user_meta/user-metas-filters';
import { UserMetasPagination } from '@/components/dashboard/user_meta/user-metas-pagination';
import { UserMetasSelectionProvider } from '@/components/dashboard/user_meta/user-metas-selection-context';
import { UserMetasTable } from '@/components/dashboard/user_meta/user-metas-table';
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(['teachers']); const { t } = useTranslation(['user_metas']);
const router = useRouter(); const router = useRouter();
const { email, phone, sortDir, status } = searchParams; const { email, phone, sortDir, state } = searchParams;
const [userMetaData, setUserMetaData] = React.useState<UserMeta[]>([]); const [userMetaData, setUserMetaData] = React.useState<UserMeta[]>([]);
@@ -97,11 +96,11 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {
let tempFilter = []; const tempFilter = [];
let tempSortDir = ''; let tempSortDir = '';
if (status) { if (state) {
tempFilter.push(`status = "${status}"`); tempFilter.push(`state = "${state}"`);
} }
if (sortDir) { if (sortDir) {
@@ -124,7 +123,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
preFinalListOption = { ...preFinalListOption, sort: tempSortDir }; preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
} }
setListOption(preFinalListOption); setListOption(preFinalListOption);
}, [sortDir, email, phone, status]); }, [sortDir, email, phone, state]);
if (showLoading) return <FormLoading />; if (showLoading) return <FormLoading />;
@@ -172,7 +171,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
<UserMetasSelectionProvider userMetas={f}> <UserMetasSelectionProvider userMetas={f}>
<Card> <Card>
<UserMetasFilters <UserMetasFilters
filters={{ email, phone, status }} filters={{ email, phone, state }}
fullData={userMetaData} fullData={userMetaData}
sortDir={sortDir} sortDir={sortDir}
/> />
@@ -206,7 +205,7 @@ interface PageProps {
email?: string; email?: string;
phone?: string; phone?: string;
sortDir?: 'asc' | 'desc'; sortDir?: 'asc' | 'desc';
status?: string; state?: string;
// //
}; };
} }

View File

@@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next';
import { PropertyItem } from '@/components/core/property-item'; import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list'; import { PropertyList } from '@/components/core/property-list';
// import { CrCategory } from '@/components/dashboard/cr/categories/type'; // import { CrCategory } from '@/components/dashboard/cr/categories/type';
import type { UserMeta } from '@/components/dashboard/user_meta/type.d'; import type { UserMeta } from '@/components/dashboard/user_meta/type_move.d';
export default function BasicDetailCard({ export default function BasicDetailCard({
userMeta, userMeta,

View File

@@ -9,13 +9,9 @@ import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Teacher } from '@/components/dashboard/teacher/type.d';
// import type { CrCategory } from '@/components/dashboard/cr/categories/type'; import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { Teacher } from '@/components/dashboard/teacher/type.d';
function getImageUrlFrRecord(teacher: Teacher): string {
return `http://127.0.0.1:8090/api/files/${teacher.collectionId}/${teacher.id}/${teacher.avatar}`;
}
export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teacher }): React.JSX.Element { export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teacher }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -29,7 +25,7 @@ export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teac
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(teacherRecord)} src={getImageUrlFromFile(teacherRecord.collectionId, teacherRecord.id, teacherRecord.avatar)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -1,5 +1,9 @@
'use client'; 'use client';
// src/app/dashboard/user_metas/view/[id]/page.tsx
// PURPOSE
// T.B.A.
//
import * as React from 'react'; import * as React from 'react';
import RouterLink from 'next/link'; import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation'; import { useParams, useRouter } from 'next/navigation';
@@ -7,7 +11,7 @@ import SampleAddressCard from '@/app/dashboard/Sample/AddressCard';
import { SampleNotifications } from '@/app/dashboard/Sample/Notifications'; import { SampleNotifications } from '@/app/dashboard/Sample/Notifications';
import SamplePaymentCard from '@/app/dashboard/Sample/PaymentCard'; import SamplePaymentCard from '@/app/dashboard/Sample/PaymentCard';
import SampleSecurityCard from '@/app/dashboard/Sample/SecurityCard'; import SampleSecurityCard from '@/app/dashboard/Sample/SecurityCard';
import { COL_USER_METAS } from '@/constants';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Link from '@mui/material/Link'; import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
@@ -21,16 +25,14 @@ import { paths } from '@/paths';
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 { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error'; import ErrorDisplay from '@/components/dashboard/error';
import { defaultUserMeta } from '@/components/dashboard/user_meta/_constants';
import { Notifications } from '@/components/dashboard/user_meta/notifications'; import { Notifications } from '@/components/dashboard/user_meta/notifications';
import type { UserMeta } from '@/components/dashboard/user_meta/type_move.d';
import FormLoading from '@/components/loading'; import FormLoading from '@/components/loading';
import BasicDetailCard from './BasicDetailCard'; import BasicDetailCard from './BasicDetailCard';
import TitleCard from './TitleCard'; import TitleCard from './TitleCard';
import { defaultUserMeta } from '@/components/dashboard/user_meta/_constants';
import type { UserMeta } from '@/components/dashboard/user_meta/type.d';
import { COL_USER_METAS } from '@/constants';
export default function Page(): React.JSX.Element { export default function Page(): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type'; // import type { LessonCategory } from '@/components/dashboard/lp_categories/type';

View File

@@ -34,6 +34,7 @@ import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { logger } from '@/lib/default-logger'; import { logger } from '@/lib/default-logger';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import { pb } from '@/lib/pb'; import { pb } from '@/lib/pb';
import { PropertyItem } from '@/components/core/property-item'; import { PropertyItem } from '@/components/core/property-item';
import { PropertyList } from '@/components/core/property-list'; import { PropertyList } from '@/components/core/property-list';
@@ -128,7 +129,11 @@ export default function Page(): React.JSX.Element {
sx={{ alignItems: 'center', flex: '1 1 auto' }} sx={{ alignItems: 'center', flex: '1 1 auto' }}
> >
<Avatar <Avatar
src={`http://127.0.0.1:8090/api/files/${showLessonCategory.collectionId}/${showLessonCategory.id}/${showLessonCategory.cat_image}`} src={getImageUrlFromFile(
showLessonCategory.collectionId,
showLessonCategory.id,
showLessonCategory.image
)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
variant="rounded" variant="rounded"
> >

View File

@@ -6,6 +6,9 @@ import ListItemIcon from '@mui/material/ListItemIcon';
import { Box } from '@mui/system'; import { Box } from '@mui/system';
import { Trash as TrashIcon } from '@phosphor-icons/react/dist/ssr/Trash'; import { Trash as TrashIcon } from '@phosphor-icons/react/dist/ssr/Trash';
import { logger } from '@/lib/default-logger';
import { toast } from '@/components/core/toaster';
interface PropsHelloworld { interface PropsHelloworld {
message: string; message: string;
} }
@@ -33,11 +36,21 @@ function InnerComponent(): React.JSX.Element {
// RULES: sample of main component // RULES: sample of main component
function MainComponent(): React.JSX.Element { function MainComponent(): React.JSX.Element {
const [state, setState] = React.useState<string>(''); const [state, setState] = React.useState<string>('');
const [loading, setLoading] = React.useState(true);
const [showError, setShowError] = React.useState<boolean>(false);
React.useEffect(() => { React.useEffect(() => {
try {
setState(funcHelloworld('hello')); setState(funcHelloworld('hello'));
} catch (error) {
setShowError(true);
}
setLoading(false);
}, []); }, []);
if (loading) return <>Loading</>;
if (showError) return <>Error</>;
// you should obey react/jsx-no-useless-fragment // you should obey react/jsx-no-useless-fragment
return ( return (
<Box> <Box>

View File

@@ -0,0 +1,5 @@
export interface OAuthProvider {
id: 'google' | 'discord' | 'github';
name: string;
logo: string;
}

Some files were not shown because too many files have changed in this diff Show More