Untitled
unknown
plain_text
9 months ago
10 kB
15
Indexable
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/vue/24/solid'
import Dialog from '@/components/ui/Dialog/Dialog.vue'
import apiClient from '@/services/api' // Import API client
// Define the Document type structure based on backend response mapping
interface DeadlineDocument {
id: number;
documentNo: string;
subject: string;
dueDate: string; // This will hold either document_deadline or arta_deadline
finalActionOffice: string; // Office/group currently responsible
document_deadline: string | null; // Added for comparison/display
arta_deadline: string | null; // Added for comparison/display
}
const props = defineProps<{
isOpen: boolean
}>()
const emit = defineEmits<{
(e: 'close'): void
}>()
const currentDate = ref(new Date())
// --- STATE FOR FETCHED DATA ---
const fetchedDocuments = ref<DeadlineDocument[]>([])
const isLoadingDocuments = ref(false)
// --- END NEW STATE ---
const currentMonth = computed(() => currentDate.value.toLocaleString('en-US', { month: 'long' }))
const currentYear = computed(() => currentDate.value.getFullYear())
const daysInMonth = computed(() => {
const year = currentDate.value.getFullYear()
const month = currentDate.value.getMonth()
return new Date(year, month + 1, 0).getDate()
})
const firstDayOfMonth = computed(() => {
const year = currentDate.value.getFullYear()
const month = currentDate.value.getMonth()
return new Date(year, month, 1).getDay()
})
const calendarDays = computed(() => {
const days = []
for (let i = 0; i < firstDayOfMonth.value; i++) {
days.push(null)
}
for (let i = 1; i <= daysInMonth.value; i++) {
days.push(i)
}
return days
})
// --- API FETCHING LOGIC ---
const fetchDeadlinedDocuments = async () => {
isLoadingDocuments.value = true
fetchedDocuments.value = [] // Clear previous results
try {
const year = currentYear.value;
const month = currentDate.value.getMonth() + 1; // Month is 1-based for PHP
// API endpoint from your route definition: /dms/deadlined-documents
const response = await apiClient.get('/dms/deadlined-documents', {
params: { year, month }
})
// Map the received data to the local interface
fetchedDocuments.value = response.data.map(doc => ({
id: doc.id,
documentNo: doc.document_no,
subject: doc.subject,
// The backend sends the preferred deadline in 'dueDate'
dueDate: doc.dueDate,
finalActionOffice: doc.finalActionOffice,
// Capture the raw deadlines for display context
document_deadline: doc.document_deadline,
arta_deadline: doc.arta_deadline
}))
} catch (error) {
console.error('Error fetching deadlined documents:', error)
} finally {
isLoadingDocuments.value = false
}
}
// --- CALENDAR NAVIGATION ---
const goToPreviousMonth = () => {
currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() - 1, 1)
selectDay(1)
}
const goToNextMonth = () => {
currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() + 1, 1)
selectDay(1)
}
const close = () => {
emit('close')
}
// --- CALENDAR DISPLAY COMPUTED PROPERTIES ---
const documentsForCurrentMonth = computed<DeadlineDocument[]>(() => {
return fetchedDocuments.value.filter(doc => doc.dueDate)
})
const highlightedDates = computed<number[]>(() => {
const dates = new Set<number>()
documentsForCurrentMonth.value.forEach((doc) => {
// Use a utility function to reliably parse dates from potentially mixed formats
const dueDate = new Date(doc.dueDate)
if (!isNaN(dueDate.getTime())) {
dates.add(dueDate.getDate())
}
})
return Array.from(dates)
})
const isHighlighted = (day: number | null) => {
return day !== null && highlightedDates.value.includes(day)
}
const selectedDayReminders = ref<DeadlineDocument[]>([])
const selectedDate = ref<Date | null>(null)
const selectDay = (day: number | null) => {
if (day === null) {
selectedDayReminders.value = []
selectedDate.value = null
return
}
const newSelectedDate = new Date(
currentDate.value.getFullYear(),
currentDate.value.getMonth(),
day,
)
selectedDate.value = newSelectedDate
selectedDayReminders.value = documentsForCurrentMonth.value.filter((doc) => {
const dueDate = new Date(doc.dueDate)
if (isNaN(dueDate.getTime())) return false;
return (
dueDate.getDate() === newSelectedDate.getDate() &&
dueDate.getMonth() === newSelectedDate.getMonth() &&
dueDate.getFullYear() === newSelectedDate.getFullYear()
)
})
}
const getDeadlineType = (doc: DeadlineDocument): string => {
// Determine the type of deadline being used (assuming document_deadline is always standard date)
// The backend logic sets dueDate based on which one is present first.
if (doc.document_deadline && doc.dueDate === doc.document_deadline) {
return 'Document Deadline'
}
if (doc.arta_deadline && doc.dueDate === doc.arta_deadline) {
return 'ARTA Deadline'
}
return 'Deadline'
}
const initializeCalendar = () => {
const today = new Date()
currentDate.value = today
fetchDeadlinedDocuments().then(() => {
selectDay(today.getDate())
})
}
// Use watch to call the central function and refetch data when the modal opens
watch(
() => props.isOpen,
(newVal) => {
if (newVal) {
initializeCalendar()
}
},
{ immediate: true },
)
// Watch for manual month navigation to trigger data fetch
watch(currentDate, () => {
if (props.isOpen) {
fetchDeadlinedDocuments().then(() => {
selectDay(currentDate.value.getDate())
})
}
}, { deep: true });
const reminderCurrentDate = computed(() => {
if (selectedDate.value) {
const options: Intl.DateTimeFormatOptions = { weekday: 'long', month: 'long', day: 'numeric' }
return selectedDate.value.toLocaleDateString('en-US', options)
}
const today = new Date()
const options: Intl.DateTimeFormatOptions = { weekday: 'long', month: 'long', day: 'numeric' }
return today.toLocaleDateString('en-US', options)
})
</script>
<template>
<Dialog :is-open="props.isOpen" @close="close" title="Document Calendar"
title-description="View and manage deadlines and schedules for documents." type="modal"
:close-on-overlay-click="true">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4 h-[330px] overflow-hidden">
<div class="flex flex-col h-full">
<div class="flex items-center justify-between mb-4">
<button @click="goToPreviousMonth" class="p-1 rounded-full hover:bg-gray-100">
<ChevronLeftIcon class="h-5 w-5 text-gray-700" />
</button>
<span class="text-lg font-semibold text-gray-800">{{ currentMonth }} {{ currentYear }}</span>
<button @click="goToNextMonth" class="p-1 rounded-full hover:bg-gray-100">
<ChevronRightIcon class="h-5 w-5 text-gray-700" />
</button>
</div>
<div class="grid grid-cols-7 text-center text-sm font-medium text-gray-500 mb-2">
<span>Sun</span>
<span>Mon</span>
<span>Tue</span>
<span>Wed</span>
<span>Thu</span>
<span>Fri</span>
<span>Sat</span>
</div>
<div v-if="isLoadingDocuments" class="text-center py-10 col-span-full text-gray-500">
Loading deadlines...
</div>
<div v-else class="grid grid-cols-7 gap-1">
<div v-for="(day, index) in calendarDays" :key="index" :class="[
'p-2 rounded-md flex items-center justify-center text-sm cursor-pointer transition-colors duration-150',
day === null ? 'bg-gray-50 text-gray-300' : 'text-gray-800 bg-white hover:bg-blue-50',
isHighlighted(day) ? 'bg-red-100 text-red-700 font-semibold border border-red-500' : '',
selectedDate &&
day === selectedDate.getDate() &&
currentDate.getMonth() === selectedDate.getMonth() &&
currentDate.getFullYear() === selectedDate.getFullYear()
? 'border-2 border-blue-500 bg-blue-50'
: '',
]" @click="selectDay(day)">
{{ day }}
</div>
</div>
</div>
<div class="flex flex-col border-t md:border-t-0 md:border-l pt-4 md:pt-0 md:pl-6 h-full overflow-y-auto">
<div class="flex justify-between items-center mb-2">
<p class="text-sm font-medium text-gray-700">Reminders: {{ reminderCurrentDate }}</p>
</div>
<div v-if="isLoadingDocuments" class="text-center py-10 text-gray-500 text-sm">
Loading reminders...
</div>
<div v-else-if="selectedDayReminders.length > 0">
<div v-for="reminder in selectedDayReminders" :key="reminder.id"
class="bg-red-100 border-l-4 border-red-500 text-red-700 p-3 mb-2 rounded-md">
<p class="font-semibold text-sm">
{{
new Date(reminder.dueDate).toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
})
}}
</p>
<p class="text-xs font-semibold">
{{ getDeadlineType(reminder) }} ({{ reminder.documentNo }})
</p>
<p class="text-xs mt-1">
Subject: {{ reminder.subject }}
</p>
<p class="text-xs">Final Action Office: <span class="font-medium">{{ reminder.finalActionOffice || 'N/A'
}}</span></p>
</div>
</div>
<div v-else class="text-gray-500 text-sm">No Due Documents for the selected day.</div>
</div>
</div>
<template #footer>
<button type="button"
class="inline-flex justify-center text-end rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
@click="close">
Close
</button>
</template>
</Dialog>
</template>Editor is loading...
Leave a Comment