Untitled

 avatar
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