Skip to content

Commit

Permalink
Add time range selector (#187)
Browse files Browse the repository at this point in the history
Add range selector
---------

Co-authored-by: Cees Voesenek <[email protected]>
  • Loading branch information
wkramer and ceesvoesenek authored Jul 25, 2023
1 parent 4613dcd commit 8cd5cd1
Show file tree
Hide file tree
Showing 3 changed files with 349 additions and 44 deletions.
142 changes: 142 additions & 0 deletions src/components/timecontrol/DateTimeSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<template>
<div class="container">
<v-tooltip right>
<template v-slot:activator="{ on: onTooltip }">
<v-menu v-model="showDatePicker">
<template v-slot:activator="{ on: onMenu, attrs }">
<v-text-field
v-model="selectedDate"
:label="label"
:prepend-icon="prependDateIcon"
readonly
v-bind="attrs"
v-on="{ ...onTooltip, ...onMenu }"
/>
</template>
<v-date-picker
v-model="selectedDate"
no-title
scrollable
@input="onSelectedDateTimeChange"
/>
</v-menu>
</template>
<span>
Set date for &ldquo;{{ label }}&rdquo;
</span>
</v-tooltip>

<v-tooltip right>
<template v-slot:activator="{ on: onTooltip }">
<v-menu v-model="showTimePicker">
<template v-slot:activator="{ on: onMenu, attrs }">
<v-text-field
v-model="selectedTime"
readonly
:prepend-icon="prependTimeIcon"
v-bind="attrs"
v-on="{ ...onTooltip, ...onMenu }"
/>
</template>
<v-time-picker
v-model="selectedTime"
no-title
scrollable
@input="onSelectedDateTimeChange"
/>
</v-menu>
</template>
<span>
Set time for &ldquo;{{ label }}&rdquo;
</span>
</v-tooltip>

<v-tooltip right>
<template v-slot:activator="{ on, attrs }">
<v-btn
v-bind="attrs"
v-on="on"
class="button"
icon
@click="setNow"
>
<v-icon>
mdi-timeline-clock-outline
</v-icon>
</v-btn>
</template>
<span>
Set current time for &ldquo;{{ label }}&rdquo;
</span>
</v-tooltip>
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
import { DateTime } from 'luxon'
@Component
export default class DateTimeSelector extends Vue {
@Prop({ default: null }) value!: Date | null
@Prop({ default: 'Date' }) label!: string
@Prop({ default: 'mdi-calendar' }) prependDateIcon!: string
@Prop({ default: 'mdi-clock' }) prependTimeIcon!: string
showDatePicker: boolean = false
showTimePicker: boolean = false
selectedDate: string | null = null
selectedTime: string | null = null
mounted() {
this.onValueChange()
}
@Watch('value')
onValueChange(): void {
this.onExternalDateTimeChange(this.value)
}
setNow(): void {
const now = new Date()
this.onExternalDateTimeChange(now)
this.$emit('input', now)
}
onExternalDateTimeChange(externalDateTime: Date | null): void {
if (externalDateTime === null) {
this.selectedDate = null
this.selectedTime = null
} else {
const dateTime = DateTime.fromJSDate(externalDateTime)
this.selectedDate = dateTime.toISODate()
this.selectedTime = dateTime
.startOf('minute')
.toISOTime({
suppressSeconds: true,
suppressMilliseconds: true,
includeOffset: false
})
}
}
onSelectedDateTimeChange(): void {
// If the time was not set, use a default.
if (!this.selectedTime) this.selectedTime = '00:00'
const dateTimeString = `${this.selectedDate}T${this.selectedTime}`
const dateTime = this.selectedDate ? DateTime.fromISO(dateTimeString).toJSDate() : null
this.$emit('input', dateTime)
}
}
</script>

<style scoped>
.container {
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
}
</style>
95 changes: 95 additions & 0 deletions src/components/timecontrol/IntervalSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<template>
<v-list class="interval-list">
<v-list-item-group
v-model="selectedIndex"
@change="onSelectInterval"
mandatory
>
<!-- Empty list item to trick the list into show no selection, while still setting "mandatory" -->
<v-list-item v-show="false"/>
<v-list-item>
<template v-slot:default="{ active }">
<v-list-item-title>
Default
</v-list-item-title>
<v-list-item-icon>
<v-icon v-show="active" small>
mdi-check
</v-icon>
</v-list-item-icon>
</template>
</v-list-item>
<v-list-item
v-for="(item, index) in items"
:key="index"
>
<template v-slot:default="{ active }">
<v-list-item-title>
{{ intervalToLocaleString(item) }}
</v-list-item-title>
<v-list-item-icon>
<v-icon v-show="active" small>
mdi-check
</v-icon>
</v-list-item-icon>
</template>
</v-list-item>
</v-list-item-group>
</v-list>
</template>

<script lang="ts">
import { DateTime, Duration } from 'luxon'
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
@Component
export default class IntervalSelector extends Vue {
// The interval is either a string with an interval, which may be "default" for the Default value,
// or null if an absolute start and end date have been selected.
@Prop({ default: 'default' }) value!: string | null
@Prop({ default: () => [] }) items!: string[]
@Prop({ default: () => new Date() }) now!: Date
selectedIndex: number | undefined = 0
mounted() {
this.onValueChange()
}
@Watch('value')
onValueChange(): void {
if (this.value === null) {
this.selectedIndex = 0
} else if (this.value === 'default') {
this.selectedIndex = 1
} else {
this.selectedIndex = this.items.findIndex((entry) => entry === this.value) + 2
}
}
intervalToLocaleString(interval: string) {
const duration = Duration.fromISO(interval)
const startDateTime = DateTime.fromJSDate(this.now).plus(duration)
return startDateTime.toRelative()
}
onSelectInterval(index: number): void {
let selectedInterval = undefined
if (index === 0) {
selectedInterval = null
} else if (index === 1) {
selectedInterval = 'default'
} else {
selectedInterval = this.items[index - 2]
}
this.$emit('input', selectedInterval)
}
}
</script>

<style scoped>
.interval-list {
max-height: 400px;
overflow-y: auto;
}
</style>
Loading

0 comments on commit 8cd5cd1

Please sign in to comment.