feat: implement core e-learning pages, course composable, and i18n localization.

This commit is contained in:
supalerk-ar66 2026-02-09 11:40:41 +07:00
parent f736eb7f38
commit e94410d0e7
9 changed files with 235 additions and 138 deletions

View file

@ -28,11 +28,16 @@ const isLoading = ref(false);
const isLoadingDetail = ref(false);
const isEnrolling = ref(false);
// Pagination State
const currentPage = ref(1);
const totalPages = ref(1);
const itemsPerPage = 12;
const { t } = useI18n();
const { currentUser } = useAuth();
const { fetchCategories } = useCategory();
const { fetchCourses, fetchCourseById, enrollCourse } = useCourse();
const { fetchCourses, fetchCourseById, enrollCourse, getLocalizedText } = useCourse();
// 2. Computed Properties
@ -41,9 +46,13 @@ const sortOptions = computed(() => [t('discovery.sortRecent')]);
const filteredCourses = computed(() => {
let result = courses.value;
if (selectedCategoryIds.value.length > 0) {
// If more than 1 category is selected, we still do client-side filtering
// because the API currently only supports one category_id at a time.
if (selectedCategoryIds.value.length > 1) {
result = result.filter(c => selectedCategoryIds.value.includes(c.category_id));
}
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase();
result = result.filter(c => {
@ -56,11 +65,7 @@ const filteredCourses = computed(() => {
});
// 3. Helper Functions
const getLocalizedText = (text: string | { th: string; en: string } | undefined) => {
if (!text) return '';
if (typeof text === 'string') return text;
return text.th || text.en || '';
};
// 4. API Actions
const loadCategories = async () => {
@ -68,11 +73,23 @@ const loadCategories = async () => {
if (res.success) categories.value = res.data || [];
};
const loadCourses = async () => {
const loadCourses = async (page = 1) => {
isLoading.value = true;
const res = await fetchCourses();
// Use server-side filtering if exactly one category is selected
const categoryId = selectedCategoryIds.value.length === 1 ? selectedCategoryIds.value[0] : undefined;
const res = await fetchCourses({
category_id: categoryId,
page: page,
limit: itemsPerPage,
forceRefresh: true
});
if (res.success) {
courses.value = res.data || [];
totalPages.value = res.totalPages || 1;
currentPage.value = res.page || 1;
}
isLoading.value = false;
};
@ -98,6 +115,12 @@ const handleEnroll = async (id: number) => {
isEnrolling.value = false;
};
// Watch for category selection changes to reload courses
watch(selectedCategoryIds, () => {
currentPage.value = 1;
loadCourses(1);
}, { deep: true });
onMounted(() => {
loadCategories();
loadCourses();
@ -166,6 +189,22 @@ onMounted(() => {
/>
</div>
<!-- Pagination Controls -->
<div v-if="totalPages > 1" class="flex justify-center mt-12 pb-10">
<q-pagination
v-model="currentPage"
:max="totalPages"
:max-pages="6"
boundary-numbers
direction-links
color="primary"
flat
active-design="unelevated"
active-color="primary"
@update:model-value="loadCourses"
/>
</div>
<!-- Empty State -->
<div
v-else