|
17 | 17 | -->
|
18 | 18 |
|
19 | 19 | <template>
|
20 |
| - <dropdown-panel :text="selectedRange.title" :inline="inline"> |
| 20 | + <dropdown-panel |
| 21 | + :text="(isCustom && CUSTOM_TITLE) || selectedRange.title" |
| 22 | + :inline="inline" |
| 23 | + :close-on-click="false" |
| 24 | + > |
21 | 25 | <template #default="{ closePanel }">
|
22 | 26 | <div class="date-range-picker-panel">
|
23 | 27 | <div class="date-range-picker-body">
|
24 | 28 | <div class="date-range-picker-preset">
|
25 | 29 | <div style="border-right: 1px solid gray;">
|
26 | 30 | <header>Quick Ranges</header>
|
27 | 31 | <div class="preset-list">
|
28 |
| - <div v-for="(rangeSet, index) in rangeSets" :key="index" class="preset-column"> |
| 32 | + <div v-for="(rangeSet, index) in RANGE_SETS" :key="index" class="preset-column"> |
29 | 33 | <div
|
30 | 34 | v-for="range in rangeSet"
|
31 | 35 | :key="range.title"
|
32 | 36 | class="preset-value"
|
33 | 37 | :class="{ selected: range === selectedRange }"
|
34 | 38 | >
|
35 |
| - <hue-link @click="selectedRange = range">{{ range.title }}</hue-link> |
| 39 | + <hue-link @click="quickSelect(range, closePanel)"> |
| 40 | + {{ range.title }} |
| 41 | + </hue-link> |
36 | 42 | </div>
|
37 | 43 | </div>
|
38 | 44 | </div>
|
|
43 | 49 | <div>
|
44 | 50 | <div class="range-header">FROM:</div>
|
45 | 51 | <datepicker
|
46 |
| - :value="customFrom" |
| 52 | + v-model="customFrom" |
47 | 53 | :typeable="true"
|
48 | 54 | input-class="range-input"
|
49 | 55 | calendar-class="range-calendar"
|
50 | 56 | format="dd/MM/yyyy"
|
51 | 57 | placeholder="DD/MM/YYYY"
|
52 |
| - @selected="setCustomFrom" |
53 | 58 | />
|
54 | 59 | </div>
|
55 | 60 | <div>
|
56 | 61 | <div class="range-header">TO:</div>
|
57 | 62 | <datepicker
|
58 |
| - :value="customTo" |
| 63 | + v-model="customTo" |
59 | 64 | :typeable="true"
|
60 | 65 | input-class="range-input"
|
61 | 66 | calendar-class="range-calendar"
|
62 | 67 | format="dd/MM/yyyy"
|
63 | 68 | placeholder="DD/MM/YYYY"
|
64 |
| - @selected="setCustomTo" |
65 | 69 | />
|
66 | 70 | </div>
|
67 | 71 | </div>
|
|
76 | 80 | </template>
|
77 | 81 |
|
78 | 82 | <script lang="ts">
|
79 |
| - import { defineComponent } from 'vue'; |
| 83 | + import { defineComponent, ref, watch } from 'vue'; |
80 | 84 |
|
81 | 85 | import { Range } from './DateRangePicker';
|
82 | 86 | import { DateTime } from 'luxon';
|
|
137 | 141 |
|
138 | 142 | const DEFAULT_RANGE = RANGE_SETS[0][0];
|
139 | 143 |
|
| 144 | + const CUSTOM_TITLE = I18n('Custom Range'); |
| 145 | +
|
140 | 146 | export default defineComponent({
|
141 | 147 | name: 'DateRangePicker',
|
142 | 148 | components: {
|
|
145 | 151 | HueButton,
|
146 | 152 | HueLink
|
147 | 153 | },
|
148 |
| -
|
149 | 154 | props: {
|
150 | 155 | inline: {
|
151 | 156 | type: Boolean,
|
152 | 157 | required: false,
|
153 | 158 | default: false
|
154 | 159 | }
|
155 | 160 | },
|
156 |
| -
|
157 | 161 | emits: ['date-range-changed'],
|
158 |
| -
|
159 |
| - setup(): { |
160 |
| - rangeSets: Range[][]; |
161 |
| -
|
162 |
| - customRange: Range; |
163 |
| - } { |
164 |
| - return { |
165 |
| - rangeSets: RANGE_SETS, |
166 |
| -
|
167 |
| - customRange: { |
168 |
| - title: I18n('Custom Range'), |
169 |
| - from: RANGE_NOW.toMillis() - DEFAULT_RANGE.from, |
170 |
| - to: RANGE_NOW.toMillis(), |
171 |
| - custom: true |
| 162 | + setup(props, { emit }) { |
| 163 | + const selectedRange = ref<Range>(DEFAULT_RANGE); |
| 164 | + const isCustom = ref<boolean>(false); |
| 165 | + const customFrom = ref<Date>(new Date(RANGE_NOW.toMillis() - DEFAULT_RANGE.from)); |
| 166 | + const customTo = ref<Date>(new Date(RANGE_NOW.toMillis())); |
| 167 | +
|
| 168 | + watch(customFrom, () => { |
| 169 | + if (customFrom.value.getTime() > customTo.value.getTime()) { |
| 170 | + customTo.value = customFrom.value; |
172 | 171 | }
|
173 |
| - }; |
174 |
| - }, |
175 |
| -
|
176 |
| - data(): { |
177 |
| - selectedRange: Range; |
178 |
| - } { |
179 |
| - return { |
180 |
| - selectedRange: DEFAULT_RANGE |
181 |
| - }; |
182 |
| - }, |
183 |
| -
|
184 |
| - computed: { |
185 |
| - customFrom(): number | undefined { |
186 |
| - if (this.selectedRange.custom) { |
187 |
| - return this.selectedRange.from; |
188 |
| - } |
189 |
| - return undefined; |
190 |
| - }, |
191 |
| -
|
192 |
| - customTo(): number | undefined { |
193 |
| - if (this.selectedRange.custom) { |
194 |
| - return this.selectedRange.to; |
195 |
| - } |
196 |
| - return undefined; |
197 |
| - } |
198 |
| - }, |
| 172 | + }); |
199 | 173 |
|
200 |
| - methods: { |
201 |
| - // TODO: Switch to v-model and value prop |
202 |
| - clear(): void { |
203 |
| - if (this.selectedRange !== DEFAULT_RANGE) { |
204 |
| - this.selectedRange = DEFAULT_RANGE; |
205 |
| - this.notify(); |
| 174 | + watch(customTo, () => { |
| 175 | + if (customTo.value.getTime() < customFrom.value.getTime()) { |
| 176 | + customFrom.value = customTo.value; |
206 | 177 | }
|
207 |
| - }, |
| 178 | + }); |
208 | 179 |
|
209 |
| - setCustomFrom(from?: Date): void { |
210 |
| - if (from) { |
211 |
| - this.customRange.from = from.valueOf(); |
212 |
| - if (this.customRange.from > this.customRange.to) { |
213 |
| - this.customRange.to = this.customRange.from; |
214 |
| - } |
215 |
| - this.selectedRange = this.customRange; |
216 |
| - this.$forceUpdate(); |
217 |
| - } |
218 |
| - }, |
219 |
| -
|
220 |
| - setCustomTo(to?: Date): void { |
221 |
| - if (to) { |
222 |
| - this.customRange.to = to.valueOf(); |
223 |
| - if (this.customRange.to < this.customRange.from) { |
224 |
| - this.customRange.from = this.customRange.to; |
225 |
| - } |
226 |
| - this.selectedRange = this.customRange; |
227 |
| - this.$forceUpdate(); |
228 |
| - } |
229 |
| - }, |
230 |
| -
|
231 |
| - notify(): void { |
| 180 | + const notify = (): void => { |
232 | 181 | let range: Range;
|
233 |
| - if (this.selectedRange.custom) { |
| 182 | + if (isCustom.value) { |
234 | 183 | range = {
|
235 |
| - title: this.selectedRange.title, |
236 |
| - from: DateTime.fromMillis(this.selectedRange.from).startOf('day').valueOf(), |
237 |
| - to: DateTime.fromMillis(this.selectedRange.to).endOf('day').valueOf(), |
| 184 | + title: CUSTOM_TITLE, |
| 185 | + from: DateTime.fromJSDate(customFrom.value).startOf('day').valueOf(), |
| 186 | + to: DateTime.fromJSDate(customTo.value).endOf('day').valueOf(), |
238 | 187 | custom: true
|
239 | 188 | };
|
240 | 189 | } else {
|
| 190 | + const { title, from, to } = selectedRange.value; |
241 | 191 | const now = DateTime.utc().valueOf();
|
242 | 192 | range = {
|
243 |
| - title: this.selectedRange.title, |
244 |
| - from: now - this.selectedRange.from, |
245 |
| - to: now - this.selectedRange.to |
| 193 | + title, |
| 194 | + from: now - from, |
| 195 | + to: now - to |
246 | 196 | };
|
247 | 197 | }
|
248 |
| - this.$emit('date-range-changed', range); |
249 |
| - }, |
| 198 | + emit('date-range-changed', range); |
| 199 | + }; |
| 200 | +
|
| 201 | + const clear = (): void => { |
| 202 | + if (isCustom.value || selectedRange.value !== DEFAULT_RANGE) { |
| 203 | + customFrom.value = new Date(RANGE_NOW.toMillis() - DEFAULT_RANGE.from); |
| 204 | + customTo.value = new Date(RANGE_NOW.toMillis()); |
| 205 | + selectedRange.value = DEFAULT_RANGE; |
| 206 | + isCustom.value = false; |
| 207 | + notify(); |
| 208 | + } |
| 209 | + }; |
250 | 210 |
|
251 |
| - apply(closePanel: () => void): void { |
252 |
| - this.notify(); |
| 211 | + const apply = (closePanel: () => void): void => { |
| 212 | + isCustom.value = true; |
| 213 | + notify(); |
253 | 214 | closePanel();
|
254 |
| - } |
| 215 | + }; |
| 216 | +
|
| 217 | + const quickSelect = (range: Range, closePanel: () => void): void => { |
| 218 | + isCustom.value = false; |
| 219 | + selectedRange.value = range; |
| 220 | + notify(); |
| 221 | + closePanel(); |
| 222 | + }; |
| 223 | +
|
| 224 | + return { |
| 225 | + apply, |
| 226 | + clear, |
| 227 | + CUSTOM_TITLE, |
| 228 | + customFrom, |
| 229 | + customTo, |
| 230 | + isCustom, |
| 231 | + quickSelect, |
| 232 | + RANGE_SETS, |
| 233 | + selectedRange |
| 234 | + }; |
255 | 235 | }
|
256 | 236 | });
|
257 | 237 | </script>
|
|
0 commit comments