client/components/date-range-picker/index.vue (287 lines of code) (raw):
<script>
// Copyright (c) 2017-2024 Uber Technologies Inc.
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import moment from 'moment';
import DatePicker from 'vue2-datepicker';
import {
getDateString,
getMaxEndDate,
getRange,
getRangeDisplayText,
getShortcuts,
getTimePanelLabel,
isDateValid,
isDayDisabled,
} from './helpers';
export default {
props: ['dateRange', 'maxDays', 'minStartDate'],
data() {
const range = getRange(this.dateRange);
return {
interval: undefined,
range,
startTimeString: getDateString(range[0]),
endTimeString: getDateString(range[1]),
now: new Date(),
open: false,
showTimePanel: false,
};
},
mounted() {
this.interval = setInterval(() => {
this.now = new Date();
}, 60 * 1000);
},
beforeDestroy() {
clearInterval(this.interval);
},
computed: {
endTime() {
return moment(this.endTimeString);
},
endTimeInvalid() {
return !isDateValid(this.endTime, this.minStartDate, this.maxEndDate);
},
isDayDisabled() {
return isDayDisabled(this.minStartDate);
},
maxEndDate() {
return getMaxEndDate(this.now);
},
rangeDisplayText() {
return getRangeDisplayText(this.dateRange);
},
shortcuts() {
return getShortcuts(this.maxDays, this.minStartDate);
},
startTime() {
return moment(this.startTimeString);
},
startTimeInvalid() {
return !isDateValid(this.startTime, this.minStartDate, this.maxEndDate);
},
startOrEndTimeInvalid() {
return this.startTimeInvalid || this.endTimeInvalid;
},
timePanelLabel() {
return getTimePanelLabel(this.showTimePanel);
},
},
methods: {
onShortcutClick(shortcut) {
this.$emit('change', shortcut.value);
this.open = false;
},
onDateRangeChange(range) {
const [startTime, endTime] = range;
if (!startTime || !endTime) {
return;
}
this.$emit('change', { startTime, endTime });
},
onCustomRangeSubmit() {
let { startTime, endTime } = this;
if (this.startOrEndTimeInvalid) {
return;
}
if (endTime.isBefore(startTime)) {
startTime = this.endTime;
endTime = this.startTime;
}
this.$emit('change', {
startTime,
endTime,
});
this.open = false;
},
onClickTimePanelLabel() {
this.showTimePanel = !this.showTimePanel;
},
},
components: {
DatePicker,
},
watch: {
dateRange() {
this.range = getRange(this.dateRange);
this.startTimeString = getDateString(this.range[0]);
this.endTimeString = getDateString(this.range[1]);
},
},
};
</script>
<template>
<div class="date-range-picker">
<date-picker
range
type="datetime"
v-model="range"
:clearable="false"
:disabled-date="isDayDisabled"
:showTimePanel="showTimePanel"
:open.sync="open"
@change="onDateRangeChange"
>
<template v-slot:input>
<input
placeholder="Date Range"
readonly
type="text"
:value="rangeDisplayText"
/>
</template>
<template v-slot:sidebar>
<div class="sidebar-column sidebar-column-shortcuts">
<h5>Quick ranges</h5>
<button
class="mx-btn mx-btn-text mx-btn-shortcut"
type="button"
v-for="shortcut in shortcuts"
:key="shortcut.value"
@click="onShortcutClick(shortcut)"
>
{{ shortcut.text }}
</button>
</div>
<div class="sidebar-column sidebar-column-custom-range">
<form @submit.prevent="onCustomRangeSubmit">
<h5>Custom range</h5>
<div>
<label for="custom-range-from">From:</label>
<input
id="custom-range-from"
maxlength="19"
v-model="startTimeString"
:class="{ invalid: startTimeInvalid }"
/>
</div>
<div>
<label for="custom-range-to">To:</label>
<input
id="custom-range-to"
maxlength="19"
v-model="endTimeString"
:class="{ invalid: endTimeInvalid }"
/>
</div>
<div>
<button
class="sidebar-button"
type="submit"
:disabled="startOrEndTimeInvalid"
>
Apply
</button>
</div>
</form>
</div>
</template>
<template v-slot:footer>
<button class="mx-btn mx-btn-text" @click="onClickTimePanelLabel">
{{ timePanelLabel }}
</button>
</template>
</date-picker>
</div>
</template>
<style lang="stylus">
sidebarColumnCustomRangeWidth = 181px;
sidebarColumnShortcutsWidth = 130px;
sidebarColumnPadding = 12px;
sidebarWidth = sidebarColumnShortcutsWidth + sidebarColumnCustomRangeWidth;
.mx-datepicker-popup {
td {
text-align: center;
}
.mx-datepicker-sidebar {
padding: 6px 0;
width: sidebarWidth;
&+ .mx-datepicker-content {
margin-left: sidebarWidth;
}
}
.sidebar-column {
float: left;
height: 100%;
padding: 0 sidebarColumnPadding;
&+ .sidebar-column {
border-left: 1px solid #e8e8e8;
}
label {
display: block;
font-weight: 500;
}
button {
padding: 0;
}
input {
margin: 0 0 8px 0;
padding: 8px 10px;
width: 156px;
&.invalid {
border: 1px solid #D44333;
&:focus {
outline: #D44333 auto 3px;
}
}
}
h5 {
text-transform: none;
}
}
.sidebar-column-shortcuts {
width: sidebarColumnShortcutsWidth;
}
.sidebar-column-custom-range {
width: sidebarColumnCustomRangeWidth;
}
button.sidebar-button {
background-color: #11939a;
border: none;
color: #fff;
cursor: pointer;
display: inline-block;
float: right;
font-size: 12px;
font-weight: bold;
padding: 8px 12px;
&:disabled {
background-color: #75F7FE;
cursor: not-allowed;
}
}
}
.date-range-picker {
input {
border: 1px solid #e5e5e4;
border-radius: 0;
box-shadow: none;
cursor: pointer;
height: auto;
margin: 0;
padding: 8px 30px 8px 10px;
width: 325px;
}
.mx-datepicker-range {
width: auto;
}
}
</style>