update edit lesson categories,
This commit is contained in:
@@ -83,5 +83,5 @@ 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'],
|
ignorePatterns: ['**/*del', '**/*bak', '**/*copy.*', '**/*copy*.*'],
|
||||||
};
|
};
|
||||||
|
2
002_source/cms/.gitignore
vendored
2
002_source/cms/.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
**/*copy.tsx
|
||||||
|
|
||||||
# **/repomix-output.xml
|
# **/repomix-output.xml
|
||||||
**/*:Zone.Identifier
|
**/*:Zone.Identifier
|
||||||
**/*bak
|
**/*bak
|
||||||
|
22
002_source/cms/public/locales/dev/PROMPT.md
Normal file
22
002_source/cms/public/locales/dev/PROMPT.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# PROMPT
|
||||||
|
|
||||||
|
please update `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/public/locales/dev/translation.json`
|
||||||
|
to add below translations, thanks.
|
||||||
|
|
||||||
|
```
|
||||||
|
dashboard.lessonCategories.edit.title
|
||||||
|
dashboard.lessonCategories.edit.avatar
|
||||||
|
dashboard.lessonCategories.edit.avatarRequirements
|
||||||
|
dashboard.lessonCategories.edit.name
|
||||||
|
dashboard.lessonCategories.edit.type
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
|
||||||
|
please read `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lesson_category/lesson-category-edit-form.tsx`
|
||||||
|
and update `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/public/locales/dev/translation.json` the missing translations, thanks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
please refactor `common.json` to smaller translation files, thanks
|
||||||
|
e.g. `lessonTypes` -> `lesson_type`
|
135
002_source/cms/public/locales/dev/common.json
Normal file
135
002_source/cms/public/locales/dev/common.json
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
{
|
||||||
|
"languageChanged": "語言已更改",
|
||||||
|
"type": "類型",
|
||||||
|
"helloworld": "你好,世界",
|
||||||
|
"Add": "新增項目",
|
||||||
|
"visible": "可視",
|
||||||
|
"hidden": "屏閉",
|
||||||
|
"Hidden": "屏閉",
|
||||||
|
"Newest": "最新",
|
||||||
|
"Oldest": "最舊",
|
||||||
|
"Lesson position": "位置",
|
||||||
|
"Lesson type": "種類",
|
||||||
|
"Type": "種類",
|
||||||
|
"Name": "名稱",
|
||||||
|
"Filter by name": "依名稱過濾",
|
||||||
|
"Filter by type": "依類型過濾",
|
||||||
|
"Actions": "動作",
|
||||||
|
"Created at": "建立時間",
|
||||||
|
"Updated at": "更新時間",
|
||||||
|
"Position": "位置",
|
||||||
|
"Visible": "顯示",
|
||||||
|
"Apply": "套用",
|
||||||
|
"Overview": "概觀",
|
||||||
|
"Dashboard": "儀表板",
|
||||||
|
"Tickets": "票券",
|
||||||
|
"Sign ups": "註冊",
|
||||||
|
"Open issues": "開啟問題",
|
||||||
|
"Closed issues": "關閉問題",
|
||||||
|
"increase": "增加",
|
||||||
|
"decrease": "減少",
|
||||||
|
"common.loading": "載入中",
|
||||||
|
"vs last month": "與上個月相比",
|
||||||
|
"Dashboards": "儀表板",
|
||||||
|
"App usage": "應用程式使用情況",
|
||||||
|
"Find your dream job": "找到你的夢想工作",
|
||||||
|
"Need help figuring things out?": "需要幫助嗎?",
|
||||||
|
"Search for jobs that match your skills and apply to them directly": "搜索符合你技能的工作,直接申請",
|
||||||
|
"Find answers to your questions and get in touch with our team.": "找到你的問題並與我們的團隊聯繫",
|
||||||
|
"Explore documentation": "探索文件",
|
||||||
|
"Learn how to get started with our product and make the most of it.": "學習如何開始使用我們的產品並充分利用它",
|
||||||
|
"Search Jobs": "搜索工作",
|
||||||
|
"Help Center": "幫助中心",
|
||||||
|
"Documentation": "文件",
|
||||||
|
"increase_in_app_usage_with": "使用者量增加",
|
||||||
|
"new_products_purchased": "新產品購買",
|
||||||
|
"Our subscriptions": "我們的訂閱",
|
||||||
|
"this_year": "今年",
|
||||||
|
"is_forecasted_to_increase_in_your_traffic_by_the_end_of_the_current_month": "今年預計增加你的流量",
|
||||||
|
"Jan": "一月",
|
||||||
|
"Feb": "二月",
|
||||||
|
"Mar": "三月",
|
||||||
|
"Apr": "四月",
|
||||||
|
"May": "五月",
|
||||||
|
"Jun": "六月",
|
||||||
|
"Jul": "七月",
|
||||||
|
"Aug": "八月",
|
||||||
|
"Sep": "九月",
|
||||||
|
"Oct": "十月",
|
||||||
|
"Nov": "十一月",
|
||||||
|
"Dec": "十二月",
|
||||||
|
"See all subscriptions": "查看所有訂閱",
|
||||||
|
"app_chat": "應用程式聊天",
|
||||||
|
"Upcoming events": "即將發生的事件",
|
||||||
|
"Based on the linked bank accounts": "基於連結的銀行帳戶",
|
||||||
|
"App limits": "應用程式限制",
|
||||||
|
"You have used {percentage} of your available spots.": "您已使用 {percentage} 的可用座位。升級計劃以建立更多專案",
|
||||||
|
"userMessagesUnread": "hello userMessagesUnread",
|
||||||
|
"Upgrade plan to create more projects": "升級計劃以建立更多專案",
|
||||||
|
"You have almost reached your limit": "您已接近您的限制",
|
||||||
|
"Upgrade plan": "升級計劃",
|
||||||
|
"Help center": "幫助中心",
|
||||||
|
"Jobs": "工作",
|
||||||
|
"go_to_chat": "前往聊天",
|
||||||
|
"See all events": "查看所有事件",
|
||||||
|
"You have used": "您已使用",
|
||||||
|
"of your available spots": "的可用座位",
|
||||||
|
"Paid": "已付款",
|
||||||
|
"Canceled": "已取消",
|
||||||
|
"Pending": "待付款",
|
||||||
|
"Expiring": "已過期",
|
||||||
|
"Refunded": "已退款",
|
||||||
|
"Total": "總計",
|
||||||
|
"Total paid": "已付款",
|
||||||
|
"Total cancelled": "已取消",
|
||||||
|
"Total pending": "待付款",
|
||||||
|
"year": "年",
|
||||||
|
"month": "月",
|
||||||
|
"week": "週",
|
||||||
|
"day": "日",
|
||||||
|
"hour": "小時",
|
||||||
|
"minute": "分鐘",
|
||||||
|
"second": "秒",
|
||||||
|
"ago": "前",
|
||||||
|
"remaining": "剩下",
|
||||||
|
"days": "天",
|
||||||
|
"hours": "小時",
|
||||||
|
"minutes": "分鐘",
|
||||||
|
"seconds": "秒",
|
||||||
|
"Clear filters": "清除篩選",
|
||||||
|
"Type information": "輸入資訊",
|
||||||
|
"Cancel": "取消",
|
||||||
|
"Delete": "刪除",
|
||||||
|
"All": "全部",
|
||||||
|
"categories": "課程分類",
|
||||||
|
"loading": "載入中",
|
||||||
|
"Notifications": "通知",
|
||||||
|
"Mark all as read": "標記所有為已讀",
|
||||||
|
"There are no notifications": "沒有通知",
|
||||||
|
"listenings": "聽講 (Listenings)",
|
||||||
|
"question_category": "問題分類",
|
||||||
|
"question_list": "問題",
|
||||||
|
"matching_frenzy": "配對 (Matching Frenzy)",
|
||||||
|
"connective_revision": "連接詞 (Connective Revision)",
|
||||||
|
"settings": "設定",
|
||||||
|
"students": "學生",
|
||||||
|
"dashboard.lessonCategories.add": "新增課程分類",
|
||||||
|
"dashboard.lessonCategories.title": "課程分類",
|
||||||
|
"dashboard.lessonCategorys.edit.name": "課程分類名稱",
|
||||||
|
"dashboard.lessonCategorys.edit.position": "課程分類順序",
|
||||||
|
"dashboard.lessonCategorys.edit.title": "編輯課程分類",
|
||||||
|
"dashboard.lessonCategorys.edit.visibleToUser": "顯示給使用者",
|
||||||
|
"dashboard.lessonCategorys.edit.visible": "顯示",
|
||||||
|
"dashboard.lessonCategorys.edit.hidden": "隱藏",
|
||||||
|
"dashboard.lessonCategorys.edit.cancelButton": "取消",
|
||||||
|
"dashboard.lessonCategorys.edit.updateButton": "更新",
|
||||||
|
"dashboard.lessonCategorys.list.title": "課堂種類",
|
||||||
|
"word-count": "字數",
|
||||||
|
"unable-to-process-request": "無法處理請求",
|
||||||
|
"detailed-error-information": "詳細錯誤資訊",
|
||||||
|
"error": {
|
||||||
|
"unable-to-process-request": "無法處理您的請求",
|
||||||
|
"detailed-error-information": "詳細錯誤資訊"
|
||||||
|
},
|
||||||
|
"name-is-required": "名稱為必填"
|
||||||
|
}
|
69
002_source/cms/public/locales/dev/lesson_category.json
Normal file
69
002_source/cms/public/locales/dev/lesson_category.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"add": "新增課程分類",
|
||||||
|
"title": "課程分類",
|
||||||
|
"basic_info": "基本資訊",
|
||||||
|
"error": {
|
||||||
|
"unable-to-process-request": "無法處理您的請求",
|
||||||
|
"detailed-error-information": "詳細錯誤資訊"
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"title": "建立課程類型",
|
||||||
|
"createButton": "建立",
|
||||||
|
"cancelButton": "取消",
|
||||||
|
"message": "請輸入課程類型名稱",
|
||||||
|
"success": "課程類型建立成功",
|
||||||
|
"error": "課程類型建立失敗",
|
||||||
|
"typeInformation": "輸入課程類型資訊",
|
||||||
|
"avatar": "上傳課程類型圖片",
|
||||||
|
"avatar_select": "選擇圖片",
|
||||||
|
"avatarRequirements": "最小 400x400 像素,PNG 或 JPEG",
|
||||||
|
"select": "請選擇",
|
||||||
|
"name": "課程類型名稱",
|
||||||
|
"position": "課程類型順序",
|
||||||
|
"visibleToUser": "顯示給使用者",
|
||||||
|
"detail-information": "詳細資訊",
|
||||||
|
"description": "課程類型描述",
|
||||||
|
"remarks": "備註"
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"avatar": "上傳圖片",
|
||||||
|
"avatarRequirements": "最小 400x400 像素,PNG 或 JPEG",
|
||||||
|
"cancelButton": "取消",
|
||||||
|
"error": "課程類型更新失敗",
|
||||||
|
"hidden": "隱藏",
|
||||||
|
"name": "課程分類名稱",
|
||||||
|
"position": "課程分類順序",
|
||||||
|
"select": "請選擇",
|
||||||
|
"success": "課程類型更新成功",
|
||||||
|
"title": "編輯課程分類",
|
||||||
|
"basic-info": "基本資訊",
|
||||||
|
"type": "類型",
|
||||||
|
"updateButton": "更新",
|
||||||
|
"visible": "顯示",
|
||||||
|
"visibleToUser": "顯示給使用者",
|
||||||
|
"write-something": "輸入一些內容"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"title": "刪除課程類型",
|
||||||
|
"message": "確定要刪除課程類型嗎?",
|
||||||
|
"success": "課程類型刪除成功",
|
||||||
|
"error": "課程類型刪除失敗",
|
||||||
|
"cancelButton": "取消",
|
||||||
|
"deleteButton": "刪除"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "課堂種類",
|
||||||
|
"message": "請選擇課程類型",
|
||||||
|
"empty": {
|
||||||
|
"title": "目前沒有課程類型",
|
||||||
|
"message": "請建立課程類型",
|
||||||
|
"create": "建立課程類型"
|
||||||
|
},
|
||||||
|
"error": "課程類型載入失敗"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"basic-details": "基本資訊"
|
||||||
|
},
|
||||||
|
"Delete Lesson Type ?": "刪除課程類型?",
|
||||||
|
"Are you sure you want to delete lesson type ?": "確定要刪除課程類型嗎?"
|
||||||
|
}
|
65
002_source/cms/public/locales/dev/lesson_type.json
Normal file
65
002_source/cms/public/locales/dev/lesson_type.json
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"add": "新增課程類型",
|
||||||
|
"title": "課程類型",
|
||||||
|
"type": "類型",
|
||||||
|
"error": {
|
||||||
|
"unable-to-process-request": "無法處理您的請求",
|
||||||
|
"detailed-error-information": "詳細錯誤資訊"
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"title": "建立課程類型",
|
||||||
|
"createButton": "建立",
|
||||||
|
"cancelButton": "取消",
|
||||||
|
"message": "請輸入課程類型名稱",
|
||||||
|
"success": "課程類型建立成功",
|
||||||
|
"error": "課程類型建立失敗",
|
||||||
|
"typeInformation": "輸入課程類型資訊",
|
||||||
|
"avatar": "上傳課程類型圖片",
|
||||||
|
"avatarRequirements": "上傳課程類型圖片",
|
||||||
|
"select": "請選擇",
|
||||||
|
"name": "課程類型名稱",
|
||||||
|
"position": "課程類型順序",
|
||||||
|
"visibleToUser": "顯示給使用者"
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"avatar": "上傳課程類型圖片",
|
||||||
|
"avatarRequirements": "上傳課程類型圖片",
|
||||||
|
"cancelButton": "取消",
|
||||||
|
"error": "課程類型更新失敗",
|
||||||
|
"hidden": "隱藏",
|
||||||
|
"name": "課程類型名稱",
|
||||||
|
"position": "課程類型順序",
|
||||||
|
"select": "請選擇",
|
||||||
|
"success": "課程類型更新成功",
|
||||||
|
"title": "編輯課程類型",
|
||||||
|
"typeInformation": "輸入課程類型資訊",
|
||||||
|
"updateButton": "更新",
|
||||||
|
"visible": "顯示",
|
||||||
|
"visibleToUser": "顯示給使用者"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"title": "刪除課程類型",
|
||||||
|
"message": "確定要刪除課程類型嗎?",
|
||||||
|
"success": "課程類型刪除成功",
|
||||||
|
"error": "課程類型刪除失敗",
|
||||||
|
"cancelButton": "取消",
|
||||||
|
"deleteButton": "刪除"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "課程類型列表",
|
||||||
|
"message": "請選擇課程類型",
|
||||||
|
"empty": {
|
||||||
|
"title": "目前沒有課程類型",
|
||||||
|
"message": "請建立課程類型",
|
||||||
|
"create": "建立課程類型"
|
||||||
|
},
|
||||||
|
"error": "課程類型載入失敗"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"basic-details": "基本資訊"
|
||||||
|
},
|
||||||
|
"Lesson Type": "課程類型",
|
||||||
|
"Lesson Types": "課程類型",
|
||||||
|
"Delete Lesson Type ?": "刪除課程類型?",
|
||||||
|
"Are you sure you want to delete lesson type ?": "確定要刪除課程類型嗎?"
|
||||||
|
}
|
@@ -188,5 +188,8 @@
|
|||||||
"dashboard.lessonCategorys.edit.cancelButton": "取消",
|
"dashboard.lessonCategorys.edit.cancelButton": "取消",
|
||||||
"dashboard.lessonCategorys.edit.updateButton": "更新",
|
"dashboard.lessonCategorys.edit.updateButton": "更新",
|
||||||
"dashboard.lessonCategorys.list.title": "課堂種類",
|
"dashboard.lessonCategorys.list.title": "課堂種類",
|
||||||
"word-count": "字數"
|
"word-count": "字數",
|
||||||
|
"dashboard.lessonTypes.list.error": "課程類型載入失敗",
|
||||||
|
"unable-to-process-request": "無法處理請求",
|
||||||
|
"detailed-error-information": "詳細錯誤資訊"
|
||||||
}
|
}
|
3
002_source/cms/src/.vscode/settings.json
vendored
Normal file
3
002_source/cms/src/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"git.ignoreLimitWarning": true
|
||||||
|
}
|
3
002_source/cms/src/app/dashboard/lesson_categories/.vscode/settings.json
vendored
Normal file
3
002_source/cms/src/app/dashboard/lesson_categories/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"git.ignoreLimitWarning": true
|
||||||
|
}
|
@@ -38,24 +38,25 @@ 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';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
|
import ErrorDisplay from '@/components/dashboard/error';
|
||||||
import { defaultLessonCategory } from '@/components/dashboard/lesson_category/_constants.ts';
|
import { defaultLessonCategory } from '@/components/dashboard/lesson_category/_constants.ts';
|
||||||
// import { defaultLessonCategory } from '@/components/dashboard/lesson_category/defaultLessonCategory';
|
// import { defaultLessonCategory } from '@/components/dashboard/lesson_category/defaultLessonCategory';
|
||||||
import { Notifications } from '@/components/dashboard/lesson_category/notifications';
|
import { Notifications } from '@/components/dashboard/lesson_category/notifications';
|
||||||
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 type { LessonCategory } from '@/components/dashboard/lesson_category/type.d.ts';
|
import type { LessonCategory } from '@/components/dashboard/lesson_category/types';
|
||||||
import FormLoading from '@/components/loading';
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
// export const metadata = { title: `Details | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
|
// export const metadata = { title: `Details | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
|
||||||
|
|
||||||
export default function Page(): React.JSX.Element {
|
export default function Page(): React.JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation(['common']);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
//
|
//
|
||||||
const { cat_id: catId } = useParams<{ cat_id: string }>();
|
const { cat_id: catId } = useParams<{ cat_id: string }>();
|
||||||
//
|
//
|
||||||
const [showLoadin, setIsLoading] = React.useState<boolean>(true);
|
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||||
const [showError, setShowError] = React.useState<boolean>(false);
|
const [showError, setShowError] = React.useState<boolean>(false);
|
||||||
//
|
//
|
||||||
const [showLessonCategory, setShowLessonCategory] = React.useState<LessonCategory>(defaultLessonCategory);
|
const [showLessonCategory, setShowLessonCategory] = React.useState<LessonCategory>(defaultLessonCategory);
|
||||||
@@ -70,16 +71,29 @@ export default function Page(): React.JSX.Element {
|
|||||||
.getOne(catId)
|
.getOne(catId)
|
||||||
.then((model: RecordModel) => {
|
.then((model: RecordModel) => {
|
||||||
setShowLessonCategory({ ...defaultLessonCategory, ...model });
|
setShowLessonCategory({ ...defaultLessonCategory, ...model });
|
||||||
setIsLoading(false);
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
toast(t('dashboard.lessonTypes.list.error'));
|
toast(t('dashboard.lessonTypes.list.error'));
|
||||||
|
|
||||||
|
setShowError(true);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setShowLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [catId]);
|
}, [catId]);
|
||||||
|
|
||||||
if (showLoadin) return <FormLoading />;
|
if (showLoading) return <FormLoading />;
|
||||||
|
|
||||||
|
if (showError)
|
||||||
|
return (
|
||||||
|
<ErrorDisplay
|
||||||
|
message={t('error.unable-to-process-request', { ns: 'common' })}
|
||||||
|
code="500"
|
||||||
|
details={t('error.detailed-error-information', { ns: 'common' })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -106,8 +120,12 @@ export default function Page(): React.JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
||||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}>
|
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}>
|
||||||
<Avatar src="/assets/avatar-1.png" sx={{ '--Avatar-size': '64px' }}>
|
<Avatar
|
||||||
MV
|
src={`http://127.0.0.1:8090/api/files/${showLessonCategory.collectionId}/${showLessonCategory.id}/${showLessonCategory.cat_image}`}
|
||||||
|
sx={{ '--Avatar-size': '64px' }}
|
||||||
|
variant="rounded"
|
||||||
|
>
|
||||||
|
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' }}>
|
||||||
@@ -150,7 +168,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
<UserIcon fontSize="var(--Icon-fontSize)" />
|
<UserIcon fontSize="var(--Icon-fontSize)" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
}
|
}
|
||||||
title="Basic details"
|
title={t('basic-details', { ns: 'lesson_category' })}
|
||||||
/>
|
/>
|
||||||
<PropertyList
|
<PropertyList
|
||||||
divider={<Divider />}
|
divider={<Divider />}
|
||||||
@@ -199,7 +217,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
<ShieldWarningIcon fontSize="var(--Icon-fontSize)" />
|
<ShieldWarningIcon fontSize="var(--Icon-fontSize)" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
}
|
}
|
||||||
title="Security"
|
title={t('security', { ns: 'lesson_category' })}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
@@ -272,7 +290,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
|
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
}
|
}
|
||||||
title="Billing details"
|
title={t('billing-details', { ns: 'lesson_category' })}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Card sx={{ borderRadius: 1 }} variant="outlined">
|
<Card sx={{ borderRadius: 1 }} variant="outlined">
|
||||||
@@ -307,7 +325,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
<HouseIcon fontSize="var(--Icon-fontSize)" />
|
<HouseIcon fontSize="var(--Icon-fontSize)" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
}
|
}
|
||||||
title="Shipping addresses"
|
title={t('shipping-addresses', { ns: 'lesson_category' })}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
|
@@ -34,11 +34,11 @@ export default function Page(): React.JSX.Element {
|
|||||||
variant="subtitle2"
|
variant="subtitle2"
|
||||||
>
|
>
|
||||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
{t('dashboard.lessonCategories.title')}
|
{t('title', { ns: 'lesson_category' })}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Typography variant="h4">{t('dashboard.lessonCategories.create.title')}</Typography>
|
<Typography variant="h4">{t('create.title', { ns: 'lesson_category' })}</Typography>
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
<LessonCategoryCreateForm />
|
<LessonCategoryCreateForm />
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
# task
|
||||||
|
|
||||||
|
## instruction
|
||||||
|
|
||||||
|
with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/_helloworld/page.tsx`
|
||||||
|
|
||||||
|
with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_types/edit/[typeId]/page.tsx`
|
||||||
|
|
||||||
|
please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_categories/edit/page.tsx`
|
||||||
|
|
||||||
|
please draft a tsx for showing error to user thanks,
|
@@ -0,0 +1,55 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import RouterLink from 'next/link';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Link from '@mui/material/Link';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { paths } from '@/paths';
|
||||||
|
import { LessonCategoryEditForm } from '@/components/dashboard/lesson_category/lesson-category-edit-form';
|
||||||
|
|
||||||
|
// import { LessonCategoryEditForm } from '@/components/dashboard/lesson_category/lesson-category-edit-form';
|
||||||
|
|
||||||
|
export default function Page(): React.JSX.Element {
|
||||||
|
const { t } = useTranslation(['common', 'lesson_category']);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log('helloworld');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: 'var(--Content-maxWidth)',
|
||||||
|
m: 'var(--Content-margin)',
|
||||||
|
p: 'var(--Content-padding)',
|
||||||
|
width: 'var(--Content-width)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
color="text.primary"
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.dashboard.lesson_categories.list}
|
||||||
|
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
|
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
|
{t('edit.title', { ns: 'lesson_category' })}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography variant="h4">{t('edit.title', { ns: 'lesson_category' })}</Typography>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
<LessonCategoryEditForm />
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
import { LessonCategory } from '@/components/dashboard/lesson_category/type';
|
import { LessonCategory } from '@/components/dashboard/lesson_category/types';
|
||||||
|
|
||||||
// import type { LessonCategory } from '@/components/dashboard/lesson_category/lesson-categories-table';
|
// import type { LessonCategory } from '@/components/dashboard/lesson_category/lesson-categories-table';
|
||||||
// import type { LessonCategory } from '@/components/dashboard/lesson_category/interfaces';
|
// import type { LessonCategory } from '@/components/dashboard/lesson_category/interfaces';
|
||||||
@@ -14,6 +14,7 @@ export const lessonCategoriesSampleData = [
|
|||||||
quota: 50,
|
quota: 50,
|
||||||
status: 'active',
|
status: 'active',
|
||||||
createdAt: dayjs().subtract(1, 'hour').toDate(),
|
createdAt: dayjs().subtract(1, 'hour').toDate(),
|
||||||
|
collectionId: '0000000001',
|
||||||
cat_name: '',
|
cat_name: '',
|
||||||
pos: 99,
|
pos: 99,
|
||||||
visible: 'visible',
|
visible: 'visible',
|
||||||
@@ -30,6 +31,7 @@ export const lessonCategoriesSampleData = [
|
|||||||
quota: 100,
|
quota: 100,
|
||||||
status: 'active',
|
status: 'active',
|
||||||
createdAt: dayjs().subtract(3, 'hour').toDate(),
|
createdAt: dayjs().subtract(3, 'hour').toDate(),
|
||||||
|
collectionId: '0000000001',
|
||||||
cat_name: '',
|
cat_name: '',
|
||||||
pos: 99,
|
pos: 99,
|
||||||
visible: 'visible',
|
visible: 'visible',
|
||||||
@@ -46,6 +48,7 @@ export const lessonCategoriesSampleData = [
|
|||||||
quota: 10,
|
quota: 10,
|
||||||
status: 'blocked',
|
status: 'blocked',
|
||||||
createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
|
createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
|
||||||
|
collectionId: '0000000001',
|
||||||
cat_name: '',
|
cat_name: '',
|
||||||
pos: 99,
|
pos: 99,
|
||||||
visible: 'visible',
|
visible: 'visible',
|
||||||
@@ -62,6 +65,7 @@ export const lessonCategoriesSampleData = [
|
|||||||
quota: 0,
|
quota: 0,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
|
createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
|
||||||
|
collectionId: '0000000001',
|
||||||
cat_name: '',
|
cat_name: '',
|
||||||
pos: 99,
|
pos: 99,
|
||||||
visible: 'visible',
|
visible: 'visible',
|
||||||
@@ -78,6 +82,7 @@ export const lessonCategoriesSampleData = [
|
|||||||
quota: 50,
|
quota: 50,
|
||||||
status: 'active',
|
status: 'active',
|
||||||
createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
|
createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
|
||||||
|
collectionId: '0000000001',
|
||||||
cat_name: '',
|
cat_name: '',
|
||||||
pos: 99,
|
pos: 99,
|
||||||
visible: 'visible',
|
visible: 'visible',
|
||||||
|
@@ -25,7 +25,7 @@ import type { Filters } from '@/components/dashboard/lesson_category/lesson-cate
|
|||||||
import { LessonCategoriesPagination } from '@/components/dashboard/lesson_category/lesson-categories-pagination';
|
import { LessonCategoriesPagination } from '@/components/dashboard/lesson_category/lesson-categories-pagination';
|
||||||
import { LessonCategoriesSelectionProvider } from '@/components/dashboard/lesson_category/lesson-categories-selection-context';
|
import { LessonCategoriesSelectionProvider } from '@/components/dashboard/lesson_category/lesson-categories-selection-context';
|
||||||
import { LessonCategoriesTable } from '@/components/dashboard/lesson_category/lesson-categories-table';
|
import { LessonCategoriesTable } from '@/components/dashboard/lesson_category/lesson-categories-table';
|
||||||
import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
|
import type { LessonCategory } from '@/components/dashboard/lesson_category/types';
|
||||||
import FormLoading from '@/components/loading';
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
// import { lessonCategoriesSampleData } from './lesson-categories-sample-data';
|
// import { lessonCategoriesSampleData } from './lesson-categories-sample-data';
|
||||||
@@ -44,7 +44,7 @@ interface PageProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation(['common']);
|
||||||
const { email, phone, sortDir, spStatus } = searchParams;
|
const { email, phone, sortDir, spStatus } = searchParams;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -96,9 +96,9 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
if (showError)
|
if (showError)
|
||||||
return (
|
return (
|
||||||
<ErrorDisplay
|
<ErrorDisplay
|
||||||
message={t('"Unable to process request"')}
|
message={t('error.unable-to-process-request', { ns: 'common' })}
|
||||||
code="500"
|
code="500"
|
||||||
details={t('Detailed error information...')}
|
details={t('error.detailed-error-information', { ns: 'common' })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -36,6 +36,7 @@ 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';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
|
import ErrorDisplay from '@/components/dashboard/error';
|
||||||
// import { getLessonTypeById } from '@/components/dashboard/lesson_type/http-actions';
|
// import { getLessonTypeById } from '@/components/dashboard/lesson_type/http-actions';
|
||||||
import { LessonTypeDefaultValue, type LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
import { LessonTypeDefaultValue, type LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
||||||
import { defaultLessonType } from '@/components/dashboard/lesson_type/interfaces';
|
import { defaultLessonType } from '@/components/dashboard/lesson_type/interfaces';
|
||||||
@@ -43,14 +44,15 @@ import { Notifications } from '@/components/dashboard/lesson_type/notifications'
|
|||||||
import { Payments } from '@/components/dashboard/lesson_type/payments';
|
import { Payments } from '@/components/dashboard/lesson_type/payments';
|
||||||
import type { Address } from '@/components/dashboard/lesson_type/shipping-address';
|
import type { Address } from '@/components/dashboard/lesson_type/shipping-address';
|
||||||
import { ShippingAddress } from '@/components/dashboard/lesson_type/shipping-address';
|
import { ShippingAddress } from '@/components/dashboard/lesson_type/shipping-address';
|
||||||
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
export default function Page(): React.JSX.Element {
|
export default function Page(): React.JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation(['common']);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
//
|
||||||
const { type_id: typeId } = useParams<{ type_id: string }>();
|
const { type_id: typeId } = useParams<{ type_id: string }>();
|
||||||
//
|
//
|
||||||
const [showLoading, setIsLoading] = React.useState<boolean>(true);
|
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||||
const [showError, setShowError] = React.useState<boolean>(false);
|
const [showError, setShowError] = React.useState<boolean>(false);
|
||||||
//
|
//
|
||||||
const [showLessonType, setShowLessonType] = React.useState<LessonType>(LessonTypeDefaultValue);
|
const [showLessonType, setShowLessonType] = React.useState<LessonType>(LessonTypeDefaultValue);
|
||||||
@@ -63,18 +65,31 @@ export default function Page(): React.JSX.Element {
|
|||||||
if (typeId) {
|
if (typeId) {
|
||||||
pb.collection('LessonsTypes')
|
pb.collection('LessonsTypes')
|
||||||
.getOne(typeId)
|
.getOne(typeId)
|
||||||
.then((lessonType: RecordModel) => {
|
.then((model: RecordModel) => {
|
||||||
setShowLessonType({ ...defaultLessonType, ...lessonType });
|
setShowLessonType({ ...defaultLessonType, ...model });
|
||||||
setIsLoading(false);
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
toast(t('dashboard.lessonTypes.list.error'));
|
toast(t('dashboard.lessonTypes.list.error'));
|
||||||
|
|
||||||
|
setShowError(true);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setShowLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [typeId]);
|
}, [typeId]);
|
||||||
|
|
||||||
if (showLoading) return <div>{t('common.loading')}</div>;
|
if (showLoading) return <FormLoading />;
|
||||||
|
|
||||||
|
if (showError)
|
||||||
|
return (
|
||||||
|
<ErrorDisplay
|
||||||
|
message={t('error.unable-to-process-request', { ns: 'common' })}
|
||||||
|
code="500"
|
||||||
|
details={t('error.detailed-error-information', { ns: 'common' })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -96,13 +111,13 @@ export default function Page(): React.JSX.Element {
|
|||||||
variant="subtitle2"
|
variant="subtitle2"
|
||||||
>
|
>
|
||||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
{t('Lesson Types')}
|
{t('list.title', { ns: 'lesson_type' })}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
|
||||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}>
|
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}>
|
||||||
<Avatar src="/assets/avatar-1.png" sx={{ '--Avatar-size': '64px' }}>
|
<Avatar src="/assets/avatar-1.png" sx={{ '--Avatar-size': '64px' }}>
|
||||||
MV
|
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' }}>
|
||||||
@@ -145,7 +160,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
<UserIcon fontSize="var(--Icon-fontSize)" />
|
<UserIcon fontSize="var(--Icon-fontSize)" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
}
|
}
|
||||||
title="Basic details"
|
title={t('basic-details', { ns: 'lesson_type' })}
|
||||||
/>
|
/>
|
||||||
<PropertyList
|
<PropertyList
|
||||||
divider={<Divider />}
|
divider={<Divider />}
|
||||||
@@ -195,7 +210,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
<ShieldWarningIcon fontSize="var(--Icon-fontSize)" />
|
<ShieldWarningIcon fontSize="var(--Icon-fontSize)" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
}
|
}
|
||||||
title="Security"
|
title={t('security', { ns: 'lesson_type' })}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
@@ -268,7 +283,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
|
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
}
|
}
|
||||||
title="Billing details"
|
title={t('billing-details', { ns: 'lesson_type' })}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Card sx={{ borderRadius: 1 }} variant="outlined">
|
<Card sx={{ borderRadius: 1 }} variant="outlined">
|
||||||
@@ -303,7 +318,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
<HouseIcon fontSize="var(--Icon-fontSize)" />
|
<HouseIcon fontSize="var(--Icon-fontSize)" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
}
|
}
|
||||||
title="Shipping addresses"
|
title={t('shipping-addresses', { ns: 'lesson_type' })}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
|
@@ -17,6 +17,7 @@ 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 type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
|
||||||
import { emptyLessonType, safeAssignment } from '@/components/dashboard/lesson_type/interfaces';
|
import { emptyLessonType, safeAssignment } from '@/components/dashboard/lesson_type/interfaces';
|
||||||
import { LessonTypesFilters } from '@/components/dashboard/lesson_type/lesson-types-filters';
|
import { LessonTypesFilters } from '@/components/dashboard/lesson_type/lesson-types-filters';
|
||||||
@@ -50,6 +51,8 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
|
|
||||||
//
|
//
|
||||||
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
|
||||||
|
const [showLoading, setShowLoading] = React.useState<boolean>(true);
|
||||||
|
const [showError, setShowError] = React.useState<boolean>(false);
|
||||||
const [lessonTypesData, setLessonTypesData] = React.useState<LessonType[]>([]);
|
const [lessonTypesData, setLessonTypesData] = React.useState<LessonType[]>([]);
|
||||||
// const [recordModel, setRecordModel] = React.useState<RecordModel[]>([]);
|
// const [recordModel, setRecordModel] = React.useState<RecordModel[]>([]);
|
||||||
const sortedLessonTypes = applySort(lessonTypesData, sortDir);
|
const sortedLessonTypes = applySort(lessonTypesData, sortDir);
|
||||||
@@ -86,6 +89,11 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
toast(t('dashboard.lessonTypes.list.error'));
|
toast(t('dashboard.lessonTypes.list.error'));
|
||||||
|
|
||||||
|
setShowError(true);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setShowLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,7 +101,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
reloadRows();
|
reloadRows();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (lessonTypesData.length < 1) return <FormLoading />;
|
if (showLoading) return <FormLoading />;
|
||||||
|
|
||||||
|
if (showError)
|
||||||
|
return (
|
||||||
|
<ErrorDisplay message={t('unable-to-process-request')} code="500" details={t('detailed-error-information')} />
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
|
||||||
import type { CreateForm, LessonCategory } from './type';
|
import type { CreateForm, LessonCategory } from './types';
|
||||||
|
|
||||||
export const defaultLessonCategory: LessonCategory = {
|
export const defaultLessonCategory: LessonCategory = {
|
||||||
isEmpty: false,
|
isEmpty: false,
|
||||||
@@ -14,6 +14,7 @@ export const defaultLessonCategory: LessonCategory = {
|
|||||||
description: 'default-description',
|
description: 'default-description',
|
||||||
remarks: 'default-remarks',
|
remarks: 'default-remarks',
|
||||||
//
|
//
|
||||||
|
collectionId: '0000000000',
|
||||||
createdAt: dayjs('2099-01-01').toDate(),
|
createdAt: dayjs('2099-01-01').toDate(),
|
||||||
//
|
//
|
||||||
name: '',
|
name: '',
|
||||||
|
@@ -5,7 +5,7 @@ import * as React from 'react';
|
|||||||
import { useSelection } from '@/hooks/use-selection';
|
import { useSelection } from '@/hooks/use-selection';
|
||||||
import type { Selection } from '@/hooks/use-selection';
|
import type { Selection } from '@/hooks/use-selection';
|
||||||
|
|
||||||
import type { LessonCategory } from './type';
|
import type { LessonCategory } from './types';
|
||||||
|
|
||||||
// import type { LessonCategory } from './lesson-categories-table';
|
// import type { LessonCategory } from './lesson-categories-table';
|
||||||
// import type { LessonCategory } from '@/components/dashboard/lesson_category/interfaces';
|
// import type { LessonCategory } from '@/components/dashboard/lesson_category/interfaces';
|
||||||
|
@@ -28,7 +28,7 @@ import type { ColumnDef } from '@/components/core/data-table';
|
|||||||
import ConfirmDeleteModal from './confirm-delete-modal';
|
import ConfirmDeleteModal from './confirm-delete-modal';
|
||||||
// import type { LessonCategory } from './interfaces.1ts';
|
// import type { LessonCategory } from './interfaces.1ts';
|
||||||
import { useLessonCategoriesSelection } from './lesson-categories-selection-context';
|
import { useLessonCategoriesSelection } from './lesson-categories-selection-context';
|
||||||
import { LessonCategory } from './type';
|
import { LessonCategory } from './types';
|
||||||
|
|
||||||
function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonCategory>[] {
|
function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonCategory>[] {
|
||||||
return [
|
return [
|
||||||
@@ -43,7 +43,10 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonC
|
|||||||
variant="subtitle2"
|
variant="subtitle2"
|
||||||
>
|
>
|
||||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||||
<Avatar src={row.avatar} variant="rounded">
|
<Avatar
|
||||||
|
src={`http://127.0.0.1:8090/api/files/${row.collectionId}/${row.id}/${row.cat_image}`}
|
||||||
|
variant="rounded"
|
||||||
|
>
|
||||||
<ImagesIcon size={32} />
|
<ImagesIcon size={32} />
|
||||||
</Avatar>{' '}
|
</Avatar>{' '}
|
||||||
<div>
|
<div>
|
||||||
@@ -72,9 +75,6 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonC
|
|||||||
name: 'word-count',
|
name: 'word-count',
|
||||||
width: '100px',
|
width: '100px',
|
||||||
},
|
},
|
||||||
|
|
||||||
{ field: 'phone', name: 'Phone number', width: '100px' },
|
|
||||||
|
|
||||||
{
|
{
|
||||||
formatter: (row): React.JSX.Element => {
|
formatter: (row): React.JSX.Element => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
@@ -3,8 +3,10 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import RouterLink from 'next/link';
|
import RouterLink from 'next/link';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { COL_LESSON_CATEGORIES, NS_LESSON_CATEGORY } from '@/constants';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Avatar } from '@mui/material';
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
import { Avatar, Divider, MenuItem } from '@mui/material';
|
||||||
// import Avatar from '@mui/material/Avatar';
|
// import Avatar from '@mui/material/Avatar';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
@@ -12,7 +14,6 @@ import Card from '@mui/material/Card';
|
|||||||
import CardActions from '@mui/material/CardActions';
|
import CardActions from '@mui/material/CardActions';
|
||||||
import CardContent from '@mui/material/CardContent';
|
import CardContent from '@mui/material/CardContent';
|
||||||
import Checkbox from '@mui/material/Checkbox';
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
import Divider from '@mui/material/Divider';
|
|
||||||
import FormControl from '@mui/material/FormControl';
|
import FormControl from '@mui/material/FormControl';
|
||||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
import FormHelperText from '@mui/material/FormHelperText';
|
import FormHelperText from '@mui/material/FormHelperText';
|
||||||
@@ -25,11 +26,13 @@ import Grid from '@mui/material/Unstable_Grid2';
|
|||||||
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
|
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
|
||||||
// import axios from 'axios';
|
// import axios from 'axios';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z as zod } from 'zod';
|
import { z as zod } from 'zod';
|
||||||
|
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
import { logger } from '@/lib/default-logger';
|
import { logger } from '@/lib/default-logger';
|
||||||
import { Option } from '@/components/core/option';
|
import { Option } from '@/components/core/option';
|
||||||
|
import { TextEditor } from '@/components/core/text-editor/text-editor';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
|
|
||||||
import { fileToBase64 } from './file-to-base64';
|
import { fileToBase64 } from './file-to-base64';
|
||||||
@@ -71,6 +74,11 @@ const defaultValues = {
|
|||||||
|
|
||||||
export function LessonCategoryCreateForm(): React.JSX.Element {
|
export function LessonCategoryCreateForm(): React.JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation(['common', 'lesson_category']);
|
||||||
|
|
||||||
|
const NS_DEFAULT = { ns: 'lesson_category' };
|
||||||
|
|
||||||
|
const [isCreating, setIsCreating] = React.useState<boolean>(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -81,7 +89,8 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
|
|||||||
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
|
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
|
||||||
|
|
||||||
const onSubmit = React.useCallback(
|
const onSubmit = React.useCallback(
|
||||||
async (_: Values): Promise<void> => {
|
async (values: Values): Promise<void> => {
|
||||||
|
setIsCreating(true);
|
||||||
try {
|
try {
|
||||||
// Make API request
|
// Make API request
|
||||||
toast.success('Customer updated');
|
toast.success('Customer updated');
|
||||||
@@ -115,19 +124,20 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<Stack divider={<Divider />} spacing={4}>
|
<Stack divider={<Divider />} spacing={4}>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Typography variant="h6">Account information</Typography>
|
<Typography variant="h6">{t('create.typeInformation', NS_DEFAULT)}</Typography>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid xs={12}>
|
<Grid xs={12}>
|
||||||
<Stack direction="row" spacing={3} sx={{ alignItems: 'center' }}>
|
<Stack direction="row" spacing={3} sx={{ alignItems: 'center' }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
border: '1px dashed var(--mui-palette-divider)',
|
border: '1px dashed var(--mui-palette-divider)',
|
||||||
borderRadius: '50%',
|
borderRadius: '5%',
|
||||||
display: 'inline-flex',
|
display: 'inline-flex',
|
||||||
p: '4px',
|
p: '4px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
variant="rounded"
|
||||||
src={avatar}
|
src={avatar}
|
||||||
sx={{
|
sx={{
|
||||||
'--Avatar-size': '100px',
|
'--Avatar-size': '100px',
|
||||||
@@ -143,8 +153,8 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack spacing={1} sx={{ alignItems: 'flex-start' }}>
|
<Stack spacing={1} sx={{ alignItems: 'flex-start' }}>
|
||||||
<Typography variant="subtitle1">Avatar</Typography>
|
<Typography variant="subtitle1">{t('create.avatar', NS_DEFAULT)}</Typography>
|
||||||
<Typography variant="caption">Min 400x400px, PNG or JPEG</Typography>
|
<Typography variant="caption">{t('create.avatarRequirements', NS_DEFAULT)}</Typography>
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -152,7 +162,7 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
|
|||||||
}}
|
}}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
>
|
>
|
||||||
Select
|
{t('create.avatar_select', NS_DEFAULT)}
|
||||||
</Button>
|
</Button>
|
||||||
<input hidden onChange={handleAvatarChange} ref={avatarInputRef} type="file" />
|
<input hidden onChange={handleAvatarChange} ref={avatarInputRef} type="file" />
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -164,7 +174,7 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl error={Boolean(errors.name)} fullWidth>
|
<FormControl error={Boolean(errors.name)} fullWidth>
|
||||||
<InputLabel required>Name</InputLabel>
|
<InputLabel required>{t('create.name', NS_DEFAULT)}</InputLabel>
|
||||||
<OutlinedInput {...field} />
|
<OutlinedInput {...field} />
|
||||||
{errors.name ? <FormHelperText>{errors.name.message}</FormHelperText> : null}
|
{errors.name ? <FormHelperText>{errors.name.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -213,25 +223,21 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Typography variant="h6">Billing information</Typography>
|
<Typography variant="h6">{t('create.detail-information', NS_DEFAULT)}</Typography>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid md={6} xs={12}>
|
<Grid md={6} xs={12}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="billingAddress.country"
|
name="billingAddress.country"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl error={Boolean(errors.billingAddress?.country)} fullWidth>
|
<Box>
|
||||||
<InputLabel required>Country</InputLabel>
|
<Typography variant="subtitle1" color="text-secondary">
|
||||||
<Select {...field}>
|
{t('create.description', NS_DEFAULT)}
|
||||||
<Option value="">Choose a country</Option>
|
</Typography>
|
||||||
<Option value="us">United States</Option>
|
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
|
||||||
<Option value="de">Germany</Option>
|
<TextEditor content="" placeholder="Write something" />
|
||||||
<Option value="es">Spain</Option>
|
</Box>
|
||||||
</Select>
|
</Box>
|
||||||
{errors.billingAddress?.country ? (
|
|
||||||
<FormHelperText>{errors.billingAddress?.country?.message}</FormHelperText>
|
|
||||||
) : null}
|
|
||||||
</FormControl>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -240,134 +246,14 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
|
|||||||
control={control}
|
control={control}
|
||||||
name="billingAddress.state"
|
name="billingAddress.state"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl error={Boolean(errors.billingAddress?.state)} fullWidth>
|
<Box>
|
||||||
<InputLabel required>State</InputLabel>
|
<Typography variant="subtitle1" color="text.secondary">
|
||||||
<OutlinedInput {...field} />
|
{t('create.remarks', NS_DEFAULT)}
|
||||||
{errors.billingAddress?.state ? (
|
</Typography>
|
||||||
<FormHelperText>{errors.billingAddress?.state?.message}</FormHelperText>
|
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
|
||||||
) : null}
|
<TextEditor content="" placeholder="Write something" />
|
||||||
</FormControl>
|
</Box>
|
||||||
)}
|
</Box>
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid md={6} xs={12}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="billingAddress.city"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl error={Boolean(errors.billingAddress?.city)} fullWidth>
|
|
||||||
<InputLabel required>City</InputLabel>
|
|
||||||
<OutlinedInput {...field} />
|
|
||||||
{errors.billingAddress?.city ? (
|
|
||||||
<FormHelperText>{errors.billingAddress?.city?.message}</FormHelperText>
|
|
||||||
) : null}
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid md={6} xs={12}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="billingAddress.zipCode"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl error={Boolean(errors.billingAddress?.zipCode)} fullWidth>
|
|
||||||
<InputLabel required>Zip code</InputLabel>
|
|
||||||
<OutlinedInput {...field} />
|
|
||||||
{errors.billingAddress?.zipCode ? (
|
|
||||||
<FormHelperText>{errors.billingAddress?.zipCode?.message}</FormHelperText>
|
|
||||||
) : null}
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid md={6} xs={12}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="billingAddress.line1"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl error={Boolean(errors.billingAddress?.line1)} fullWidth>
|
|
||||||
<InputLabel required>Address</InputLabel>
|
|
||||||
<OutlinedInput {...field} />
|
|
||||||
{errors.billingAddress?.line1 ? (
|
|
||||||
<FormHelperText>{errors.billingAddress?.line1?.message}</FormHelperText>
|
|
||||||
) : null}
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid md={6} xs={12}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="taxId"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl error={Boolean(errors.taxId)} fullWidth>
|
|
||||||
<InputLabel>Tax ID</InputLabel>
|
|
||||||
<OutlinedInput {...field} placeholder="e.g EU372054390" />
|
|
||||||
{errors.taxId ? <FormHelperText>{errors.taxId.message}</FormHelperText> : null}
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Stack>
|
|
||||||
<Stack spacing={3}>
|
|
||||||
<Typography variant="h6">Shipping information</Typography>
|
|
||||||
<FormControlLabel control={<Checkbox defaultChecked />} label="Same as billing address" />
|
|
||||||
</Stack>
|
|
||||||
<Stack spacing={3}>
|
|
||||||
<Typography variant="h6">Additional information</Typography>
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
<Grid md={6} xs={12}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="timezone"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl error={Boolean(errors.timezone)} fullWidth>
|
|
||||||
<InputLabel required>Timezone</InputLabel>
|
|
||||||
<Select {...field}>
|
|
||||||
<Option value="">Select a timezone</Option>
|
|
||||||
<Option value="new_york">US - New York</Option>
|
|
||||||
<Option value="california">US - California</Option>
|
|
||||||
<Option value="london">UK - London</Option>
|
|
||||||
</Select>
|
|
||||||
{errors.timezone ? <FormHelperText>{errors.timezone.message}</FormHelperText> : null}
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid md={6} xs={12}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="language"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl error={Boolean(errors.language)} fullWidth>
|
|
||||||
<InputLabel required>Language</InputLabel>
|
|
||||||
<Select {...field}>
|
|
||||||
<Option value="">Select a language</Option>
|
|
||||||
<Option value="en">English</Option>
|
|
||||||
<Option value="es">Spanish</Option>
|
|
||||||
<Option value="de">German</Option>
|
|
||||||
</Select>
|
|
||||||
{errors.language ? <FormHelperText>{errors.language.message}</FormHelperText> : null}
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid md={6} xs={12}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="currency"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl error={Boolean(errors.currency)} fullWidth>
|
|
||||||
<InputLabel>Currency</InputLabel>
|
|
||||||
<Select {...field}>
|
|
||||||
<Option value="">Select a currency</Option>
|
|
||||||
<Option value="USD">USD</Option>
|
|
||||||
<Option value="EUR">EUR</Option>
|
|
||||||
<Option value="RON">RON</Option>
|
|
||||||
</Select>
|
|
||||||
{errors.currency ? <FormHelperText>{errors.currency.message}</FormHelperText> : null}
|
|
||||||
</FormControl>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -377,11 +263,12 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
||||||
<Button color="secondary" component={RouterLink} href={paths.dashboard.lesson_categories.list}>
|
<Button color="secondary" component={RouterLink} href={paths.dashboard.lesson_categories.list}>
|
||||||
Cancel
|
{t('create.cancelButton', NS_DEFAULT)}
|
||||||
</Button>
|
|
||||||
<Button type="submit" variant="contained">
|
|
||||||
Create customer
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<LoadingButton disabled={isCreating} loading={isCreating} type="submit" variant="contained">
|
||||||
|
{t('create.createButton', NS_DEFAULT)}
|
||||||
|
</LoadingButton>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
</form>
|
</form>
|
||||||
|
@@ -0,0 +1,334 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import RouterLink from 'next/link';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { COL_LESSON_CATEGORIES, NS_LESSON_CATEGORY } from '@/constants';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
import { Avatar, Divider, MenuItem } from '@mui/material';
|
||||||
|
// import Avatar from '@mui/material/Avatar';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Card from '@mui/material/Card';
|
||||||
|
import CardActions from '@mui/material/CardActions';
|
||||||
|
import CardContent from '@mui/material/CardContent';
|
||||||
|
// import Checkbox from '@mui/material/Checkbox';
|
||||||
|
|
||||||
|
import FormControl from '@mui/material/FormControl';
|
||||||
|
// import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
import FormHelperText from '@mui/material/FormHelperText';
|
||||||
|
import InputLabel from '@mui/material/InputLabel';
|
||||||
|
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||||
|
import Select from '@mui/material/Select';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Grid from '@mui/material/Unstable_Grid2';
|
||||||
|
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
|
||||||
|
import type { RecordModel } from 'pocketbase';
|
||||||
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { z as zod } from 'zod';
|
||||||
|
|
||||||
|
import { paths } from '@/paths';
|
||||||
|
import { logger } from '@/lib/default-logger';
|
||||||
|
import { pb } from '@/lib/pb';
|
||||||
|
import { Option } from '@/components/core/option';
|
||||||
|
import { TextEditor } from '@/components/core/text-editor/text-editor';
|
||||||
|
import { toast } from '@/components/core/toaster';
|
||||||
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
|
import ErrorDisplay from '../error';
|
||||||
|
import { defaultLessonCategory } from './_constants';
|
||||||
|
import { fileToBase64 } from './file-to-base64';
|
||||||
|
import { LessonCategory, EditFormProps } from './types';
|
||||||
|
|
||||||
|
const schema = zod.object({
|
||||||
|
cat_name: zod.string().min(1, 'Name is required').max(255),
|
||||||
|
type: zod.string().min(1, 'Name is required').max(255),
|
||||||
|
pos: zod.number().min(1, 'Phone is required').max(15),
|
||||||
|
visible: zod.string().max(255),
|
||||||
|
description: zod.string().optional(),
|
||||||
|
remarks: zod.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Values = zod.infer<typeof schema>;
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
cat_name: '',
|
||||||
|
type: '',
|
||||||
|
pos: 1,
|
||||||
|
visible: 'visible',
|
||||||
|
} satisfies Values;
|
||||||
|
|
||||||
|
export function LessonCategoryEditForm(): React.JSX.Element {
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation(['common', 'lesson_category']);
|
||||||
|
|
||||||
|
const NS_DEFAULT = { ns: 'lesson_category' };
|
||||||
|
const { cat_id: catId } = useParams<{ cat_id: string }>();
|
||||||
|
//
|
||||||
|
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
|
||||||
|
const [showLoading, setShowLoading] = React.useState<boolean>(false);
|
||||||
|
const [showError, setShowError] = React.useState<boolean>(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
setValue,
|
||||||
|
reset,
|
||||||
|
watch,
|
||||||
|
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
|
||||||
|
|
||||||
|
const onSubmit = React.useCallback(async (values: Values): Promise<void> => {
|
||||||
|
// setIsUpdating(true);
|
||||||
|
// const tempUpdate: LessonCategoryEditFormProps = {
|
||||||
|
// name: values.cat_name,
|
||||||
|
// type: values.type,
|
||||||
|
// pos: values.pos,
|
||||||
|
// visible: values.visible ? 'visible' : 'hidden',
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
// pb.collection(COL_LESSON_CATEGORIES)
|
||||||
|
// .update(catId, tempUpdate)
|
||||||
|
// .then((res) => {
|
||||||
|
// logger.debug(res);
|
||||||
|
// toast.success(t('update.success', NS_DEFAULT));
|
||||||
|
// setIsUpdating(false);
|
||||||
|
// router.push(paths.dashboard.lesson_categories.list);
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// logger.error(err);
|
||||||
|
// toast.error('Something went wrong!');
|
||||||
|
// })
|
||||||
|
// .finally(() => {
|
||||||
|
// //
|
||||||
|
// setIsUpdating(false);
|
||||||
|
// });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const avatarInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
// const avatar = watch('avatar');
|
||||||
|
|
||||||
|
const handleAvatarChange = React.useCallback(
|
||||||
|
async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const url = await fileToBase64(file);
|
||||||
|
// setValue('avatar', url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setValue]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [textDescription, setTextDescription] = React.useState<string>('loading');
|
||||||
|
const handleLoad = React.useCallback(
|
||||||
|
(id: string) => {
|
||||||
|
setShowLoading(true);
|
||||||
|
pb.collection(COL_LESSON_CATEGORIES)
|
||||||
|
.getOne(id)
|
||||||
|
.then((model: RecordModel) => {
|
||||||
|
const temp: LessonCategory = { ...defaultLessonCategory, ...model };
|
||||||
|
reset(temp);
|
||||||
|
setTextDescription(temp.description);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(err);
|
||||||
|
toast(t('list.error', NS_DEFAULT));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setShowLoading(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[catId]
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
handleLoad(catId);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [catId]);
|
||||||
|
|
||||||
|
if (showLoading) return <FormLoading />;
|
||||||
|
if (showError)
|
||||||
|
return (
|
||||||
|
<ErrorDisplay
|
||||||
|
message={t('error.unable-to-process-request', NS_DEFAULT)}
|
||||||
|
code="500"
|
||||||
|
details={t('error.detailed-error-information', NS_DEFAULT)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Stack divider={<Divider />} spacing={4}>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<Typography variant="h6">{t('edit.basic-info', NS_DEFAULT)}</Typography>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid xs={12}>
|
||||||
|
<Stack direction="row" spacing={3} sx={{ alignItems: 'center' }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
border: '1px dashed var(--mui-palette-divider)',
|
||||||
|
borderRadius: '5%',
|
||||||
|
display: 'inline-flex',
|
||||||
|
p: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/*
|
||||||
|
<Avatar
|
||||||
|
src={avatar}
|
||||||
|
sx={{
|
||||||
|
'--Avatar-size': '100px',
|
||||||
|
'--Icon-fontSize': 'var(--icon-fontSize-lg)',
|
||||||
|
alignItems: 'center',
|
||||||
|
bgcolor: 'var(--mui-palette-background-level1)',
|
||||||
|
color: 'var(--mui-palette-text-primary)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
// TODO: resume me
|
||||||
|
<CameraIcon fontSize="var(--Icon-fontSize)" />
|
||||||
|
</Avatar>
|
||||||
|
*/}
|
||||||
|
</Box>
|
||||||
|
<Stack spacing={1} sx={{ alignItems: 'flex-start' }}>
|
||||||
|
<Typography variant="subtitle1">{t('edit.avatar', NS_DEFAULT)}</Typography>
|
||||||
|
<Typography variant="caption">{t('edit.avatarRequirements', NS_DEFAULT)}</Typography>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
avatarInputRef.current?.click();
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
{t('edit.select', NS_DEFAULT)}
|
||||||
|
</Button>
|
||||||
|
<input hidden onChange={handleAvatarChange} ref={avatarInputRef} type="file" />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid md={6} xs={12}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="cat_name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl error={Boolean(errors.cat_name)} fullWidth>
|
||||||
|
<InputLabel required>{t('edit.name', NS_DEFAULT)}</InputLabel>
|
||||||
|
<OutlinedInput {...field} />
|
||||||
|
{errors.cat_name ? <FormHelperText>{errors.cat_name.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid md={6} xs={12}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="pos"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl error={Boolean(errors.pos)} fullWidth>
|
||||||
|
<InputLabel required>{t('edit.position', NS_DEFAULT)}</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => {
|
||||||
|
field.onChange(parseInt(e.target.value));
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{errors.pos ? <FormHelperText>{errors.pos.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid md={6} xs={12}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="visible"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl error={Boolean(errors.visible)} fullWidth>
|
||||||
|
<InputLabel>{t('edit.visible', NS_DEFAULT)}</InputLabel>
|
||||||
|
<Select {...field}>
|
||||||
|
<MenuItem value="visible">{t('edit.visible', NS_DEFAULT)}</MenuItem>
|
||||||
|
<MenuItem value="hidden">{t('edit.hidden', NS_DEFAULT)}</MenuItem>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{errors.visible ? <FormHelperText>{errors.visible.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<Typography variant="h6">{t('create.detail-information', NS_DEFAULT)}</Typography>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid md={6} xs={12}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => {
|
||||||
|
console.log({ field });
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle1" color="text-secondary">
|
||||||
|
{t('create.description', NS_DEFAULT)}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
|
||||||
|
{JSON.stringify({ field })}
|
||||||
|
<TextEditor
|
||||||
|
{...field}
|
||||||
|
content={textDescription}
|
||||||
|
onUpdate={({ editor }) => {
|
||||||
|
console.log(editor.getHTML());
|
||||||
|
field.onChange({ target: { value: editor.getHTML() } });
|
||||||
|
}}
|
||||||
|
placeholder={t('edit.write-something', NS_DEFAULT)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid md={6} xs={12}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="billingAddress.state"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle1" color="text.secondary">
|
||||||
|
{t('create.remarks', NS_DEFAULT)}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
|
||||||
|
<TextEditor content="" placeholder="Write something" />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</CardContent>
|
||||||
|
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
||||||
|
<Button color="secondary" component={RouterLink} href={paths.dashboard.lesson_types.list}>
|
||||||
|
{t('edit.cancelButton', NS_DEFAULT)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<LoadingButton disabled={isUpdating} loading={isUpdating} type="submit" variant="contained">
|
||||||
|
{t('edit.updateButton', NS_DEFAULT)}
|
||||||
|
</LoadingButton>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
@@ -0,0 +1,352 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import RouterLink from 'next/link';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { COL_LESSON_CATEGORIES, NS_LESSON_CATEGORY } from '@/constants';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
import { Avatar, Divider, MenuItem } from '@mui/material';
|
||||||
|
// import Avatar from '@mui/material/Avatar';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Card from '@mui/material/Card';
|
||||||
|
import CardActions from '@mui/material/CardActions';
|
||||||
|
import CardContent from '@mui/material/CardContent';
|
||||||
|
// import Checkbox from '@mui/material/Checkbox';
|
||||||
|
|
||||||
|
import FormControl from '@mui/material/FormControl';
|
||||||
|
// import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
import FormHelperText from '@mui/material/FormHelperText';
|
||||||
|
import InputLabel from '@mui/material/InputLabel';
|
||||||
|
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||||
|
import Select from '@mui/material/Select';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Grid from '@mui/material/Unstable_Grid2';
|
||||||
|
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
|
||||||
|
import type { RecordModel } from 'pocketbase';
|
||||||
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { z as zod } from 'zod';
|
||||||
|
|
||||||
|
import { paths } from '@/paths';
|
||||||
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
import { logger } from '@/lib/default-logger';
|
||||||
|
import { pb } from '@/lib/pb';
|
||||||
|
import { Option } from '@/components/core/option';
|
||||||
|
import { TextEditor } from '@/components/core/text-editor/text-editor';
|
||||||
|
import { toast } from '@/components/core/toaster';
|
||||||
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
|
import ErrorDisplay from '../error';
|
||||||
|
import { defaultLessonCategory } from './_constants';
|
||||||
|
import { fileToBase64 } from './file-to-base64';
|
||||||
|
import { EditFormProps, LessonCategory } from './types';
|
||||||
|
|
||||||
|
// TODO: review this
|
||||||
|
const schema = zod.object({
|
||||||
|
cat_name: zod.string().min(1, 'name-is-required').max(255),
|
||||||
|
//
|
||||||
|
pos: zod.number().min(1, 'Phone is required').max(99),
|
||||||
|
visible: zod.string().max(255),
|
||||||
|
//
|
||||||
|
description: zod.string().optional(),
|
||||||
|
remarks: zod.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Values = zod.infer<typeof schema>;
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
cat_name: '',
|
||||||
|
// cat_image: undefined,
|
||||||
|
pos: 0,
|
||||||
|
visible: 'hidden',
|
||||||
|
// lesson_id: 'default-lesson-id',
|
||||||
|
description: 'default-description',
|
||||||
|
remarks: 'default-remarks',
|
||||||
|
//
|
||||||
|
} satisfies Values;
|
||||||
|
|
||||||
|
export function LessonCategoryEditForm(): React.JSX.Element {
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation(['common', 'lesson_category']);
|
||||||
|
|
||||||
|
const NS_DEFAULT = { ns: 'lesson_category' };
|
||||||
|
|
||||||
|
const { cat_id: catId } = useParams<{ cat_id: string }>();
|
||||||
|
//
|
||||||
|
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
|
||||||
|
const [showLoading, setShowLoading] = React.useState<boolean>(false);
|
||||||
|
const [showError, setShowError] = React.useState<boolean>(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
setValue,
|
||||||
|
reset,
|
||||||
|
watch,
|
||||||
|
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
|
||||||
|
|
||||||
|
const onSubmit = React.useCallback(async (values: Values): Promise<void> => {
|
||||||
|
setIsUpdating(true);
|
||||||
|
const tempUpdate: EditFormProps = {
|
||||||
|
cat_name: values.cat_name,
|
||||||
|
pos: values.pos,
|
||||||
|
visible: values.visible,
|
||||||
|
description: values.description,
|
||||||
|
remarks: values.remarks,
|
||||||
|
};
|
||||||
|
|
||||||
|
pb.collection(COL_LESSON_CATEGORIES)
|
||||||
|
.update(catId, tempUpdate)
|
||||||
|
.then((res) => {
|
||||||
|
logger.debug(res);
|
||||||
|
toast.success(t('update.success', NS_DEFAULT));
|
||||||
|
router.push(paths.dashboard.lesson_categories.list);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(err);
|
||||||
|
toast.error('Something went wrong!');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
//
|
||||||
|
setIsUpdating(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const avatarInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
// const avatar = watch('avatar');
|
||||||
|
|
||||||
|
const handleAvatarChange = React.useCallback(
|
||||||
|
async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const url = await fileToBase64(file);
|
||||||
|
// setValue('avatar', url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setValue]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [textDescription, setTextDescription] = React.useState<string>('loading');
|
||||||
|
const [textRemarks, setTextRemarks] = React.useState<string>('loading');
|
||||||
|
const handleLoad = React.useCallback(
|
||||||
|
(id: string) => {
|
||||||
|
setShowLoading(true);
|
||||||
|
pb.collection(COL_LESSON_CATEGORIES)
|
||||||
|
.getOne(id)
|
||||||
|
.then((model: RecordModel) => {
|
||||||
|
const temp: LessonCategory = { ...defaultLessonCategory, ...model };
|
||||||
|
reset(temp);
|
||||||
|
setTextDescription(temp.description);
|
||||||
|
setTextRemarks(temp.remarks);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(err);
|
||||||
|
toast(t('list.error', NS_DEFAULT));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setShowLoading(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[catId]
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
handleLoad(catId);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [catId]);
|
||||||
|
|
||||||
|
if (showLoading) return <FormLoading />;
|
||||||
|
if (showError)
|
||||||
|
return (
|
||||||
|
<ErrorDisplay
|
||||||
|
message={t('error.unable-to-process-request', NS_DEFAULT)}
|
||||||
|
code="500"
|
||||||
|
details={t('error.detailed-error-information', NS_DEFAULT)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Stack divider={<Divider />} spacing={4}>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<Typography variant="h6">{t('edit.basic-info', NS_DEFAULT)}</Typography>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid xs={12}>
|
||||||
|
<Stack direction="row" spacing={3} sx={{ alignItems: 'center' }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
border: '1px dashed var(--mui-palette-divider)',
|
||||||
|
borderRadius: '5%',
|
||||||
|
display: 'inline-flex',
|
||||||
|
p: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/*
|
||||||
|
<Avatar
|
||||||
|
src={avatar}
|
||||||
|
sx={{
|
||||||
|
'--Avatar-size': '100px',
|
||||||
|
'--Icon-fontSize': 'var(--icon-fontSize-lg)',
|
||||||
|
alignItems: 'center',
|
||||||
|
bgcolor: 'var(--mui-palette-background-level1)',
|
||||||
|
color: 'var(--mui-palette-text-primary)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
// TODO: resume me
|
||||||
|
<CameraIcon fontSize="var(--Icon-fontSize)" />
|
||||||
|
</Avatar>
|
||||||
|
*/}
|
||||||
|
</Box>
|
||||||
|
<Stack spacing={1} sx={{ alignItems: 'flex-start' }}>
|
||||||
|
<Typography variant="subtitle1">{t('edit.avatar', NS_DEFAULT)}</Typography>
|
||||||
|
<Typography variant="caption">{t('edit.avatarRequirements', NS_DEFAULT)}</Typography>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
avatarInputRef.current?.click();
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
{t('edit.select', NS_DEFAULT)}
|
||||||
|
</Button>
|
||||||
|
<input hidden onChange={handleAvatarChange} ref={avatarInputRef} type="file" />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid md={6} xs={12}>
|
||||||
|
<Controller
|
||||||
|
disabled={isUpdating}
|
||||||
|
control={control}
|
||||||
|
name="cat_name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl disabled={isUpdating} error={Boolean(errors.cat_name)} fullWidth>
|
||||||
|
<InputLabel required>{t('edit.name', NS_DEFAULT)}</InputLabel>
|
||||||
|
<OutlinedInput {...field} />
|
||||||
|
{errors.cat_name ? <FormHelperText>{errors.cat_name.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid md={6} xs={12}>
|
||||||
|
<Controller
|
||||||
|
disabled={isUpdating}
|
||||||
|
control={control}
|
||||||
|
name="pos"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl error={Boolean(errors.pos)} fullWidth>
|
||||||
|
<InputLabel required>{t('edit.position', NS_DEFAULT)}</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => {
|
||||||
|
field.onChange(parseInt(e.target.value));
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{errors.pos ? <FormHelperText>{errors.pos.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid md={6} xs={12}>
|
||||||
|
<Controller
|
||||||
|
disabled={isUpdating}
|
||||||
|
control={control}
|
||||||
|
name="visible"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl error={Boolean(errors.visible)} fullWidth>
|
||||||
|
<InputLabel>{t('edit.visible', NS_DEFAULT)}</InputLabel>
|
||||||
|
<Select {...field}>
|
||||||
|
<MenuItem value="visible">{t('edit.visible', NS_DEFAULT)}</MenuItem>
|
||||||
|
<MenuItem value="hidden">{t('edit.hidden', NS_DEFAULT)}</MenuItem>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{errors.visible ? <FormHelperText>{errors.visible.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<Typography variant="h6">{t('create.detail-information', NS_DEFAULT)}</Typography>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid md={6} xs={12}>
|
||||||
|
<Controller
|
||||||
|
disabled={isUpdating}
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle1" color="text-secondary">
|
||||||
|
{t('create.description', NS_DEFAULT)}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
|
||||||
|
<TextEditor
|
||||||
|
{...field}
|
||||||
|
content={textDescription}
|
||||||
|
onUpdate={({ editor }) => {
|
||||||
|
field.onChange({ target: { value: editor.getHTML() } });
|
||||||
|
}}
|
||||||
|
placeholder={t('edit.write-something', NS_DEFAULT)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid md={6} xs={12}>
|
||||||
|
<Controller
|
||||||
|
disabled={isUpdating}
|
||||||
|
control={control}
|
||||||
|
name="remarks"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle1" color="text.secondary">
|
||||||
|
{t('create.remarks', NS_DEFAULT)}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
|
||||||
|
<TextEditor
|
||||||
|
content={textRemarks}
|
||||||
|
onUpdate={({ editor }) => {
|
||||||
|
field.onChange({ target: { value: editor.getText() } });
|
||||||
|
}}
|
||||||
|
hideToolbar={true}
|
||||||
|
placeholder={t('edit.write-something', NS_DEFAULT)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</CardContent>
|
||||||
|
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
||||||
|
<Button color="secondary" component={RouterLink} href={paths.dashboard.lesson_types.list}>
|
||||||
|
{t('edit.cancelButton', NS_DEFAULT)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<LoadingButton disabled={isUpdating} loading={isUpdating} type="submit" variant="contained">
|
||||||
|
{t('edit.updateButton', NS_DEFAULT)}
|
||||||
|
</LoadingButton>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
@@ -10,6 +10,7 @@ export interface LessonCategory {
|
|||||||
lesson_id: string;
|
lesson_id: string;
|
||||||
description: string;
|
description: string;
|
||||||
remarks: string;
|
remarks: string;
|
||||||
|
collectionId: string;
|
||||||
//
|
//
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
@@ -26,3 +27,11 @@ export interface CreateForm {
|
|||||||
pos: number;
|
pos: number;
|
||||||
visible: string;
|
visible: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EditFormProps {
|
||||||
|
cat_name: string;
|
||||||
|
pos: number;
|
||||||
|
visible: string;
|
||||||
|
description?: string;
|
||||||
|
remarks?: string;
|
||||||
|
}
|
@@ -60,6 +60,7 @@ const defaultValues = {
|
|||||||
export function LessonTypeCreateForm(): React.JSX.Element {
|
export function LessonTypeCreateForm(): React.JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [isCreating, setIsCreating] = React.useState<boolean>(false);
|
const [isCreating, setIsCreating] = React.useState<boolean>(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -79,7 +80,6 @@ export function LessonTypeCreateForm(): React.JSX.Element {
|
|||||||
pb.collection(COL_LESSON_TYPES)
|
pb.collection(COL_LESSON_TYPES)
|
||||||
.create(data)
|
.create(data)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
console.log(res);
|
|
||||||
logger.debug(res);
|
logger.debug(res);
|
||||||
toast.success(t('dashboard.lessonTypes.update.success'));
|
toast.success(t('dashboard.lessonTypes.update.success'));
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
@@ -90,23 +90,6 @@ export function LessonTypeCreateForm(): React.JSX.Element {
|
|||||||
toast.error('Something went wrong!');
|
toast.error('Something went wrong!');
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// const tempCreate: LessonTypeCreateForm = LessonTypeCreateFormDefault;
|
|
||||||
// tempCreate.name = values.name;
|
|
||||||
// tempCreate.type = values.type;
|
|
||||||
// tempCreate.pos = 1;
|
|
||||||
// tempCreate.visible = 'visible';
|
|
||||||
|
|
||||||
// createLessonType(tempCreate)
|
|
||||||
// .then((res) => {
|
|
||||||
// router.push(paths.dashboard.lesson_types.list);
|
|
||||||
// toast.success(t('dashboard.lessonTypes.create.success'));
|
|
||||||
// })
|
|
||||||
// .catch((err) => {
|
|
||||||
// logger.error(err);
|
|
||||||
// toast.error(t('dashboard.lessonTypes.create.error'));
|
|
||||||
// setIsCreating(false);
|
|
||||||
// });
|
|
||||||
},
|
},
|
||||||
[router]
|
[router]
|
||||||
);
|
);
|
||||||
|
@@ -6,7 +6,7 @@ import { useParams, useRouter } from 'next/navigation';
|
|||||||
import { COL_LESSON_TYPES } from '@/constants';
|
import { COL_LESSON_TYPES } from '@/constants';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import { MenuItem } from '@mui/material';
|
import { Avatar, MenuItem } from '@mui/material';
|
||||||
// import Avatar from '@mui/material/Avatar';
|
// import Avatar from '@mui/material/Avatar';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
@@ -35,6 +35,7 @@ import { logger } from '@/lib/default-logger';
|
|||||||
import { pb } from '@/lib/pb';
|
import { pb } from '@/lib/pb';
|
||||||
// import { Option } from '@/components/core/option';
|
// import { Option } from '@/components/core/option';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
// import { getLessonTypeById, updateLessonType } from './http-actions';
|
// import { getLessonTypeById, updateLessonType } from './http-actions';
|
||||||
// TODO: this may be wrong
|
// TODO: this may be wrong
|
||||||
@@ -74,8 +75,9 @@ export function LessonTypeEditForm(): React.JSX.Element {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { typeId } = useParams<{ typeId: string }>();
|
const { typeId } = useParams<{ typeId: string }>();
|
||||||
|
//
|
||||||
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
|
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
|
||||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
const [showLoading, setShowLoading] = React.useState<boolean>(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -83,7 +85,7 @@ export function LessonTypeEditForm(): React.JSX.Element {
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
setValue,
|
setValue,
|
||||||
reset,
|
reset,
|
||||||
// watch,
|
watch,
|
||||||
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
|
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
|
||||||
|
|
||||||
const onSubmit = React.useCallback(async (values: Values): Promise<void> => {
|
const onSubmit = React.useCallback(async (values: Values): Promise<void> => {
|
||||||
@@ -129,10 +131,10 @@ export function LessonTypeEditForm(): React.JSX.Element {
|
|||||||
(id: string) => {
|
(id: string) => {
|
||||||
pb.collection(COL_LESSON_TYPES)
|
pb.collection(COL_LESSON_TYPES)
|
||||||
.getOne(id)
|
.getOne(id)
|
||||||
.then((lessonType: RecordModel) => {
|
.then((model: RecordModel) => {
|
||||||
reset({ ...defaultLessonType, ...lessonType });
|
reset({ ...defaultLessonType, ...model });
|
||||||
|
|
||||||
setIsLoading(false);
|
setShowLoading(false);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
@@ -148,7 +150,7 @@ export function LessonTypeEditForm(): React.JSX.Element {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [typeId]);
|
}, [typeId]);
|
||||||
|
|
||||||
if (isLoading) return <>loading</>;
|
if (showLoading) return <FormLoading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
@@ -2,5 +2,6 @@ const COL_LESSON_TYPES = 'LessonsTypes';
|
|||||||
const COL_LESSON_CATEGORIES = 'LessonsCategories';
|
const COL_LESSON_CATEGORIES = 'LessonsCategories';
|
||||||
const NO_VALUE = 'NO_VALUE';
|
const NO_VALUE = 'NO_VALUE';
|
||||||
const NO_NUM = -Infinity;
|
const NO_NUM = -Infinity;
|
||||||
|
const NS_LESSON_CATEGORY = 'lesson_category';
|
||||||
|
|
||||||
export { COL_LESSON_TYPES, NO_VALUE, NO_NUM, COL_LESSON_CATEGORIES };
|
export { COL_LESSON_TYPES, NO_VALUE, NO_NUM, COL_LESSON_CATEGORIES, NS_LESSON_CATEGORY };
|
||||||
|
@@ -7,7 +7,6 @@ import { initReactI18next } from 'react-i18next';
|
|||||||
|
|
||||||
import { logger } from '@/lib/default-logger';
|
import { logger } from '@/lib/default-logger';
|
||||||
|
|
||||||
//
|
|
||||||
// .
|
// .
|
||||||
// .use(detector)
|
// .use(detector)
|
||||||
// use(detector);
|
// use(detector);
|
||||||
|
@@ -41,8 +41,8 @@
|
|||||||
"**/*.bug",
|
"**/*.bug",
|
||||||
"**/*.del",
|
"**/*.del",
|
||||||
"**/*.draft",
|
"**/*.draft",
|
||||||
"**/*copy.*",
|
"**/* copy *.tsx",
|
||||||
"**/*copy*.*"
|
"**/* copy.tsx"
|
||||||
//
|
//
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user