edit: response.message
This commit is contained in:
parent
d7f824f353
commit
7de5457170
21 changed files with 227 additions and 127 deletions
|
|
@ -17,14 +17,14 @@
|
||||||
<span>Dashboard</span>
|
<span>Dashboard</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
||||||
<NuxtLink
|
<!-- <NuxtLink
|
||||||
to="/admin/courses"
|
to="/admin/courses"
|
||||||
class="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-primary-100 transition mb-2"
|
class="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-primary-100 transition mb-2"
|
||||||
active-class="bg-primary-500 text-white hover:bg-primary-600"
|
active-class="bg-primary-500 text-white hover:bg-primary-600"
|
||||||
>
|
>
|
||||||
<q-icon name="school" size="24px" />
|
<q-icon name="school" size="24px" />
|
||||||
<span>จัดการหลักสูตร</span>
|
<span>จัดการหลักสูตร</span>
|
||||||
</NuxtLink>
|
</NuxtLink> -->
|
||||||
|
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/admin/courses/pending"
|
to="/admin/courses/pending"
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ const fetchCategories = async () => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'ไม่สามารถโหลดข้อมูลหมวดหมู่ได้',
|
message: (error as any).data?.message || 'ไม่สามารถโหลดข้อมูลหมวดหมู่ได้',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -270,32 +270,32 @@ const handleSave = async () => {
|
||||||
saving.value = true;
|
saving.value = true;
|
||||||
try {
|
try {
|
||||||
if (isEditing.value && editingId.value) {
|
if (isEditing.value && editingId.value) {
|
||||||
await adminService.updateCategory(editingId.value, {
|
const response = await adminService.updateCategory(editingId.value, {
|
||||||
id: editingId.value,
|
id: editingId.value,
|
||||||
...form.value
|
...form.value
|
||||||
});
|
});
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'แก้ไขหมวดหมู่สำเร็จ',
|
message: response.message || 'แก้ไขหมวดหมู่สำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await adminService.createCategory({
|
const response = await adminService.createCategory({
|
||||||
...form.value,
|
...form.value,
|
||||||
created_by: parseInt(authStore.user?.id || '0')
|
created_by: parseInt(authStore.user?.id || '0')
|
||||||
});
|
});
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'เพิ่มหมวดหมู่สำเร็จ',
|
message: response.message || 'เพิ่มหมวดหมู่สำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
showModal.value = false;
|
showModal.value = false;
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'เกิดข้อผิดพลาด กรุณาลองใหม่',
|
message: error.data?.message || 'เกิดข้อผิดพลาด กรุณาลองใหม่',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -311,17 +311,17 @@ const confirmDelete = (category: CategoryResponse) => {
|
||||||
persistent: true
|
persistent: true
|
||||||
}).onOk(async () => {
|
}).onOk(async () => {
|
||||||
try {
|
try {
|
||||||
await adminService.deleteCategory(category.id);
|
const response = await adminService.deleteCategory(category.id);
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'ลบหมวดหมู่สำเร็จ',
|
message: response.message || 'ลบหมวดหมู่สำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'เกิดข้อผิดพลาดในการลบ',
|
message: error.data?.message || 'เกิดข้อผิดพลาดในการลบ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,7 @@ const fetchCourse = async () => {
|
||||||
const courseId = Number(route.params.id);
|
const courseId = Number(route.params.id);
|
||||||
course.value = await adminService.getCourseForReview(courseId);
|
course.value = await adminService.getCourseForReview(courseId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = 'ไม่สามารถโหลดข้อมูลคอร์สได้';
|
error.value = (err as any).data?.message || 'ไม่สามารถโหลดข้อมูลคอร์สได้';
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
@ -395,17 +395,17 @@ const confirmApprove = () => {
|
||||||
}).onOk(async () => {
|
}).onOk(async () => {
|
||||||
actionLoading.value = true;
|
actionLoading.value = true;
|
||||||
try {
|
try {
|
||||||
await adminService.approveCourse(course.value!.id);
|
const response = await adminService.approveCourse(course.value!.id);
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'อนุมัติคอร์สสำเร็จ',
|
message: response.message || 'อนุมัติคอร์สสำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
router.push('/admin/courses/pending');
|
router.push('/admin/courses/pending');
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'เกิดข้อผิดพลาดในการอนุมัติคอร์ส',
|
message: err.data?.message || 'เกิดข้อผิดพลาดในการอนุมัติคอร์ส',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -419,18 +419,18 @@ const confirmReject = async () => {
|
||||||
|
|
||||||
actionLoading.value = true;
|
actionLoading.value = true;
|
||||||
try {
|
try {
|
||||||
await adminService.rejectCourse(course.value.id, rejectReason.value);
|
const response = await adminService.rejectCourse(course.value.id, rejectReason.value);
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'ปฏิเสธคอร์สสำเร็จ',
|
message: response.message || 'ปฏิเสธคอร์สสำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
showRejectModal.value = false;
|
showRejectModal.value = false;
|
||||||
router.push('/admin/courses/pending');
|
router.push('/admin/courses/pending');
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'เกิดข้อผิดพลาดในการปฏิเสธคอร์ส',
|
message: err.data?.message || 'เกิดข้อผิดพลาดในการปฏิเสธคอร์ส',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ const fetchPendingCourses = async () => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'ไม่สามารถโหลดข้อมูลคอร์สได้',
|
message: (error as any).data?.message || 'ไม่สามารถโหลดข้อมูลคอร์สได้',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-gray-900">
|
<h1 class="text-2xl font-bold text-gray-900">
|
||||||
สวัสดี, {{ authStore.user?.fullName || 'ผู้ดูแลระบบ' }}
|
สวัสดี, {{ authStore.user?.firstName }} {{ authStore.user?.lastName }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-gray-600 mt-2">ยินดีต้อนรับกลับสู่ระบบ</p>
|
<p class="text-gray-600 mt-2">ยินดีต้อนรับกลับสู่ระบบ</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<div class="text-sm font-semibold text-gray-900">{{ authStore.user?.fullName || 'ผู้ดูแลระบบ' }}</div>
|
<div class="text-sm font-semibold text-gray-900">{{ authStore.user?.firstName }} {{ authStore.user?.lastName }}</div>
|
||||||
<div class="text-xs text-gray-500">{{ authStore.user?.role || 'ADMIN' }}</div>
|
<div class="text-xs text-gray-500">{{ authStore.user?.role || 'ADMIN' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<!-- User Info Header -->
|
<!-- User Info Header -->
|
||||||
<q-item class="bg-primary-50">
|
<q-item class="bg-primary-50">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label class="text-weight-bold">{{ authStore.user?.fullName }}</q-item-label>
|
<q-item-label class="text-weight-bold">{{ authStore.user?.firstName }} {{ authStore.user?.lastName }}</q-item-label>
|
||||||
<q-item-label caption>{{ authStore.user?.email }}</q-item-label>
|
<q-item-label caption>{{ authStore.user?.email }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
@ -85,8 +85,8 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- Recent Courses -->
|
<!-- Recent Courses -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="text-xl font-semibold mb-4">หลักสูตรล่าสุด</h2>
|
<h2 class="text-xl font-semibold mb-4">Dashboard</h2>
|
||||||
<div class="space-y-4">
|
<!-- <div class="space-y-4">
|
||||||
<div class="flex items-center gap-4 p-4 bg-gray-50 rounded-lg">
|
<div class="flex items-center gap-4 p-4 bg-gray-50 rounded-lg">
|
||||||
<div class="w-16 h-16 bg-primary-100 rounded-lg flex items-center justify-center">
|
<div class="w-16 h-16 bg-primary-100 rounded-lg flex items-center justify-center">
|
||||||
<q-icon name="code" size="32px" class="text-primary-600" />
|
<q-icon name="code" size="32px" class="text-primary-600" />
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
</div>
|
</div>
|
||||||
<q-btn flat color="primary" label="ดูรายละเอียด" />
|
<q-btn flat color="primary" label="ดูรายละเอียด" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,11 @@
|
||||||
icon="download"
|
icon="download"
|
||||||
@click="exportExcel"
|
@click="exportExcel"
|
||||||
/>
|
/>
|
||||||
<q-btn
|
<!-- <q-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
label="+ เพิ่มผู้ใช้ใหม่"
|
label="+ เพิ่มผู้ใช้ใหม่"
|
||||||
@click="showAddModal = true"
|
@click="showAddModal = true"
|
||||||
/>
|
/> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -279,7 +279,7 @@ const fetchUsers = async () => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'ไม่สามารถโหลดข้อมูลผู้ใช้ได้',
|
message: (error as any).data?.message || 'ไม่สามารถโหลดข้อมูลผู้ใช้ได้',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -326,7 +326,7 @@ const changeRole = (user: AdminUserResponse) => {
|
||||||
message: `เลือก Role ใหม่สำหรับ ${user.profile.first_name}`,
|
message: `เลือก Role ใหม่สำหรับ ${user.profile.first_name}`,
|
||||||
options: {
|
options: {
|
||||||
type: 'radio',
|
type: 'radio',
|
||||||
model: roleIds[user.role.code],
|
model: roleIds[user.role.code] as any,
|
||||||
items: [
|
items: [
|
||||||
{ label: 'Instructor', value: 1 },
|
{ label: 'Instructor', value: 1 },
|
||||||
{ label: 'Student', value: 2 },
|
{ label: 'Student', value: 2 },
|
||||||
|
|
@ -337,17 +337,17 @@ const changeRole = (user: AdminUserResponse) => {
|
||||||
persistent: true
|
persistent: true
|
||||||
}).onOk(async (roleId: number) => {
|
}).onOk(async (roleId: number) => {
|
||||||
try {
|
try {
|
||||||
await adminService.updateUserRole(user.id, roleId);
|
const response = await adminService.updateUserRole(user.id, roleId);
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'เปลี่ยน Role สำเร็จ',
|
message: response.message || 'เปลี่ยน Role สำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
fetchUsers();
|
fetchUsers();
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'เกิดข้อผิดพลาดในการเปลี่ยน Role',
|
message: error.data?.message || 'เกิดข้อผิดพลาดในการเปลี่ยน Role',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -362,17 +362,17 @@ const confirmDelete = (user: AdminUserResponse) => {
|
||||||
persistent: true
|
persistent: true
|
||||||
}).onOk(async () => {
|
}).onOk(async () => {
|
||||||
try {
|
try {
|
||||||
await adminService.deleteUser(user.id);
|
const response = await adminService.deleteUser(user.id);
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'ลบผู้ใช้สำเร็จ',
|
message: response.message || 'ลบผู้ใช้สำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
fetchUsers();
|
fetchUsers();
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'เกิดข้อผิดพลาดในการลบผู้ใช้',
|
message: error.data?.message || 'เกิดข้อผิดพลาดในการลบผู้ใช้',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -430,7 +430,7 @@ const saveLesson = async () => {
|
||||||
navigateTo(`/instructor/courses/${courseId}/structure`);
|
navigateTo(`/instructor/courses/${courseId}/structure`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to save lesson:', error);
|
console.error('Failed to save lesson:', error);
|
||||||
$q.notify({ type: 'negative', message: 'ไม่สามารถบันทึกได้', position: 'top' });
|
$q.notify({ type: 'negative', message: (error as any).data?.error?.message || (error as any).data?.message || 'ไม่สามารถบันทึกได้', position: 'top' });
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false;
|
saving.value = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,20 @@
|
||||||
autogrow
|
autogrow
|
||||||
rows="3"
|
rows="3"
|
||||||
/>
|
/>
|
||||||
|
<q-card-section class="flex justify-end gap-2">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
label="ยกเลิก"
|
||||||
|
@click="navigateTo(`/instructor/courses/${courseId}/structure`)"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
label="บันทึก"
|
||||||
|
icon="save"
|
||||||
|
:loading="saving"
|
||||||
|
@click="saveLesson"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
|
|
@ -182,20 +196,7 @@
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<q-card-section class="flex justify-end gap-2">
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
label="ยกเลิก"
|
|
||||||
@click="navigateTo(`/instructor/courses/${courseId}/structure`)"
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
color="primary"
|
|
||||||
label="บันทึก"
|
|
||||||
icon="save"
|
|
||||||
:loading="saving"
|
|
||||||
@click="saveLesson"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,8 @@
|
||||||
|
|
||||||
<!-- Status Badges -->
|
<!-- Status Badges -->
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<q-badge v-if="course.is_free" color="purple">เผยแพร่</q-badge>
|
<q-badge v-if="course.is_free" color="purple">ฟรี</q-badge>
|
||||||
|
<q-badge v-else color="purple">เสียเงิน</q-badge>
|
||||||
<q-badge :color="getStatusColor(course.status)">
|
<q-badge :color="getStatusColor(course.status)">
|
||||||
{{ getStatusLabel(course.status) }}
|
{{ getStatusLabel(course.status) }}
|
||||||
</q-badge>
|
</q-badge>
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,7 @@
|
||||||
:rules="[val => !!val || 'กรุณากรอกชื่อบทเรียน']"
|
:rules="[val => !!val || 'กรุณากรอกชื่อบทเรียน']"
|
||||||
lazy-rules="ondemand"
|
lazy-rules="ondemand"
|
||||||
hide-bottom-space
|
hide-bottom-space
|
||||||
|
class="mb-4"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
v-model="lessonForm.title.en"
|
v-model="lessonForm.title.en"
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,7 @@ const handleSubmit = async () => {
|
||||||
|
|
||||||
const response = await instructorService.createCourse(form.value);
|
const response = await instructorService.createCourse(form.value);
|
||||||
|
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: response.message,
|
message: response.message,
|
||||||
|
|
@ -206,9 +207,9 @@ const handleSubmit = async () => {
|
||||||
// Redirect to course edit page
|
// Redirect to course edit page
|
||||||
// Note: Assuming response.data contains the created course with ID
|
// Note: Assuming response.data contains the created course with ID
|
||||||
if (response.data && response.data.id) {
|
if (response.data && response.data.id) {
|
||||||
router.push(`/instructor/courses/${response.data.id}/edit`);
|
await navigateTo(`/instructor/courses/${response.data.id}`);
|
||||||
} else {
|
} else {
|
||||||
navigateTo('/instructor/courses');
|
await navigateTo('/instructor/courses');
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
|
|
|
||||||
|
|
@ -100,9 +100,9 @@
|
||||||
<div class="flex justify-between items-start mb-2">
|
<div class="flex justify-between items-start mb-2">
|
||||||
<h3 class="font-semibold text-gray-900 line-clamp-2">{{ course.title.th }}</h3>
|
<h3 class="font-semibold text-gray-900 line-clamp-2">{{ course.title.th }}</h3>
|
||||||
<q-badge :color="getStatusColor(course.status)" class="ml-2">
|
<q-badge :color="getStatusColor(course.status)" class="ml-2">
|
||||||
{{ getStatusLabel(course.status) }}
|
{{ getStatusLabel(course.status) }}
|
||||||
</q-badge>
|
</q-badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-sm text-gray-500 line-clamp-2 mb-3">
|
<p class="text-sm text-gray-500 line-clamp-2 mb-3">
|
||||||
{{ course.description.th }}
|
{{ course.description.th }}
|
||||||
|
|
@ -277,18 +277,18 @@ const confirmDelete = (course: CourseResponse) => {
|
||||||
persistent: true
|
persistent: true
|
||||||
}).onOk(async () => {
|
}).onOk(async () => {
|
||||||
try {
|
try {
|
||||||
await instructorService.deleteCourse(course.id);
|
const response = await instructorService.deleteCourse(course.id);
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'ลบหลักสูตรสำเร็จ',
|
message: response.message || 'ลบหลักสูตรสำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
// Refresh list
|
// Refresh list
|
||||||
fetchCourses();
|
fetchCourses();
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'ไม่สามารถลบหลักสูตรได้',
|
message: error.data?.message || 'ไม่สามารถลบหลักสูตรได้',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@
|
||||||
:alt="course.title"
|
:alt="course.title"
|
||||||
class="w-full h-full object-cover"
|
class="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
<span v-else class="text-3xl">{{ course.icon }}</span>
|
<q-icon v-else :name="course.icon" class="text-3xl" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="font-semibold text-gray-900">{{ course.title }}</div>
|
<div class="font-semibold text-gray-900">{{ course.title }}</div>
|
||||||
|
|
|
||||||
|
|
@ -350,12 +350,19 @@ const getRoleLabel = (role: string) => {
|
||||||
return labels[role] || role;
|
return labels[role] || role;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (date: string) => {
|
const formatDate = (date: string, includeTime = true) => {
|
||||||
return new Date(date).toLocaleDateString('th-TH', {
|
const options: Intl.DateTimeFormatOptions = {
|
||||||
year: 'numeric',
|
day: 'numeric',
|
||||||
month: 'long',
|
month: 'short',
|
||||||
day: 'numeric'
|
year: '2-digit'
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (includeTime) {
|
||||||
|
options.hour = '2-digit';
|
||||||
|
options.minute = '2-digit';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(date).toLocaleDateString('th-TH', options);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Avatar upload
|
// Avatar upload
|
||||||
|
|
@ -398,21 +405,21 @@ const handleAvatarUpload = async (event: Event) => {
|
||||||
|
|
||||||
uploadingAvatar.value = true;
|
uploadingAvatar.value = true;
|
||||||
try {
|
try {
|
||||||
await userService.uploadAvatar(file);
|
const response = await userService.uploadAvatar(file);
|
||||||
|
|
||||||
// Re-fetch profile to get presigned URL from backend
|
// Re-fetch profile to get presigned URL from backend
|
||||||
await fetchProfile();
|
await fetchProfile();
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'อัพโหลดรูปโปรไฟล์สำเร็จ',
|
message: response.message || 'อัพโหลดรูปโปรไฟล์สำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('Failed to upload avatar:', error);
|
console.error('Failed to upload avatar:', error);
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'เกิดข้อผิดพลาดในการอัพโหลดรูป',
|
message: error.data?.message || 'เกิดข้อผิดพลาดในการอัพโหลดรูป',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -426,7 +433,7 @@ const handleUpdateProfile = async () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call real API to update profile
|
// Call real API to update profile
|
||||||
await userService.updateProfile({
|
const response = await userService.updateProfile({
|
||||||
first_name: editForm.value.firstName,
|
first_name: editForm.value.firstName,
|
||||||
last_name: editForm.value.lastName,
|
last_name: editForm.value.lastName,
|
||||||
phone: editForm.value.phone || null
|
phone: editForm.value.phone || null
|
||||||
|
|
@ -437,7 +444,7 @@ const handleUpdateProfile = async () => {
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'อัพเดทโปรไฟล์สำเร็จ',
|
message: response.message || 'อัพเดทโปรไฟล์สำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -445,7 +452,7 @@ const handleUpdateProfile = async () => {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'เกิดข้อผิดพลาด กรุณาลองใหม่อีกครั้ง',
|
message: error.data?.message || 'เกิดข้อผิดพลาด กรุณาลองใหม่อีกครั้ง',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -458,14 +465,14 @@ const handleChangePassword = async () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call real API to change password
|
// Call real API to change password
|
||||||
await userService.changePassword(
|
const response = await userService.changePassword(
|
||||||
passwordForm.value.currentPassword,
|
passwordForm.value.currentPassword,
|
||||||
passwordForm.value.newPassword
|
passwordForm.value.newPassword
|
||||||
);
|
);
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'เปลี่ยนรหัสผ่านสำเร็จ',
|
message: response.message || 'เปลี่ยนรหัสผ่านสำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -478,9 +485,9 @@ const handleChangePassword = async () => {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: error.response?.status === 401
|
message: error.data?.message || (error.response?.status === 401
|
||||||
? 'รหัสผ่านปัจจุบันไม่ถูกต้อง'
|
? 'รหัสผ่านปัจจุบันไม่ถูกต้อง'
|
||||||
: 'เกิดข้อผิดพลาดในการเปลี่ยนรหัสผ่าน',
|
: 'เกิดข้อผิดพลาดในการเปลี่ยนรหัสผ่าน'),
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -157,11 +157,11 @@ const forgotLoading = ref(false);
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
await authStore.login(email.value, password.value);
|
const response = await authStore.login(email.value, password.value);
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'เข้าสู่ระบบสำเร็จ',
|
message: response.message || 'เข้าสู่ระบบสำเร็จ',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
// Redirect based on role
|
// Redirect based on role
|
||||||
|
|
@ -184,11 +184,11 @@ const handleLogin = async () => {
|
||||||
const handleForgotPassword = async () => {
|
const handleForgotPassword = async () => {
|
||||||
forgotLoading.value = true;
|
forgotLoading.value = true;
|
||||||
try {
|
try {
|
||||||
await authService.forgotPassword(forgotEmail.value);
|
const response = await authService.forgotPassword(forgotEmail.value);
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: 'ส่งลิงก์รีเซ็ตรหัสผ่านไปยังอีเมลของคุณแล้ว',
|
message: response.message || 'ส่งลิงก์รีเซ็ตรหัสผ่านไปยังอีเมลของคุณแล้ว',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -197,7 +197,7 @@ const handleForgotPassword = async () => {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: error.message || 'เกิดข้อผิดพลาด กรุณาลองใหม่',
|
message: error.data?.message || 'เกิดข้อผิดพลาด กรุณาลองใหม่',
|
||||||
position: 'top'
|
position: 'top'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@ export interface AdminUserResponse {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UsersListResponse {
|
export interface UsersListResponse {
|
||||||
code: number;
|
code: number;
|
||||||
message: string;
|
message: string;
|
||||||
|
|
@ -368,17 +374,21 @@ export const adminService = {
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateUserRole(userId: number, roleId: number): Promise<void> {
|
async updateUserRole(userId: number, roleId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
if (useMockData) {
|
if (useMockData) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
return;
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'User role updated successfully (Mock)',
|
||||||
|
data: undefined
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
await $fetch(`/api/admin/usermanagement/role/${userId}`, {
|
const response = await $fetch<ApiResponse<void>>(`/api/admin/usermanagement/role/${userId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -389,25 +399,33 @@ export const adminService = {
|
||||||
role_id: roleId
|
role_id: roleId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteUser(userId: number): Promise<void> {
|
async deleteUser(userId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
if (useMockData) {
|
if (useMockData) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
return;
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'User deleted successfully (Mock)',
|
||||||
|
data: undefined
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
await $fetch(`/api/admin/usermanagement/users/${userId}`, {
|
const response = await $fetch<ApiResponse<void>>(`/api/admin/usermanagement/users/${userId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`
|
Authorization: `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
// ============ Pending Courses ============
|
// ============ Pending Courses ============
|
||||||
|
|
@ -451,17 +469,21 @@ export const adminService = {
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async approveCourse(courseId: number, comment?: string): Promise<void> {
|
async approveCourse(courseId: number, comment?: string): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
if (useMockData) {
|
if (useMockData) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
return;
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Course approved successfully (Mock)',
|
||||||
|
data: undefined
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
await $fetch(`/api/admin/courses/${courseId}/approve`, {
|
const response = await $fetch<ApiResponse<void>>(`/api/admin/courses/${courseId}/approve`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -469,19 +491,25 @@ export const adminService = {
|
||||||
},
|
},
|
||||||
body: { comment: comment || '' }
|
body: { comment: comment || '' }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
async rejectCourse(courseId: number, comment: string): Promise<void> {
|
async rejectCourse(courseId: number, comment: string): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
if (useMockData) {
|
if (useMockData) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
return;
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Course rejected successfully (Mock)',
|
||||||
|
data: undefined
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
await $fetch(`/api/admin/courses/${courseId}/reject`, {
|
const response = await $fetch<ApiResponse<void>>(`/api/admin/courses/${courseId}/reject`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -489,6 +517,8 @@ export const adminService = {
|
||||||
},
|
},
|
||||||
body: { comment }
|
body: { comment }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
// ============ Categories ============
|
// ============ Categories ============
|
||||||
|
|
@ -512,17 +542,21 @@ export const adminService = {
|
||||||
return response.data.categories;
|
return response.data.categories;
|
||||||
},
|
},
|
||||||
|
|
||||||
async createCategory(data: CreateCategoryRequest): Promise<CategoryResponse> {
|
async createCategory(data: CreateCategoryRequest): Promise<ApiResponse<CategoryResponse>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
if (useMockData) {
|
if (useMockData) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
return { ...MOCK_CATEGORIES[0], id: Date.now() };
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Category created successfully (Mock)',
|
||||||
|
data: { ...MOCK_CATEGORIES[0], id: Date.now() }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
const response = await $fetch<CategoryResponse>('/api/admin/categories', {
|
const response = await $fetch<ApiResponse<CategoryResponse>>('/api/admin/categories', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -534,17 +568,21 @@ export const adminService = {
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateCategory(id: number, data: UpdateCategoryRequest): Promise<CategoryResponse> {
|
async updateCategory(id: number, data: UpdateCategoryRequest): Promise<ApiResponse<CategoryResponse>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
if (useMockData) {
|
if (useMockData) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
return { ...MOCK_CATEGORIES[0], id };
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Category updated successfully (Mock)',
|
||||||
|
data: { ...MOCK_CATEGORIES[0], id }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
const response = await $fetch<CategoryResponse>(`/api/admin/categories/${id}`, {
|
const response = await $fetch<ApiResponse<CategoryResponse>>(`/api/admin/categories/${id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -556,23 +594,29 @@ export const adminService = {
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteCategory(id: number): Promise<void> {
|
async deleteCategory(id: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
if (useMockData) {
|
if (useMockData) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
return;
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Category deleted successfully (Mock)',
|
||||||
|
data: undefined
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
await $fetch(`/api/admin/categories/${id}`, {
|
const response = await $fetch<ApiResponse<void>>(`/api/admin/categories/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`
|
Authorization: `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,13 @@ export interface LoginResponse {
|
||||||
role: string;
|
role: string;
|
||||||
avatarUrl?: string | null;
|
avatarUrl?: string | null;
|
||||||
};
|
};
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock data for development
|
// Mock data for development
|
||||||
|
|
@ -135,14 +142,23 @@ export const authService = {
|
||||||
lastName: response.user.profile.last_name,
|
lastName: response.user.profile.last_name,
|
||||||
role: response.user.role.code,
|
role: response.user.role.code,
|
||||||
avatarUrl: response.user.profile.avatar_url
|
avatarUrl: response.user.profile.avatar_url
|
||||||
}
|
},
|
||||||
|
message: 'เข้าสู่ระบบสำเร็จ' // Note: Backend usually returns message too, but we can default it or use backend's
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Re-throw custom errors (like STUDENT role block)
|
// Re-throw custom errors (like STUDENT role block)
|
||||||
if (error.message && !error.response) {
|
if (error.message && !error.response) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle API errors
|
// Handle API errors
|
||||||
|
const apiError = error.data?.error || error.data;
|
||||||
|
const errorMessage = apiError?.message || error.message;
|
||||||
|
|
||||||
|
if (errorMessage) {
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
throw new Error('อีเมลหรือรหัสผ่านไม่ถูกต้อง');
|
throw new Error('อีเมลหรือรหัสผ่านไม่ถูกต้อง');
|
||||||
}
|
}
|
||||||
|
|
@ -161,22 +177,27 @@ export const authService = {
|
||||||
userCookie.value = null;
|
userCookie.value = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
async forgotPassword(email: string): Promise<void> {
|
async forgotPassword(email: string): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
if (useMockData) {
|
if (useMockData) {
|
||||||
// Mock: simulate sending email
|
// Mock: simulate sending email
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
return;
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'ส่งลิงก์รีเซ็ตรหัสผ่านไปยังอีเมลของคุณแล้ว (Mock)',
|
||||||
|
data: undefined
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Real API
|
// Real API
|
||||||
await $fetch('/api/auth/reset-request', {
|
const response = await $fetch<ApiResponse<void>>('/api/auth/reset-request', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
body: { email }
|
body: { email }
|
||||||
});
|
});
|
||||||
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
async resetPassword(token: string, password: string): Promise<void> {
|
async resetPassword(token: string, password: string): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -214,14 +214,18 @@ export const instructorService = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean data - remove empty thumbnail_url
|
// Clean data - remove empty thumbnail_url
|
||||||
|
|
||||||
const cleanedData = { ...data };
|
const cleanedData = { ...data };
|
||||||
if (!cleanedData.thumbnail_url) {
|
if (!cleanedData.thumbnail_url) {
|
||||||
delete cleanedData.thumbnail_url;
|
delete cleanedData.thumbnail_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('data', JSON.stringify(cleanedData));
|
||||||
|
|
||||||
return await authRequest<ApiResponse<CourseResponse>>('/api/instructors/courses', {
|
return await authRequest<ApiResponse<CourseResponse>>('/api/instructors/courses', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: cleanedData
|
body: formData
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@ export interface UserProfileResponse {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
// Request body for PUT /api/user/me
|
// Request body for PUT /api/user/me
|
||||||
export interface UpdateProfileRequest {
|
export interface UpdateProfileRequest {
|
||||||
prefix?: {
|
prefix?: {
|
||||||
|
|
@ -132,7 +138,7 @@ export const userService = {
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateProfile(data: UpdateProfileRequest): Promise<UserProfileResponse> {
|
async updateProfile(data: UpdateProfileRequest): Promise<ApiResponse<UserProfileResponse>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
|
|
@ -140,11 +146,15 @@ export const userService = {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
// In mock mode, just return the current profile with updates
|
// In mock mode, just return the current profile with updates
|
||||||
const profile = await this.mockGetProfile();
|
const profile = await this.mockGetProfile();
|
||||||
return { ...profile, profile: { ...profile.profile, ...data } };
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Update profile successfully',
|
||||||
|
data: { ...profile, profile: { ...profile.profile, ...data } }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
const response = await $fetch<UserProfileResponse>('/api/user/me', {
|
const response = await $fetch<ApiResponse<UserProfileResponse>>('/api/user/me', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -156,18 +166,22 @@ export const userService = {
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
async changePassword(oldPassword: string, newPassword: string): Promise<void> {
|
async changePassword(oldPassword: string, newPassword: string): Promise<ApiResponse<null>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
if (useMockData) {
|
if (useMockData) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
// In mock mode, just simulate success
|
// In mock mode, just simulate success
|
||||||
return;
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Change password successfully',
|
||||||
|
data: null
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
await $fetch('/api/user/change-password', {
|
const response = await $fetch<ApiResponse<null>>('/api/user/change-password', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -178,23 +192,29 @@ export const userService = {
|
||||||
newPassword
|
newPassword
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadAvatar(file: File): Promise<{ avatar_url: string; id: number }> {
|
async uploadAvatar(file: File): Promise<ApiResponse<{ avatar_url: string; id: number }>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
if (useMockData) {
|
if (useMockData) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
// Return mock URL
|
// Return mock URL
|
||||||
return { avatar_url: URL.createObjectURL(file), id: 1 };
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Upload avatar successfully',
|
||||||
|
data: { avatar_url: URL.createObjectURL(file), id: 1 }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
const response = await $fetch<{ code: number; message: string; data: { avatar_url: string; id: number } }>('/api/user/upload-avatar', {
|
const response = await $fetch<ApiResponse<{ avatar_url: string; id: number }>>('/api/user/upload-avatar', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -203,6 +223,6 @@ export const userService = {
|
||||||
body: formData
|
body: formData
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.data;
|
return response;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export const useAuthStore = defineStore('auth', {
|
||||||
refreshTokenCookie.value = response.refreshToken;
|
refreshTokenCookie.value = response.refreshToken;
|
||||||
userCookie.value = JSON.stringify(this.user);
|
userCookie.value = JSON.stringify(this.user);
|
||||||
|
|
||||||
return { token: this.token, user: this.user };
|
return response;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export const useInstructorStore = defineStore('instructor', {
|
||||||
title: course.title.th,
|
title: course.title.th,
|
||||||
students: 0, // TODO: Get from API
|
students: 0, // TODO: Get from API
|
||||||
lessons: 0, // TODO: Get from course detail API
|
lessons: 0, // TODO: Get from course detail API
|
||||||
icon: ['📘', '📗', '📙', '📕', '📒'][index % 5],
|
icon: 'book',
|
||||||
thumbnail: course.thumbnail_url || null
|
thumbnail: course.thumbnail_url || null
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue