From c4d0333a76447d7e78e9749d8b6c2724b4a10452 Mon Sep 17 00:00:00 2001 From: Melissa Autumn Date: Mon, 11 Dec 2023 13:02:55 -0800 Subject: [PATCH 1/7] Add AppointmentStore and CalendarStore. - CalendarStore will only pull if the `isInit` value is false. - CalendarStore cache is busted on user action. - AppointmentStore will always pull if requested (Due to some impl concerns.) --- frontend/src/App.vue | 104 +++++++------------ frontend/src/components/SettingsCalendar.vue | 23 ++-- frontend/src/stores/alert-store.js | 4 +- frontend/src/stores/appointment-store.js | 66 ++++++++++++ frontend/src/stores/calendar-store.js | 40 +++++++ frontend/src/stores/user-store.js | 2 +- frontend/src/views/AppointmentsView.vue | 15 ++- frontend/src/views/CalendarView.vue | 34 +++--- frontend/src/views/HomeView.vue | 7 -- frontend/src/views/ProfileView.vue | 28 ++--- frontend/src/views/ScheduleView.vue | 27 +++-- frontend/src/views/SettingsView.vue | 8 +- 12 files changed, 204 insertions(+), 154 deletions(-) create mode 100644 frontend/src/stores/appointment-store.js create mode 100644 frontend/src/stores/calendar-store.js diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 26d680097..dd2e7b554 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -10,10 +10,7 @@
- +
@@ -32,23 +29,26 @@ diff --git a/frontend/src/components/SettingsCalendar.vue b/frontend/src/components/SettingsCalendar.vue index f2041e561..88e5b883e 100644 --- a/frontend/src/components/SettingsCalendar.vue +++ b/frontend/src/components/SettingsCalendar.vue @@ -8,7 +8,7 @@ import { calendarManagementType } from '@/definitions'; import { IconArrowRight } from '@tabler/icons-vue'; -import { ref, reactive, inject, onMounted, computed } from 'vue'; +import { + ref, reactive, inject, onMounted, computed, +} from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute, useRouter } from 'vue-router'; import AlertBox from '@/elements/AlertBox'; @@ -208,12 +210,14 @@ import GoogleSignInBtn from '@/assets/img/google/1x/btn_google_signin_light_norm import GoogleSignInBtn2x from '@/assets/img/google/2x/btn_google_signin_light_normal_web@2x.png'; import PrimaryButton from '@/elements/PrimaryButton'; import SecondaryButton from '@/elements/SecondaryButton'; -import ConfirmationModal from "@/components/ConfirmationModal.vue"; +import ConfirmationModal from '@/components/ConfirmationModal.vue'; +import { useCalendarStore } from '@/stores/calendar-store'; // component constants const { t } = useI18n({ useScope: 'global' }); const call = inject('call'); const refresh = inject('refresh'); +const calendarStore = useCalendarStore(); const calendarConnectError = ref(''); @@ -223,11 +227,6 @@ const deleteCalendarModalTarget = ref(null); // Temp until we get a store solution rolling const loading = ref(false); -// view properties -defineProps({ - calendars: Array, // list of calendars from db -}); - // handle calendar user input to add or edit calendar connections const inputModes = { hidden: 0, @@ -264,7 +263,9 @@ const closeModals = async () => { }; const refreshData = async () => { - await refresh({ onlyConnectedCalendars: false }); + // Invalidate our calendar store + await calendarStore.reset(); + await refresh(); loading.value = false; }; @@ -391,7 +392,5 @@ onMounted(async () => { calendarConnectError.value = route.query.error; await router.replace(route.path); } - - await refreshData(); }); diff --git a/frontend/src/stores/alert-store.js b/frontend/src/stores/alert-store.js index 074c0eac2..9f0d2d153 100644 --- a/frontend/src/stores/alert-store.js +++ b/frontend/src/stores/alert-store.js @@ -13,7 +13,7 @@ const initialSiteNotificationObject = { // eslint-disable-next-line import/prefer-default-export export const useSiteNotificationStore = defineStore('siteNotification', { state: () => ({ - data: initialSiteNotificationObject, + data: structuredClone(initialSiteNotificationObject), }), getters: { isVisible() { @@ -50,7 +50,7 @@ export const useSiteNotificationStore = defineStore('siteNotification', { }); }, reset() { - this.$patch({ data: initialSiteNotificationObject }); + this.$patch({ data: structuredClone(initialSiteNotificationObject) }); }, }, }); diff --git a/frontend/src/stores/appointment-store.js b/frontend/src/stores/appointment-store.js new file mode 100644 index 000000000..d0d04bc19 --- /dev/null +++ b/frontend/src/stores/appointment-store.js @@ -0,0 +1,66 @@ +import { defineStore } from 'pinia'; +import { appointmentState } from '@/definitions'; +import { useUserStore } from '@/stores/user-store'; +import dj from 'dayjs'; + +const initialData = { + appointments: [], + isInit: false, +}; + +// eslint-disable-next-line import/prefer-default-export +export const useAppointmentStore = defineStore('appointments', { + state: () => ({ + data: structuredClone(initialData), + }), + getters: { + isLoaded() { + return this.data.isInit; + }, + appointments() { + return this.data.appointments; + }, + pendingAppointments() { + return this.data.appointments.filter((a) => a.status === appointmentState.pending); + }, + }, + actions: { + status(appointment) { + // check past events + if (appointment.slots.filter((s) => dj(s.start).isAfter(dj())).length === 0) { + return appointmentState.past; + } + // check booked events + if (appointment.slots.filter((s) => s.attendee_id != null).length > 0) { + return appointmentState.booked; + } + // else event is still wating to be booked + return appointmentState.pending; + }, + reset() { + this.$patch({ data: structuredClone(initialData) }); + }, + async mergeCalendarInfo(calendarsById) { + const userStore = useUserStore(); + + this.data.appointments.forEach((a) => { + a.calendar_title = calendarsById[a.calendar_id]?.title; + a.calendar_color = calendarsById[a.calendar_id]?.color; + a.status = this.status(a); + a.active = a.status !== appointmentState.past; // TODO + // convert start dates from UTC back to users timezone + a.slots.forEach((s) => { + s.start = dj.utc(s.start).tz(userStore.data.timezone ?? dj.tz.guess()); + }); + }); + }, + async fetch(call) { + const { data, error } = await call('me/appointments').get().json(); + if (!error.value) { + if (data.value === null || typeof data.value === 'undefined') return; + this.data.appointments = data.value; + this.data.isInit = true; + } + }, + }, +}); diff --git a/frontend/src/stores/calendar-store.js b/frontend/src/stores/calendar-store.js new file mode 100644 index 000000000..b622af7da --- /dev/null +++ b/frontend/src/stores/calendar-store.js @@ -0,0 +1,40 @@ +import { defineStore } from 'pinia'; + +const initialData = { + calendars: [], + isInit: false, +}; + +// eslint-disable-next-line import/prefer-default-export +export const useCalendarStore = defineStore('calendars', { + state: () => ({ + data: structuredClone(initialData), + }), + getters: { + isLoaded() { + return this.data.isInit; + }, + unconnectedCalendars() { + return this.data.calendars.filter((cal) => !cal.connected); + }, + connectedCalendars() { + return this.data.calendars.filter((cal) => cal.connected); + }, + allCalendars() { + return this.data.calendars; + }, + }, + actions: { + reset() { + this.$patch({ data: structuredClone(initialData) }); + }, + async fetch(call) { + const { data, error } = await call('me/calendars?only_connected=false').get().json(); + if (!error.value) { + if (data.value === null || typeof data.value === 'undefined') return; + this.data.calendars = data.value; + this.data.isInit = true; + } + }, + }, +}); diff --git a/frontend/src/stores/user-store.js b/frontend/src/stores/user-store.js index 542428436..74f63aa62 100644 --- a/frontend/src/stores/user-store.js +++ b/frontend/src/stores/user-store.js @@ -20,7 +20,7 @@ export const useUserStore = defineStore('user', { return this.data.accessToken !== null; }, reset() { - this.$patch({ data: initialUserObject }); + this.$patch({ data: structuredClone(initialUserObject) }); }, async profile(fetch) { const { error, data } = await fetch('me').get().json(); diff --git a/frontend/src/views/AppointmentsView.vue b/frontend/src/views/AppointmentsView.vue index 3f0b84878..2c2824cf0 100644 --- a/frontend/src/views/AppointmentsView.vue +++ b/frontend/src/views/AppointmentsView.vue @@ -6,7 +6,7 @@ @@ -202,7 +202,7 @@ { // handle filtered appointments list const filteredAppointments = computed(() => { - let list = props.appointments ? [...props.appointments] : []; + let list = appointmentStore.appointments ? [...appointmentStore.appointments] : []; // by search input if (search.value !== '') { list = list.filter((a) => a.title.toLowerCase().includes(search.value.toLowerCase())); diff --git a/frontend/src/views/CalendarView.vue b/frontend/src/views/CalendarView.vue index 7891d1850..6431f431e 100644 --- a/frontend/src/views/CalendarView.vue +++ b/frontend/src/views/CalendarView.vue @@ -1,6 +1,6 @@ diff --git a/frontend/src/views/ProfileView.vue b/frontend/src/views/ProfileView.vue index 9d72a9796..9b8c976e9 100644 --- a/frontend/src/views/ProfileView.vue +++ b/frontend/src/views/ProfileView.vue @@ -16,12 +16,12 @@
-
{{ calendars.length }}/∞
+
{{ calendarStore.connectedCalendars.length }}/∞
{{ t('heading.calendarsConnected') }}
-
{{ pendingAppointments.length }}
+
{{ appointmentStore.pendingAppointments.length }}
{{ t('heading.pendingAppointments') }}
@@ -31,10 +31,10 @@ diff --git a/frontend/src/views/ScheduleView.vue b/frontend/src/views/ScheduleView.vue index ae601c256..026515134 100644 --- a/frontend/src/views/ScheduleView.vue +++ b/frontend/src/views/ScheduleView.vue @@ -38,7 +38,7 @@
@@ -67,7 +67,7 @@ v-show="tabActive === calendarViews.day" class="w-full md:w-4/5" :selected="activeDate" - :appointments="pendingAppointments" + :appointments="appointmentStore.pendingAppointments" :events="calendarEvents" popup-position="top" /> @@ -75,7 +75,7 @@ diff --git a/frontend/src/stores/appointment-store.js b/frontend/src/stores/appointment-store.js index d0d04bc19..e68bed600 100644 --- a/frontend/src/stores/appointment-store.js +++ b/frontend/src/stores/appointment-store.js @@ -40,12 +40,10 @@ export const useAppointmentStore = defineStore('appointments', { reset() { this.$patch({ data: structuredClone(initialData) }); }, - async mergeCalendarInfo(calendarsById) { + async postFetchProcess() { const userStore = useUserStore(); this.data.appointments.forEach((a) => { - a.calendar_title = calendarsById[a.calendar_id]?.title; - a.calendar_color = calendarsById[a.calendar_id]?.color; a.status = this.status(a); a.active = a.status !== appointmentState.past; // TODO // convert start dates from UTC back to users timezone @@ -61,6 +59,8 @@ export const useAppointmentStore = defineStore('appointments', { this.data.appointments = data.value; this.data.isInit = true; } + // After we fetch the data, apply some processing + await this.postFetchProcess(); }, }, }); From b8d0ce6cb67402b8a540c9aa13eadcf546d1a397 Mon Sep 17 00:00:00 2001 From: Melissa Autumn Date: Fri, 15 Dec 2023 10:41:00 -0800 Subject: [PATCH 7/7] PR suggestions --- frontend/src/App.vue | 4 +--- frontend/src/components/SettingsAccount.vue | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index dcd03810a..0fad1a3b8 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -30,9 +30,7 @@