Skip to content

Commit

Permalink
Merge pull request #86 from LCOGT/feature/calendar
Browse files Browse the repository at this point in the history
Feature/calendar
  • Loading branch information
capetillo authored Nov 14, 2024
2 parents 35e5987 + 837c58f commit 856414b
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 45 deletions.
32 changes: 18 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/pro-regular-svg-icons": "^6.6.0",
"@fortawesome/vue-fontawesome": "^3.0.6",
"@popperjs/core": "^2.11.8",
"@vuepic/vue-datepicker": "^9.0.3",
"bulma": "^1.0.0",
"core-js": "^3.8.3",
Expand All @@ -28,7 +29,7 @@
"pinia-plugin-persistedstate": "^3.2.1",
"sass": "^1.75.0",
"sass-loader": "^14.2.1",
"v-calendar": "^3.1.2",
"v-calendar": "^3.0.0",
"vue": "^3.2.13",
"vue-demi": "^0.14.10",
"vue-router": "^4.3.2",
Expand Down
31 changes: 30 additions & 1 deletion src/components/RealTimeInterface/TimePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const availableTimes = ref({})
const localTimes = ref([])
const timeInterval = 15
const today = ref(new Date())
const oneYearFromNow = ref(new Date())
oneYearFromNow.value.setFullYear(oneYearFromNow.value.getFullYear() + 1)
const emits = defineEmits(['timeSelected'])
Expand Down Expand Up @@ -205,6 +208,22 @@ const isDateAllowed = (date) => {
return date >= firstDate && date <= lastDate
}
const disabledDates = computed(() => {
if (Object.keys(availableTimes.value).length === 0) {
return []
}
// Calculate the number of days between today and one year from now
const daysCount = Math.floor((oneYearFromNow.value - today.value) / (1000 * 60 * 60 * 24))
// Generates an array of dates from today to one year from now to filter out the disabled dates
return Array.from({ length: daysCount + 1 }, (_, index) => {
const date = new Date(today.value)
date.setDate(today.value.getDate() + index)
return date
}).filter(date => !isDateAllowed(date))
})
// Handles both resetting the session and updating localTimes.value when the date changes
watch(date, (newDate, oldDate) => {
if (newDate !== oldDate) {
Expand All @@ -226,6 +245,7 @@ watch(startTime, (newTime, oldTime) => {
onMounted(() => {
getAvailableTimes()
})
</script>

<template>
Expand All @@ -238,7 +258,16 @@ onMounted(() => {
<div class="column is-one-third">
<p>Select a date and time:</p>
<div>
<v-date-picker v-model="date" class="blue-bg" :allowed-dates="isDateAllowed" @click="refreshTimes" />
<VDatePicker
v-model="date"
mode="date"
:min-date="today"
:disabled-dates="disabledDates"
:max-date="oneYearFromNow"
is-required
@update:model-value="refreshTimes"
expanded
/>
</div>
</div>
<div class="column">
Expand Down
4 changes: 2 additions & 2 deletions src/components/Scheduling/AdvancedScheduling.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ const handleExposuresUpdate = (exposures) => {
}
const handleDateRangeUpdate = (dateRange) => {
startDate.value = dateRange[0].toISOString().split('T')[0]
endDate.value = dateRange[1].toISOString().split('T')[0]
startDate.value = dateRange.start.toISOString().split('T')[0]
endDate.value = dateRange.end.toISOString().split('T')[0]
emitSelections()
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Scheduling/BeginnerScheduling.vue
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ const fetchTargets = async (startDate, endDate) => {
const handleDateRangeUpdate = (newDateRange) => {
dateRange.value = newDateRange
startDate.value = newDateRange[0].toISOString().split('.')[0]
endDate.value = newDateRange[1].toISOString().split('.')[0]
startDate.value = newDateRange.start.toISOString().split('.')[0]
endDate.value = newDateRange.end.toISOString().split('.')[0]
fetchTargets(startDate.value, endDate.value)
}
Expand Down
32 changes: 12 additions & 20 deletions src/components/Scheduling/Calendar.vue
Original file line number Diff line number Diff line change
@@ -1,41 +1,33 @@
<script setup>
import { ref, watch, defineEmits } from 'vue'
import VueDatePicker from '@vuepic/vue-datepicker'
import '@vuepic/vue-datepicker/dist/main.css'
import { ref, watch, defineEmits, onMounted } from 'vue'
import { fetchSemesterData, currentSemesterEnd } from '../../utils/calendarUtils'
const emits = defineEmits(['updateDateRange'])
// here's the documentation for vuedatepicker:
// https://vue3datepicker.com/props/general-configuration/
const dateRange = ref()
const today = new Date()
watch(dateRange, (newVal) => {
emits('updateDateRange', newVal)
})
const currentDate = new Date()
onMounted(async () => {
await fetchSemesterData()
})
</script>

<template>
<div class="container">
<h3>Select a Date Range</h3>
<VueDatePicker
<VDatePicker
v-model="dateRange"
range
:enable-time-picker="false"
:min-date="new Date()"
:max-date="new Date(currentDate.setDate(currentDate.getDate() + 15))"
mode="date"
is-range
:min-date="today"
:max-date="new Date(currentSemesterEnd)"
placeholder="Select Dates"
required
:date-range="dateRange"
class="custom-date-picker"
is-required
/>
</div>
</template>

<style scoped>
.custom-date-picker {
width: 20%;
}
</style>
3 changes: 3 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import vuetify from './plugins/vuetify'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faBook, faStar, faChartLine, faCalendarDays, faGamepad, faChevronRight, faSquare, faThLarge, faGear, faSliders, faClock, faXmark, faPlusCircle, faDownload, faPenRuler, faListCheck } from '@fortawesome/free-solid-svg-icons'
import { faTelescope, faLocationDot, faCameraRetro } from '@fortawesome/pro-regular-svg-icons'
import VCalendar from 'v-calendar'
import 'v-calendar/style.css'

library.add(faBook, faStar, faChartLine, faCalendarDays, faGamepad, faChevronRight, faSquare, faThLarge, faGear, faSliders, faClock, faXmark, faPlusCircle, faDownload, faPenRuler, faTelescope, faLocationDot, faCameraRetro, faListCheck)

Expand All @@ -19,4 +21,5 @@ createApp(App)
.use(router)
.use(vuetify)
.use(pinia)
.use(VCalendar, {})
.mount('#app')
12 changes: 7 additions & 5 deletions src/tests/integration/components/beginnerScheduling.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,16 @@ describe('BeginnerScheduling.vue', () => {
const endDate = new Date()
// Adds 15 days to the start date
endDate.setDate(startDate.getDate() + 15)

const newDateRange = [startDate, endDate]

const newDateRange = {
start: startDate,
end: endDate
}
await wrapper.vm.handleDateRangeUpdate(newDateRange)

expect(fetchApiCall).toHaveBeenCalledTimes(1)
// Because the Calendar component is a child of BeginnerScheduling, this test also makes the api call to fetch semester data (which is not tested here)
expect(fetchApiCall).toHaveBeenCalledTimes(2)
expect(fetchApiCall).toHaveBeenCalledWith({
url: `https://whatsup.lco.global/range/?start=${startDate.toISOString().split('.')[0]}&end=${endDate.toISOString().split('.')[0]}&aperture=0m4&mode=full`,
url: `https://whatsup.lco.global/range/?start=${newDateRange.start.toISOString().split('.')[0]}&end=${newDateRange.end.toISOString().split('.')[0]}&aperture=0m4&mode=full`,
method: 'GET',
successCallback: expect.any(Function),
failCallback: expect.any(Function)
Expand Down
47 changes: 47 additions & 0 deletions src/tests/integration/components/calendar.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { shallowMount } from '@vue/test-utils'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import Calendar from '../../../components/Scheduling/Calendar.vue'
import { fetchSemesterData, currentSemesterEnd } from '../../../utils/calendarUtils'
import flushPromises from 'flush-promises'

vi.mock('../../../utils/calendarUtils', () => ({
fetchSemesterData: vi.fn(),
currentSemesterEnd: '2024-08-01T12:00:00Z'
}))

describe('Calendar.vue', () => {
let wrapper

beforeEach(() => {
vi.clearAllMocks()

const VDatePickerStub = {
name: 'VDatePicker',
template: '<div></div>',
props: ['max-date']
}

wrapper = shallowMount(Calendar, {
global: {
stubs: {
VDatePicker: VDatePickerStub
}
}
})
})

it('calls fetchSemesterData on mount', () => {
expect(fetchSemesterData).toHaveBeenCalledTimes(1)
})

it('passes the correct max-date to VDatePicker', async () => {
await flushPromises()
const datePicker = wrapper.findComponent({ name: 'VDatePicker' })
expect(datePicker.exists()).toBe(true)
// Note: The prop name 'maxDate' is camel-cased instead of using 'max-date' because Vue automatically converts kebab-case prop names to camelCase
// when passing them in js. In templates, we use 'max-date', but when accessing props programmatically, we use 'maxDate'
const maxDateProp = datePicker.props('maxDate')
const maxDateIsoString = new Date(maxDateProp).toISOString().split('.')[0] + 'Z'
expect(maxDateIsoString).toBe(currentSemesterEnd)
})
})
50 changes: 50 additions & 0 deletions src/tests/unit/utils/calendarUtils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { fetchApiCall } from '../../../utils/api.js'
import { fetchSemesterData, currentSemesterEnd, currentSemesterStart } from '../../../utils/calendarUtils.js'
import { createTestStores } from '../../../utils/testUtils'

vi.mock('@/utils/api.js', () => ({
fetchApiCall: vi.fn()
}))

describe('calendarUtils.js', () => {
let configurationStore

beforeEach(() => {
const { configurationStore: store } = createTestStores()
configurationStore = store
fetchApiCall.mockClear()
})

describe('fetchSemesterData', () => {
it('sets currentSemesterStart and currentSemesterEnd after making the fetchApiCall', async () => {
const mockResponse = {
results: [
{ start: '2024-01-01', end: '2024-06-30' },
{ start: '2024-07-01', end: '2024-12-31' }
]
}

// Mocking today's date to ensure the correct semester is selected
const today = new Date('2024-08-15')
// This sets the system time to the provided date
vi.setSystemTime(today)

fetchApiCall.mockImplementationOnce(({ successCallback }) => {
successCallback(mockResponse)
})

await fetchSemesterData()

expect(fetchApiCall).toHaveBeenCalledTimes(1)
expect(fetchApiCall).toHaveBeenCalledWith({
url: 'http://mock-api.com/semesters/',
method: 'GET',
successCallback: expect.any(Function)
})

expect(currentSemesterStart).toBe('2024-07-01')
expect(currentSemesterEnd).toBe('2024-12-31')
})
})
})
Loading

0 comments on commit 856414b

Please sign in to comment.