Skip to content

Commit

Permalink
custom calendar widget
Browse files Browse the repository at this point in the history
  • Loading branch information
ThePyProgrammer committed Apr 16, 2023
1 parent 102b0e9 commit 1d81ce2
Show file tree
Hide file tree
Showing 4 changed files with 796 additions and 0 deletions.
398 changes: 398 additions & 0 deletions frontend/src/components/calendar/Calendar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,398 @@
<template>
<div ref="calendarContainer" class="min-h-full min-w-full text-gray-800">
<div class="w-full border grid grid-cols-7">
<Top />
<div
v-for="day in daysOfTheWeek" :key="day"
class="text-center text-sm md:text-base lg:text-lg font-semibold border"
>
{{ day.substring(0, 3) }}
</div>


<!-- <div v-if="firstDayOfCurrentMonth > 0"> -->
<div
v-for="day in firstDayOfCurrentMonth"
:key="day"
class="h-16 md:h-36 w-full border opacity-50"
></div>
<!-- </div> -->
<div
v-for="day in daysInCurrentMonth"
:key="day"
class="h-16 md:h-36 w-full border align-top"
>
<div
class="w-full h-full text-xs md:text-sm lg:text-base text-center transition-colors"
:class="{
'bg-slate-200 text-gray-600 font-medium': isToday(day),
'hover:bg-gray-100 hover:text-gray-700': !isToday(day),
}"
>

<div v-if="isToday(day)" class="flex w-full justify-center items-center pt-1">
<div class="h-6 w-6 flex justify-center items-center rounded-full shadow-sm" :class="{'bg-purple-600 text-white': isToday(day)}">
<h3 class="font-medium"> {{ day }} </h3>
</div>
</div>
<div v-else>{{ day }}</div>


<!-- <span>{{ day }}</span> -->
<div
v-for="evt in maxThreeTodaysEvent(day, events)"
:key="evt.id"
class="hidden md:block"
>
<!-- color="#ffcccb" -->
<v-card
class="w-full px-2 py-1 flex space-x-1 items-center whitespace-nowrap overflow-hidden cursor-pointer rounded-sm my-1"
@click="togglePopover($event, evt)"
:class="{'bg-red-200': evt.conference, 'bg-blue-200': evt.competition}">
<div class="w-1/12">
<div class="h-2 w-2 rounded-full bg-purple-600"></div>
</div>
<div class="w-11/12">
<h5 class="text-xs tracking-tight text-clip overflow-hidden">
{{ evt.title }}
</h5>
</div>
</v-card>
</div>

<div
v-if="allTodaysEvent(day, events).length > 3"
class="hidden md:flex mt-2 w-full px-2 py-1 space-x-2 items-center whitespace-nowrap overflow-hidden hover:text-gray-800 hover:font-medium cursor-pointer rounded-sm"
@click="openModal(day, allTodaysEvent(day, events))"
>
<div class="w-1/12">

<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 6v12m6-6H6"
/>
</svg>
</div>
<div class="w-11/12">
<h6
class="text-xs tracking-tight text-clip text-left overflow-hidden"
>
{{ allTodaysEvent(day, events).length - 3 + " more events" }}
</h6>
</div>
</div>

<div
v-if="allTodaysEvent(day, events).length > 0"
class="flex md:hidden h-2/3 w-full justify-center items-center"
@click="openModal(day, allTodaysEvent(day, events))"
>
<div
class="h-6 w-6 flex justify-center items-center text-xs bg-purple-600 rounded-full shadow-sm"
>
<h3 class="font-medium text-white">
{{ allTodaysEvent(day, events).length }}
</h3>
</div>
</div>
</div>
</div>

<!-- <div v-if="lastEmptyCells > 0"> -->
<div
v-for="day in lastEmptyCells"
:key="day"
class="h-16 md:h-36 w-full border opacity-50"
></div></div>

<!-- mobile navigation -->
<div class="md:hidden col-span-7 flex justify-between items-center p-2">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5 hover:text-gray-500 cursor-pointer hover:h-6 hover:w-6 transition-all"
@click="calendarStore.decrementMonth(1)"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M18.75 19.5l-7.5-7.5 7.5-7.5m-6 15L5.25 12l7.5-7.5"
/>
</svg>
</div>
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5 hover:text-gray-500 cursor-pointer hover:h-6 hover:w-6 transition-all"
@click="calendarStore.incrementMonth(1)"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M11.25 4.5l7.5 7.5-7.5 7.5m-6-15l7.5 7.5-7.5 7.5"
/>
</svg>
</div>
<!-- </div> -->
</div>
</div>

<!-- use the modal component -->
<transition name="modal">
<Modal
v-if="modalShow"
@close-modal="closeModal"
@toggle-popover="togglePopover"
:day="modalDay"
:month="calendarStore.getMonth"
:year="calendarStore.getYear"
:events="modalEvents"
/>
</transition>

<!-- popover component -->
<div
ref="popoverRef"
:class="{ hidden: !popoverShow, block: popoverShow }"
class="bg-gray-100 mb-3 block z-50 max-w-xs rounded-lg shadow-md"
>
<slot
name="eventDialog"
:eventDialogData="todaysEvent"
:closeEventDialog="togglePopover"
/>
</div>
</template>

<script lang="ts" setup>
import { ref, onMounted, onUpdated, type Ref } from "vue";
import Top from "@/components/calendar/Top.vue";
import Modal from "@/components/calendar/EventsModal.vue";
import { useCalendarStore } from "@/store/calendar";
import { usePopover } from "@/composables/popover";
import { Event } from "@/types/calendar"
/**************************************
* PROPS
* ************************************
*/
const props = defineProps({
events: {
type: Array<Event>,
required: true,
},
});
// Store initialization and subscription
const calendarStore = useCalendarStore();
calendarStore.$subscribe((mutation, state) => {
getDaysInMonth();
getFirstDayOfMonth();
});
// component variables
const daysOfTheWeek = {
1: "Sunday",
2: "Monday",
3: "Tuesday",
4: "Wednesday",
5: "Thursday",
6: "Friday",
7: "Saturday",
};
const daysInCurrentMonth = ref(0);
const firstDayOfCurrentMonth = ref(0);
const lastEmptyCells = ref(0);
const modalShow = ref(false);
const modalDay = ref(0);
const popoverRef: Ref<HTMLElement | null> = ref(null);
const modalEvents: Ref<Event[]> = ref([]);
// popover composable
const { popoverShow, todaysEvent, togglePopover } = usePopover(popoverRef);
/**
* Gets the number of days present in a month
* The month is gotten from the calendar store
*/
const getDaysInMonth = () => {
daysInCurrentMonth.value = new Date(
calendarStore.getYear,
calendarStore.getMonth + 1,
0
).getDate();
};
/**
* Gets in number, the first day of a month
* The month is gottenn from the calendar store
*/
const getFirstDayOfMonth = () => {
firstDayOfCurrentMonth.value = new Date(
calendarStore.getYear,
calendarStore.getMonth,
1
).getDay();
};
/**
* Gets the last empty cells (if any) on the calendar grid
*/
const lastCalendarCells = () => {
let totalGrid = firstDayOfCurrentMonth.value <= 5 ? 35 : 42;
lastEmptyCells.value =
totalGrid - daysInCurrentMonth.value - firstDayOfCurrentMonth.value;
};
/**
* Validates a day to check if it's today or not
*
* @param {number} day The day to validate
* @return boolean True or false if it's today or not
*/
const isToday = (day: number) => {
let today = new Date();
if (
calendarStore.getYear == today.getFullYear() &&
calendarStore.getMonth == today.getMonth() &&
day == today.getDate()
)
return true;
return false;
};
/**
* Validates a day to check if event start date is current calendar date or not
*
* @param {number} day The calendar month date to check against
* @param {string} startdate The event start date
* @return boolean True or false if event is today or not
*/
const isEventToday = (day: number, startdate: string) => {
if (
calendarStore.getYear == parseInt(startdate.substring(0, 4)) &&
calendarStore.getMonth + 1 == parseInt(startdate.substring(5, 7)) &&
day == parseInt(startdate.substring(8, 10))
)
return true;
return false;
};
/**
* Gets at most, 3 calendar events on a given day
* This events are displayed on the calendar grid (>= Large screens)
*
* @param {number} day calendar month day whose event(s) we're getting
* @param {array} events Array of events objects to filter through
*
* @return array Array of the filtered day's event(s)
*/
const maxThreeTodaysEvent = (day: number, events: Event[]) => {
if (!events.length) return [];
let threeTodaysEventArr = [] as Event[];
events.forEach((event: Event) => {
if (threeTodaysEventArr.length == 3) return threeTodaysEventArr;
if (isEventToday(day, event.time.start)) {
threeTodaysEventArr.push(event);
}
});
return threeTodaysEventArr;
};
/**
* Gets all the calendar events on a given day
*
* @param {number} day calendar month day whose event(s) we're getting
* @param {array} events Array of events objects to filter through
*
* @return array Array of the filtered day's event(s)
*/
const allTodaysEvent = (day: number, events: Event[]) => {
if (!events.length) return [];
let todaysEvent = [] as Event[];
events.forEach((event) => {
if (isEventToday(day, event.time.start)) {
todaysEvent.push(event);
}
});
return todaysEvent;
};
/**
* Open the event details modal
*
* @param {number} day current calendar month day
* @param {array} events Array of events objects to show on the modal
*
* @return null
*/
const openModal = (day: number, events: Event[]) => {
popoverShow.value = false; // close any open popover before opening modal
modalEvents.value = events;
modalDay.value = day;
modalShow.value = true;
};
/**
* Close the event details modal
*/
const closeModal = () => {
modalEvents.value = [];
modalShow.value = false;
modalDay.value = 0;
};
/************************************************************************
* LIFECYCLE HOOKS
* **********************************************************************
*/
onMounted(() => {
getDaysInMonth();
getFirstDayOfMonth();
lastCalendarCells();
});
onUpdated(() => {
getFirstDayOfMonth();
lastCalendarCells();
});
</script>

<style scoped>
.modal-enter-active,
.modal-leave-active {
transition: translate 0.5s ease;
}
.modal-enter-from,
.modal-leave-to {
/** opacity: 0; **/
translate: 0px 100%;
}
</style>
Loading

0 comments on commit 1d81ce2

Please sign in to comment.