feat: implement core e-learning pages, course composable, and i18n localization.
This commit is contained in:
parent
f736eb7f38
commit
e94410d0e7
9 changed files with 235 additions and 138 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue