Skip to content
Snippets Groups Projects
Commit 7260b165 authored by Sander August Heggland Schrader's avatar Sander August Heggland Schrader
Browse files

Added an input component for date ranges

parent 09f0f1a8
No related branches found
No related tags found
1 merge request!81Renting
<template>
<div class="calendar">
<div
class="
grid grid-cols-7
py-2
mt-0.5
border-b border-black border-opacity-10
dark:border-litepie-secondary-700 dark:border-opacity-100
"
>
<div class="months" v-for="day in days" :key="day">{{ day }}</div>
</div>
<div class="grid grid-cols-7 gap-y-0.5 my-1">
<div
class="days blocked"
v-for="index in daysBeforeStartOfMonth"
:key="index"
>
<button>
{{ daysInMonthBeforeMonth - (daysBeforeStartOfMonth - index) }}
</button>
</div>
<div
class="days relative"
v-for="index in daysInMonth"
:key="index"
:class="{
disabled: this.isBlocked(index),
selected: this.isDayStartDate(index) && !this.endDate,
start: this.isDayStartDate(index),
end: this.isDayEndDate(index),
error: !!(
this.isBlocked(index) && this.isDayBetweenStartAndEndDate(index)
),
between: this.isDayBetweenStartAndEndDate(index),
}"
>
<span class="overlay absolute inset-0"></span>
<button v-on:Click="selectDate(index)">{{ index }}</button>
</div>
<div
class="days blocked"
v-for="index in daysRemainingInLastWeek"
:key="index"
>
<button>{{ index }}</button>
</div>
</div>
<p v-if="doesDaysSelectedContainBlockedDays()" class="text-red-500">
{{ errorMessage }}
</p>
</div>
</template>
<script>
export default {
props: {
month: {
type: Date,
required: true,
},
blockedDaysRange: {
type: Array,
default: () => [],
},
start: {
type: Date,
default: () => null,
},
end: {
type: Date,
default: () => null,
},
errorMessage: {
type: String,
default: () => "You cannot select a blocked day",
},
},
data() {
return {
days: ["Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"],
};
},
computed: {
monthDate() {
return new Date(this.month);
},
startDate() {
return this.start ? new Date(this.start) : null;
},
endDate() {
return this.end ? new Date(this.end) : null;
},
blockedDays() {
const blockedDays = [];
this.blockedDaysRange.forEach((blockedDay) => {
if (blockedDay.length === 2) {
const start = new Date(blockedDay[0]);
const end = new Date(blockedDay[1]);
// Check if the start or end dates range ends in the current month or after the current month
if(start.getMonth() <= this.monthDate.getMonth() && end.getMonth() >= this.monthDate.getMonth()) {
if(start.getMonth() < this.monthDate.getMonth() && end.getMonth() > this.monthDate.getMonth()) {
// Add all days of month to the list
for(let i = 1; i <= this.daysInMonth; i++) {
blockedDays.push(i);
}
} else if(start.getMonth() === this.monthDate.getMonth()) {
// Add all days of start month to the list
for(let i = start.getDate(); i <= this.daysInMonth; i++) {
blockedDays.push(i);
}
} else if(end.getMonth() === this.monthDate.getMonth()) {
// Add all days of end month to the list
for(let i = 1; i <= end.getDate(); i++) {
blockedDays.push(i);
}
}
}
} else {
// check that day is in this month
const day = new Date(blockedDay[0]);
if (day.getMonth() !== this.monthDate.getMonth()) return;
blockedDays.push(day.getDate());
}
});
return blockedDays;
},
daysInMonth() {
return new Date(
this.monthDate.getFullYear(),
this.monthDate.getMonth() + 1,
0
).getDate();
},
daysRemainingInLastWeek() {
return (
7 -
new Date(
new Date(this.month).getFullYear(),
new Date(this.month).getMonth() + 1,
0
).getDay()
);
},
daysInMonthBeforeMonth() {
let daysInMonth = new Date(
new Date(this.month).getFullYear(),
new Date(this.month).getMonth(),
0
).getDate();
return daysInMonth;
},
daysBeforeStartOfMonth() {
let firstDayOfMonth = new Date(
new Date(this.month).getFullYear(),
new Date(this.month).getMonth(),
1
);
let dayOfWeek = firstDayOfMonth.getDay();
let daysBefore = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
return daysBefore;
},
},
methods: {
isDateInMonth(date) {
if (!date) return null;
return (
date.getFullYear() === this.monthDate.getFullYear() &&
date.getMonth() === this.monthDate.getMonth()
);
},
isDayStartDate(day) {
// Check that start day is in month
if (!this.isDateInMonth(this.startDate)) return false;
return this.startDate && this.startDate.getDate() === day;
},
isDayEndDate(day) {
// Check that end day is in month
if (!this.isDateInMonth(this.endDate)) return false;
return this.endDate && this.endDate.getDate() === day;
},
selectDate(day) {
const date = new Date(
this.monthDate.getFullYear(),
this.monthDate.getMonth(),
day
);
this.$emit("selectDate", date);
},
isBlocked(day) {
return this.blockedDays.includes(day);
},
isDayBlocked(day) {
return this.blockedDays.includes(day);
},
isDayBetweenBlocked(day) {
return this.blockedDaysRange.some(([start, end]) => {
return start <= day && day <= end;
});
},
// Get date from day and month and check if it is between start and end
isDayBetweenStartAndEndDate(day) {
if (!this.startDate || !this.endDate) return false;
const date = new Date(
this.monthDate.getFullYear(),
this.monthDate.getMonth(),
day
);
return (
this.startDate.getTime() < date.getTime() &&
date.getTime() < this.endDate.getTime()
);
},
doesDaysSelectedContainBlockedDays() {
return false;
},
},
};
</script>
<style scoped>
.calendar {
max-width: 400px;
}
.months {
font-size: small;
text-align: center;
}
.days {
font-size: small;
text-align: center;
}
.blocked button {
cursor: not-allowed;
color: gray;
}
.disabled {
cursor: not-allowed;
}
.disabled button {
cursor: not-allowed;
}
button {
background-color: transparent;
width: 3rem;
height: 3rem;
}
.disabled span {
background-color: red;
border-radius: 5px;
opacity: 0.6;
}
.disabled button {
color: black;
}
.selected.start span {
background-color: blue;
opacity: 0.8;
border-radius: 5px;
}
.start span {
background-color: blue;
opacity: 0.8;
border-radius: 10px 0 0 10px;
}
.start button {
color: white;
font-weight: bold;
}
.end button {
color: white;
font-weight: bold;
}
.between span {
background-color: lightblue;
opacity: 0.8;
}
.error span {
background-color: red;
opacity: 0.7;
border-radius: 0;
}
.end button {
color: white;
font-weight: bold;
}
.end span {
background-color: blue;
opacity: 0.8;
border-radius: 0 10px 10px 0;
}
.overlay {
z-index: -1;
}
</style>
\ No newline at end of file
<template>
<div>
<div class="input" v-on:click="openCalendar()">
<label>
<input type="text" v-model="value" placeholder="HEI HEI">
</label>
</div>
<div class="picker" :style="style">
<div class="calenders">
<div>
<month-selector
@back="back"
type="month"
@forward="forward('month')"
:month="month"
></month-selector>
<calendar-component
:month="month"
:start="startDate"
:end="endDate"
@selectDate="selectDate"
:blockedDaysRange="blockedDaysRange"
></calendar-component>
</div>
<div class="split"></div>
<div>
<month-selector
@back="back"
type="monghM"
@forward="forward('monghM')"
:month="monghM"
></month-selector>
<calendar-component
:month="monghM"
:start="startDate"
:end="endDate"
@selectDate="selectDate"
:blockedDaysRange="blockedDaysRange"
></calendar-component>
</div>
</div>
<div class="footer">
<p v-if="error" class="error"> {{ errorMessage }} </p>
<div v-else></div>
<div class="buttons">
<button class="btn btn-gray" v-on:click="reset()">RESET</button>
<button class="btn btn-blue" v-on:click="complete()">COMPLETE</button>
</div>
</div>
</div>
</div>
</template>
<script>
import CalendarComponent from "./CalendarComponent.vue";
import MonthSelector from "./MonthSelector.vue";
export default {
components: {
CalendarComponent,
MonthSelector,
},
props: {
range: {
type: Boolean,
default: true,
},
blockedDaysRange: {
type: Array,
default: () => [],
}
},
data() {
return {
month: new Date(),
monghM: new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1),
startDate: new Date(Number.MAX_VALUE),
endDate: null,
error: false,
errorMessage: "Produktet kan ikke utlånes i denne perioden",
open: false,
value: null,
style: {
display: "none",
},
};
},
methods: {
openCalendar() {
// Edit style
this.open = true;
this.style = {
display: "flex",
};
},
reset() {
this.startDate = new Date(Number.MAX_VALUE);
this.endDate = null;
this.value = null;
this.error = false;
},
complete() {
if(this.error) return;
this.open = false;
this.style = {
display: "none",
};
// Set value to the start and end date
this.value = `${this.startDate.toLocaleDateString()} - ${this.endDate.toLocaleDateString()}`;
this.$emit('value', {
startDate: this.startDate,
endDate: this.endDate
})
},
selectDate(date) {
const day = date.getDate();
const month = date.getMonth();
const year = date.getFullYear();
if (this.isBlocked(day, month, year)) return;
if (date > this.startDate) {
this.endDate = date;
} else {
this.startDate = date;
}
if (this.range) {
this.value = `${this.startDate?.toLocaleDateString()} - ${this.endDate?.toLocaleDateString() || ""}`;
} else {
this.value = `${this.startDate.toLocaleDateString()}`;
}
// Check if start and end check if any days in range is blocked
for(let i = new Date(this.startDate.toUTCString()); i <= this.endDate; i.setDate(i.getDate() + 1)) {
if (this.isBlocked(i.getDate(), i.getMonth(), i.getFullYear())) {
this.error = true;
return;
}
}
if(this.error) this.error = false;
},
isBlocked(day, month, year) {
return this.blockedDaysRange.some(([start, end]) => {
// Check if start is the same day
if (
start.getDate() === day &&
start.getMonth() === month &&
start.getFullYear() === year
) {
return true;
}
return (
(start <= new Date(year, month, day) &&
new Date(year, month, day) <= end)
);
});
},
back(type) {
switch (type) {
case "month": {
this.month.setMonth(this.month.getMonth() - 1);
this.month = new Date(this.month);
break;
}
case "monghM":
this.monghM.setMonth(this.monghM.getMonth() - 1);
this.monghM = new Date(this.monghM);
if (this.monghM.getMonth() === this.month.getMonth()) {
this.month.setMonth(this.month.getMonth() - 1);
this.month = new Date(this.month);
}
break;
}
},
forward(type) {
switch (type) {
case "month": {
this.month.setMonth(this.month.getMonth() + 1);
this.month = new Date(this.month);
if (this.month.getMonth() == this.monghM.getMonth()) {
this.monghM.setMonth(this.monghM.getMonth() + 1);
this.monghM = new Date(this.monghM);
}
break;
}
case "monghM":
this.monghM.setMonth(this.monghM.getMonth() + 1);
this.monghM = new Date(this.monghM);
break;
}
},
},
};
</script>
<style scoped>
.input label {
display: block;
position: relative;
}
.input input {
position: relative;
width: 100%;
overflow: hidden;
border: 1px solid #ccc;
border-radius: 10px;
padding: 10px;
padding-right: 50px;
}
.picker {
position: absolute;
background-color: white;
margin-top: 5px;
z-index: 50;
border: 1px solid black;
border-radius: 4px;
padding: 5px;
flex-direction: column;
width: auto;
max-width: 700px;
overflow: auto;
-webkit-box-shadow: 0px 10px 13px -7px #000000, 5px 5px 14px 5px rgba(0,0,0,0);
box-shadow: 0px 10px 13px -7px #000000, 5px 5px 14px 5px rgba(0,0,0,0);
}
.split {
border-left: 2px solid black;
margin-left: 10px;
margin-right: 10px;
height: auto;
}
.calenders {
display: flex;
}
.footer {
display: flex;
justify-content: space-between;
}
.error {
align-self: center;
color: red;
}
.buttons {
float: right;
display: flex;
justify-content: flex-end;
}
@media only screen and (max-width: 800px) {
.calenders {
flex-direction: column;
}
.picker {
max-width: 420px;
}
}
.btn {
@apply font-bold py-2 px-4 rounded;
}
.btn-blue {
@apply bg-blue-500 text-white;
}
.btn-blue:hover {
@apply bg-blue-700;
}
.btn-gray:hover {
@apply bg-gray-300;
}
</style>
\ No newline at end of file
<template>
<div class="container-c main">
<div class="button" v-on:click="back">
<button>
<span>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 19l-7-7 7-7"></path></svg>
</span>
</button>
</div>
<div class="container-c text">
<div class="date">
{{monthShort}}
</div>
<div class="date">
{{year}}
</div>
</div>
<div class="button" v-on:click="forward">
<button>
<span>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 5l7 7-7 7"></path></svg>
</span>
</button>
</div>
</div>
</template>
<script>
export default {
props: {
month: {
type: Date,
required: true,
},
type: {
type: String,
required: true
}
},
computed: {
monthDate() {
return new Date(this.month);
},
monthShort() {
return this.monthDate.toLocaleString('default', { month: 'short' }).toUpperCase();
},
year() {
return this.monthDate.getFullYear();
},
}, methods: {
back() {
this.$emit('back', this.type);
},
forward() {
console.log(this.type)
this.$emit('forward', this.type);
},
}
}
</script>
<style>
.container-c {
display: flex;
}
.container-c.main {
justify-content: space-between;
border: 1px solid black;
padding: 5px;
border-radius: 5px;
max-width: 400px;
}
.text {
justify-content: space-between;
width: 50%;
text-align: center;
}
.date {
width: 50%;
text-align: center;
}
.button {
display: flex;
justify-content: center;
align-items: center;
}
</style>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment