LogsCalendarView.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. <template>
  2. <Section :title="t('logs.calendar-title')" :padding="false">
  3. <template #toolbar>
  4. <router-link to="/logs" class="button neutral">
  5. <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
  6. <path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8l8 8l1.41-1.41L7.83 13H20z"/>
  7. </svg>
  8. {{ t('logs.back-to-list') }}
  9. </router-link>
  10. </template>
  11. <div class="padding">
  12. <Calendar
  13. :events="calendarEvents"
  14. :loading="loading"
  15. :error="error"
  16. :current-month="currentMonthIndex"
  17. :current-year="currentYear"
  18. @event-click="handleEventClick"
  19. @date-click="handleDayClick"
  20. @month-change="handleMonthChange"
  21. />
  22. </div>
  23. </Section>
  24. </template>
  25. <script setup>
  26. import { ref, computed, onMounted } from 'vue'
  27. import { useRouter } from 'vue-router'
  28. import { useI18n } from 'vue-i18n'
  29. import Calendar from 'picocrank/vue/components/Calendar.vue'
  30. import Section from 'picocrank/vue/components/Section.vue'
  31. const router = useRouter()
  32. const { t } = useI18n()
  33. const logs = ref([])
  34. const loading = ref(false)
  35. const error = ref(null)
  36. const currentMonthIndex = ref(new Date().getMonth())
  37. const currentYear = ref(new Date().getFullYear())
  38. // Convert logs to calendar events format
  39. const calendarEvents = computed(() => {
  40. return logs.value
  41. .filter(log => {
  42. // Only include logs with valid start dates
  43. if (!log.datetimeStarted) return false
  44. const startDate = new Date(log.datetimeStarted)
  45. return !isNaN(startDate.getTime())
  46. })
  47. .map(log => {
  48. const startDate = new Date(log.datetimeStarted)
  49. let endDate = log.datetimeFinished ? new Date(log.datetimeFinished) : null
  50. // Validate end date
  51. if (endDate && isNaN(endDate.getTime())) {
  52. endDate = null
  53. }
  54. return {
  55. id: log.executionTrackingId,
  56. title: log.actionTitle || 'Untitled Action',
  57. date: startDate,
  58. startDate: startDate,
  59. endDate: endDate,
  60. actionIcon: log.actionIcon,
  61. user: log.user,
  62. tags: log.tags,
  63. logEntry: log
  64. }
  65. })
  66. })
  67. async function fetchLogs() {
  68. loading.value = true
  69. error.value = null
  70. try {
  71. // Currently fetches only the default page (startOffset: 0)
  72. // Multi-page fetching: loop through pages until no more logs or limit reached
  73. const allLogs = []
  74. let startOffset = BigInt(0)
  75. const maxLogs = 10000 // Reasonable limit to prevent excessive API calls
  76. const pageSize = 100 // Typical page size, will be updated from API response
  77. while (allLogs.length < maxLogs) {
  78. const args = {
  79. "startOffset": startOffset,
  80. }
  81. const response = await window.client.getLogs(args)
  82. const pageLogs = response.logs || []
  83. // If no logs returned, we've reached the end
  84. if (pageLogs.length === 0) {
  85. break
  86. }
  87. // Append logs from this page
  88. allLogs.push(...pageLogs)
  89. // Update offset for next page
  90. const currentPageSize = Number(response.pageSize) || pageLogs.length
  91. startOffset += BigInt(currentPageSize)
  92. // If we got fewer logs than the page size, we've reached the end
  93. if (pageLogs.length < currentPageSize) {
  94. break
  95. }
  96. }
  97. logs.value = allLogs
  98. } catch (err) {
  99. console.error('Failed to fetch logs:', err)
  100. error.value = 'Failed to load logs'
  101. window.showBigError('fetch-logs-calendar', 'getting logs for calendar', err, false)
  102. } finally {
  103. loading.value = false
  104. }
  105. }
  106. function handleEventClick(event) {
  107. // Navigate to the execution view when clicking on a calendar event
  108. if (event.id) {
  109. router.push(`/logs/${event.id}`)
  110. }
  111. }
  112. function handleDayClick(date) {
  113. // Navigate to logs list filtered by the selected date
  114. // Format date as YYYY-MM-DD using local date components to avoid timezone issues
  115. const year = date.getFullYear()
  116. const month = String(date.getMonth() + 1).padStart(2, '0')
  117. const day = String(date.getDate()).padStart(2, '0')
  118. const dateString = `${year}-${month}-${day}`
  119. router.push({ path: '/logs', query: { date: dateString } })
  120. }
  121. function handleMonthChange(month, year) {
  122. currentMonthIndex.value = month
  123. currentYear.value = year
  124. // Optionally fetch logs for the new month if needed
  125. // For now, we'll keep all logs loaded
  126. }
  127. onMounted(() => {
  128. fetchLogs()
  129. })
  130. </script>
  131. <style scoped>
  132. .padding {
  133. padding: 1rem;
  134. }
  135. @media (prefers-color-scheme: dark) {
  136. :deep(div.calendar-header-nav) {
  137. background-color: var(--bg, #111);
  138. color: var(--text-color, #fff);
  139. border-color: var(--border-color, #333);
  140. }
  141. :deep(div.calendar-header-nav h2.calendar-title) {
  142. color: #fff !important;
  143. }
  144. }
  145. </style>