98 lines
5.3 KiB
Vue
98 lines
5.3 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* @file AnnouncementModal.vue
|
|
* @description Modal component to display course announcements
|
|
*/
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean;
|
|
announcements: any[];
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:modelValue', value: boolean): void;
|
|
}>();
|
|
|
|
const { locale, t } = useI18n()
|
|
|
|
// Helper for localization
|
|
const getLocalizedText = (text: any) => {
|
|
if (!text) return ''
|
|
if (typeof text === 'string') return text
|
|
|
|
const currentLocale = locale.value as 'th' | 'en'
|
|
return text[currentLocale] || text.th || text.en || ''
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<q-dialog :model-value="modelValue" @update:model-value="(val) => emit('update:modelValue', val)" backdrop-filter="blur(4px)">
|
|
<q-card class="min-w-[320px] md:min-w-[600px] rounded-3xl overflow-hidden !bg-white dark:!bg-slate-900 shadow-2xl">
|
|
<q-card-section class="!bg-white dark:!bg-slate-900 border-b border-gray-100 dark:border-white/5 p-5 flex items-center justify-between sticky top-0 z-10">
|
|
<div>
|
|
<div class="text-xl font-bold flex items-center gap-2 text-slate-900 dark:text-white">
|
|
<div class="w-10 h-10 rounded-full bg-blue-50 dark:bg-blue-900/30 text-primary flex items-center justify-center">
|
|
<q-icon name="campaign" size="22px" />
|
|
</div>
|
|
{{ $t('classroom.announcements') }}
|
|
</div>
|
|
<div class="text-xs text-slate-500 dark:text-slate-400 ml-12 mt-1">{{ announcements.length }} {{ $t('common.items') }}</div>
|
|
</div>
|
|
<q-btn icon="close" flat round dense v-close-popup class="text-slate-400 hover:text-slate-700 dark:hover:text-white bg-slate-50 dark:bg-slate-800" />
|
|
</q-card-section>
|
|
|
|
<q-card-section class="p-0 scroll max-h-[70vh] bg-slate-50 dark:bg-[#0B0F1A]">
|
|
<div v-if="announcements.length > 0" class="p-4 space-y-4">
|
|
<div
|
|
v-for="(ann, index) in announcements"
|
|
:key="index"
|
|
class="p-5 rounded-2xl bg-white dark:bg-slate-800 shadow-sm border border-gray-200 dark:border-white/5 transition-all hover:shadow-md relative overflow-hidden group"
|
|
:class="{'ring-2 ring-orange-200 dark:ring-orange-900/40 !bg-orange-50/50 dark:!bg-orange-900/20': ann.is_pinned}"
|
|
>
|
|
<!-- Pinned Banner -->
|
|
<div v-if="ann.is_pinned" class="absolute top-0 right-0 p-3">
|
|
<q-icon name="push_pin" color="orange" size="18px" class="transform rotate-45" />
|
|
</div>
|
|
|
|
<div class="flex items-start gap-4 mb-3">
|
|
<q-avatar
|
|
:color="ann.is_pinned ? 'orange' : 'primary'"
|
|
text-color="white"
|
|
size="42px"
|
|
font-size="20px"
|
|
class="shadow-sm"
|
|
>
|
|
<span class="font-bold">{{ (getLocalizedText(ann.title) || 'A').charAt(0) }}</span>
|
|
</q-avatar>
|
|
<div class="flex-1">
|
|
<div class="font-bold text-lg text-slate-900 dark:text-white leading-tight pr-8 capitalize font-display">
|
|
{{ getLocalizedText(ann.title) || $t('sidebar.announcements') }}
|
|
</div>
|
|
<div class="text-xs text-slate-500 dark:text-slate-400 flex items-center gap-2 mt-1.5">
|
|
<span class="flex items-center gap-1 bg-slate-100 dark:bg-slate-700 px-2 py-0.5 rounded-md">
|
|
<q-icon name="today" size="12px" />
|
|
{{ new Date(ann.created_at || Date.now()).toLocaleDateString(locale === 'th' ? 'th-TH' : 'en-US', { day: 'numeric', month: 'short', year: 'numeric' }) }}
|
|
</span>
|
|
<span class="flex items-center gap-1 bg-slate-100 dark:bg-slate-700 px-2 py-0.5 rounded-md">
|
|
<q-icon name="access_time" size="12px" />
|
|
{{ new Date(ann.created_at || Date.now()).toLocaleTimeString(locale === 'th' ? 'th-TH' : 'en-US', { hour: '2-digit', minute: '2-digit' }) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pl-[58px]">
|
|
<div class="text-slate-600 dark:text-slate-300 leading-relaxed text-sm whitespace-pre-wrap">
|
|
{{ getLocalizedText(ann.content) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="p-10 flex flex-col items-center justify-center text-slate-400">
|
|
<q-icon name="campaign" size="40px" class="mb-2 opacity-50" />
|
|
<p>{{ $t('classroom.noAnnouncements') }}</p>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</q-dialog>
|
|
</template>
|