feat: del mock , crud add instructor , VerifyEmail
This commit is contained in:
parent
b2365a4c6a
commit
278bc17fa0
8 changed files with 345 additions and 1566 deletions
|
|
@ -193,7 +193,7 @@
|
|||
<q-tab-panel name="instructors" class="p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900">ผู้สอนในรายวิชา</h2>
|
||||
<q-btn color="primary" icon="person_add" label="เพิ่มผู้สอน" @click="showAddInstructorDialog = true" />
|
||||
<q-btn v-if="isPrimaryInstructor" color="primary" icon="person_add" label="เพิ่มผู้สอน" @click="showAddInstructorDialog = true" />
|
||||
</div>
|
||||
|
||||
<div v-if="loadingInstructors" class="flex justify-center py-10">
|
||||
|
|
@ -223,7 +223,7 @@
|
|||
<q-item-label caption>{{ instructor.user.email }}</q-item-label>
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section side>
|
||||
<q-item-section side v-if="isPrimaryInstructor">
|
||||
<q-btn flat dense round icon="more_vert">
|
||||
<q-menu>
|
||||
<q-item v-if="!instructor.is_primary" clickable v-close-popup @click="setPrimaryInstructor(instructor.user_id)">
|
||||
|
|
@ -469,14 +469,15 @@
|
|||
<q-card-section>
|
||||
<q-select
|
||||
v-model="selectedUser"
|
||||
:options="filteredUsers"
|
||||
:options="searchResults"
|
||||
option-value="id"
|
||||
option-label="email"
|
||||
label="ค้นหาผู้ใช้ (Email หรือ Username)"
|
||||
label="ค้นหาผู้สอน (Email หรือ Username)"
|
||||
hint="พิมพ์อย่างน้อย 2 ตัวอักษรเพื่อค้นหา"
|
||||
use-input
|
||||
filled
|
||||
@filter="filterUsers"
|
||||
:loading="loadingUsers"
|
||||
:loading="loadingSearch"
|
||||
>
|
||||
<template v-slot:option="scope">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
|
|
@ -521,9 +522,9 @@ import {
|
|||
type ChapterResponse,
|
||||
type AnnouncementResponse,
|
||||
type CreateAnnouncementRequest,
|
||||
type CourseInstructorResponse
|
||||
type CourseInstructorResponse,
|
||||
type SearchInstructorResult
|
||||
} from '~/services/instructor.service';
|
||||
import { adminService, type AdminUserResponse } from '~/services/admin.service';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'instructor',
|
||||
|
|
@ -532,6 +533,7 @@ definePageMeta({
|
|||
|
||||
const $q = useQuasar();
|
||||
const route = useRoute();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// Data
|
||||
const course = ref<CourseDetailResponse | null>(null);
|
||||
|
|
@ -559,11 +561,11 @@ const announcementForm = ref<CreateAnnouncementRequest>({
|
|||
const instructors = ref<CourseInstructorResponse[]>([]);
|
||||
const loadingInstructors = ref(false);
|
||||
const showAddInstructorDialog = ref(false);
|
||||
const selectedUser = ref<AdminUserResponse | null>(null);
|
||||
const users = ref<AdminUserResponse[]>([]);
|
||||
const filteredUsers = ref<AdminUserResponse[]>([]);
|
||||
const loadingUsers = ref(false);
|
||||
const selectedUser = ref<SearchInstructorResult | null>(null);
|
||||
const searchResults = ref<SearchInstructorResult[]>([]);
|
||||
const loadingSearch = ref(false);
|
||||
const addingInstructor = ref(false);
|
||||
const searchQuery = ref('');
|
||||
|
||||
// Attachment handling
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null);
|
||||
|
|
@ -582,6 +584,14 @@ const sortedChapters = computed(() => {
|
|||
return course.value.chapters.slice().sort((a, b) => a.sort_order - b.sort_order);
|
||||
});
|
||||
|
||||
// Check if current user is the primary instructor
|
||||
const isPrimaryInstructor = computed(() => {
|
||||
if (!authStore.user?.id) return false;
|
||||
const currentUserId = parseInt(authStore.user.id);
|
||||
const myInstructorRecord = instructors.value.find(i => i.user_id === currentUserId);
|
||||
return myInstructorRecord?.is_primary === true;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const fetchCourse = async () => {
|
||||
loading.value = true;
|
||||
|
|
@ -757,31 +767,40 @@ const fetchInstructors = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
const filterUsers = async (val: string, update: (callback: () => void) => void) => {
|
||||
if (users.value.length === 0) {
|
||||
loadingUsers.value = true;
|
||||
try {
|
||||
users.value = await adminService.getUsers();
|
||||
} catch (error) {
|
||||
console.error('Failed to load users', error);
|
||||
} finally {
|
||||
loadingUsers.value = false;
|
||||
}
|
||||
let searchTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
const filterUsers = (val: string, update: (callback: () => void) => void, abort: () => void) => {
|
||||
// Abort if query is too short
|
||||
if (val.length < 2) {
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
|
||||
update(() => {
|
||||
const needle = val.toLowerCase();
|
||||
const existingInstructorIds = instructors.value.map(i => i.user_id);
|
||||
|
||||
filteredUsers.value = users.value.filter(v => {
|
||||
// Exclude existing instructors
|
||||
if (existingInstructorIds.includes(v.id)) return false;
|
||||
// Clear previous timeout
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
// Debounce the search
|
||||
searchTimeout = setTimeout(async () => {
|
||||
loadingSearch.value = true;
|
||||
try {
|
||||
const results = await instructorService.searchInstructors(val);
|
||||
const existingInstructorIds = instructors.value.map(i => i.user_id);
|
||||
|
||||
// Filter by username or email
|
||||
return v.username.toLowerCase().indexOf(needle) > -1 ||
|
||||
v.email.toLowerCase().indexOf(needle) > -1;
|
||||
});
|
||||
});
|
||||
// Filter out existing instructors
|
||||
update(() => {
|
||||
searchResults.value = results.filter(r => !existingInstructorIds.includes(r.id));
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to search instructors', error);
|
||||
update(() => {
|
||||
searchResults.value = [];
|
||||
});
|
||||
} finally {
|
||||
loadingSearch.value = false;
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const addInstructor = async () => {
|
||||
|
|
@ -790,7 +809,7 @@ const addInstructor = async () => {
|
|||
addingInstructor.value = true;
|
||||
try {
|
||||
const courseId = parseInt(route.params.id as string);
|
||||
const response = await instructorService.addInstructor(courseId, selectedUser.value.id);
|
||||
const response = await instructorService.addInstructor(courseId, selectedUser.value.email);
|
||||
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue