Skip to content

Commit 02d6c44

Browse files
committed
Add DatePicker v1
1 parent c990c78 commit 02d6c44

1 file changed

Lines changed: 188 additions & 135 deletions

File tree

components/DatePicker.vue

Lines changed: 188 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup>
22
/** Vendor */
3-
import { DateTime } from "luxon"
3+
import { DateTime, Info } from "luxon"
44
55
/** UI */
66
import Button from "@/components/ui/Button.vue"
@@ -16,13 +16,70 @@ const props = defineProps({
1616
1717
const month = ref(DateTime.now().month)
1818
const year = ref(DateTime.now().year)
19-
const startDate = ref(null)
20-
const endDate = ref(null)
21-
const days = ref([])
19+
const startDate = ref({})
20+
const endDate = ref({})
21+
const weekdays = ref(Info.weekdays('narrow', { locale: 'en-US' }))
22+
const days = computed(() => {
23+
let rawDays = []
24+
const firstDay = DateTime.local(year.value, month.value).setLocale('en-US')
25+
const lastDay = firstDay.endOf('month')
2226
23-
// console.log(month.value);
24-
// console.log(year.value);
25-
// console.log(DateTime.local(year.value, month.value).toFormat('LLLL'));
27+
for (let day = firstDay; day <= lastDay; day = day.plus({ days: 1 })) {
28+
rawDays.push(day)
29+
}
30+
31+
if (firstDay.weekday !== 1) {
32+
let prevDay = firstDay
33+
while (prevDay.weekday !== 1) {
34+
prevDay = prevDay.minus({ days: 1 }).startOf('day')
35+
rawDays.unshift(prevDay)
36+
}
37+
}
38+
39+
if (lastDay.weekday !== 7) {
40+
let nextDay = lastDay
41+
while (nextDay.weekday !== 7) {
42+
nextDay = nextDay.plus({ days: 1 }).startOf('day')
43+
rawDays.push(nextDay)
44+
}
45+
}
46+
47+
let resDays = []
48+
while (rawDays.length) {
49+
resDays.push(rawDays.splice(0, 7))
50+
}
51+
52+
return resDays
53+
})
54+
55+
const handleSelectDate = (d) => {
56+
if (!startDate.value.ts) {
57+
startDate.value = d
58+
} else if (startDate.value.ts !== d.ts) {
59+
if (!endDate.value.ts) {
60+
if (startDate.value.ts > d.ts) {
61+
endDate.value = startDate.value
62+
startDate.value = d
63+
} else {
64+
endDate.value = d
65+
}
66+
} else {
67+
startDate.value = d
68+
endDate.value = {}
69+
}
70+
} else {
71+
if (!endDate.value.ts) {
72+
startDate.value = {}
73+
} else {
74+
startDate.value = endDate.value
75+
endDate.value = {}
76+
}
77+
}
78+
}
79+
80+
const isInSelectedPeriod = (d) => {
81+
return startDate.value.ts < d.ts && d.ts < endDate.value.ts
82+
}
2683
2784
const isOpen = ref(false)
2885
const handleOpen = () => {
@@ -53,11 +110,11 @@ const handleMonthChange = (v) => {
53110
switch (month.value + v) {
54111
case 0:
55112
month.value = 12
56-
year.value -= 1
113+
year.value --
57114
break
58115
case 13:
59116
month.value = 1
60-
year.value += 1
117+
year.value ++
61118
break
62119
default:
63120
month.value += v
@@ -75,136 +132,132 @@ const handleMonthChange = (v) => {
75132

76133
<template #content>
77134
<Flex direction="column" gap="12">
78-
<!-- <div :class="$style.calendar"> -->
79-
<div>
80-
<!-- <Flex align="center" gap="6" :class="$style.calendar_header"> -->
81-
<Flex align="center" justify="center" gap="6">
82-
<Icon
83-
@click="handleMonthChange(-1)"
84-
name="chevron"
85-
size="14"
86-
color="tertiary"
87-
:style="{ transform: 'rotate(90deg)' }"
88-
/>
89-
90-
<Text size="12" color="secondary"> {{ `${DateTime.local(year, month).toFormat('LLLL')} ${year}` }} </Text>
91-
92-
<Icon
93-
@click="handleMonthChange(1)"
94-
name="chevron"
95-
size="14"
96-
color="tertiary"
97-
:style="{ transform: 'rotate(-90deg)' }"
98-
/>
99-
</Flex>
100-
<!-- <div class="calendar-body">
101-
<div class="day" v-for="day in days" :key="day.date">
102-
<span
103-
:class="{
104-
'is-today': isToday(day.date),
105-
'is-selected': isSelected(day.date),
106-
'is-in-range': isInRange(day.date)
107-
}"
108-
@click="selectDate(day.date)"
109-
>
110-
{{ day.date.getDate() }}
111-
</span>
112-
</div>
113-
</div>
114-
<div class="selected-dates">
115-
<p>Start Date: {{ formatDate(startDate) }}</p>
116-
<p>End Date: {{ formatDate(endDate) }}</p>
117-
</div> -->
118-
</div>
119-
120-
121-
<!-- <Text size="12" weight="500" color="secondary">Filter by Date</Text> -->
122-
123-
<!-- <Input v-model="searchTerm" size="small" placeholder="Search" autofocus /> -->
124-
125-
<!-- <Flex direction="column" gap="8" :class="$style.message_types_list">
126-
<template
127-
v-if="
128-
Object.keys(filters.message_type).filter((t) =>
129-
t.toLowerCase().includes(searchTerm.trim().toLowerCase()),
130-
).length
131-
"
132-
>
133-
<Checkbox
134-
v-for="msg_type in Object.keys(filters.message_type).filter((t) =>
135-
t.toLowerCase().includes(searchTerm.trim().toLowerCase()),
136-
)"
137-
v-model="filters.message_type[msg_type]"
138-
>
139-
<Text size="12" weight="500" color="primary">{{ msg_type.replace("Msg", "") }}</Text>
140-
</Checkbox>
141-
</template>
142-
<Flex v-else direction="column" gap="8">
143-
<Text size="12" weight="500" color="tertiary">Nothing was found</Text>
144-
</Flex>
145-
</Flex> -->
135+
<Flex align="center" justify="center" gap="6">
136+
<Icon
137+
@click="handleMonthChange(-1)"
138+
name="chevron"
139+
size="14"
140+
color="tertiary"
141+
:style="{ transform: 'rotate(90deg)' }"
142+
/>
143+
144+
<Text size="12" color="secondary"> {{ `${DateTime.local(year, month).toFormat('LLLL')} ${year}` }} </Text>
145+
146+
<Icon
147+
@click="handleMonthChange(1)"
148+
name="chevron"
149+
size="14"
150+
color="tertiary"
151+
:style="{ transform: 'rotate(-90deg)' }"
152+
/>
153+
</Flex>
154+
155+
<Flex direction="column" gap="16" wide :class="$style.table">
156+
<table>
157+
<thead>
158+
<tr>
159+
<th v-for="wd in weekdays">
160+
<Text size="10" color="secondary"> {{ wd }} </Text>
161+
</th>
162+
</tr>
163+
</thead>
164+
165+
<tbody>
166+
<tr v-for="w in days">
167+
<td v-for="d in w">
168+
<Flex align="center" justify="center"
169+
@click="handleSelectDate(d)"
170+
:class="[
171+
$style.day,
172+
(d.ts === startDate.ts || d.ts === endDate.ts) && $style.edgeDate,
173+
isInSelectedPeriod(d) && $style.inSelectedPeriod
174+
]"
175+
>
176+
<Text size="12" color="primary"
177+
:class="[
178+
d.month !== month && $style.notInCurrentMonth,
179+
(d.ts === startDate.ts || d.ts === endDate.ts || isInSelectedPeriod(d)) && $style.text_primary
180+
]"
181+
> {{ d.day }} </Text>
182+
</Flex>
183+
</td>
184+
</tr>
185+
</tbody>
186+
</table>
187+
</Flex>
146188

147189
<Button @click="handleApply" type="secondary" size="mini" wide>Apply</Button>
148190
</Flex>
149191
</template>
150192
</Popover>
151-
<!-- <Popover :open="isMessageTypePopoverOpen" @on-close="onMessageTypePopoverClose" width="250">
152-
<Button @click="handleOpenMessageTypePopover" type="secondary" size="mini">
153-
<Icon name="plus-circle" size="12" color="tertiary" />
154-
<Text color="secondary">Message Type</Text>
155-
156-
<template v-if="Object.keys(filters.message_type).find((f) => filters.message_type[f])">
157-
<div :class="$style.vertical_divider" />
158-
159-
<Text size="12" weight="600" color="primary">
160-
{{
161-
Object.keys(filters.message_type).filter((f) => filters.message_type[f]).length < 3
162-
? Object.keys(filters.message_type)
163-
.filter((f) => filters.message_type[f])
164-
.map((f) => f.replace("Msg", ""))
165-
.join(", ")
166-
: `${Object.keys(filters.message_type)
167-
.filter((f) => filters.message_type[f])[0]
168-
.replace("Msg", "")} and ${
169-
Object.keys(filters.message_type).filter((f) => filters.message_type[f]).length - 1
170-
} more`
171-
}}
172-
</Text>
173-
174-
<Icon @click.stop="resetFilters('message_type', true)" name="close-circle" size="12" color="secondary" />
175-
</template>
176-
</Button>
193+
</template>
177194

178-
<template #content>
179-
<Flex direction="column" gap="12">
180-
<Text size="12" weight="500" color="secondary">Filter by Message Type</Text>
181-
182-
<Input v-model="searchTerm" size="small" placeholder="Search" autofocus />
183-
184-
<Flex direction="column" gap="8" :class="$style.message_types_list">
185-
<template
186-
v-if="
187-
Object.keys(filters.message_type).filter((t) =>
188-
t.toLowerCase().includes(searchTerm.trim().toLowerCase()),
189-
).length
190-
"
191-
>
192-
<Checkbox
193-
v-for="msg_type in Object.keys(filters.message_type).filter((t) =>
194-
t.toLowerCase().includes(searchTerm.trim().toLowerCase()),
195-
)"
196-
v-model="filters.message_type[msg_type]"
197-
>
198-
<Text size="12" weight="500" color="primary">{{ msg_type.replace("Msg", "") }}</Text>
199-
</Checkbox>
200-
</template>
201-
<Flex v-else direction="column" gap="8">
202-
<Text size="12" weight="500" color="tertiary">Nothing was found</Text>
203-
</Flex>
204-
</Flex>
195+
<style module>
196+
.table {
197+
transition: all 0.2s ease;
205198
206-
<Button @click="handleApplyMessageTypeFilters" type="secondary" size="mini" wide>Apply</Button>
207-
</Flex>
208-
</template>
209-
</Popover> -->
210-
</template>
199+
& table {
200+
width: 100%;
201+
height: fit-content;
202+
203+
border-spacing: 0px;
204+
205+
& tbody {
206+
& tr {
207+
transition: all 0.05s ease;
208+
}
209+
}
210+
211+
& tr th {
212+
text-align: center;
213+
padding-bottom: 8px;
214+
}
215+
216+
& tr td {
217+
text-align: center;
218+
219+
cursor: pointer;
220+
}
221+
222+
th:first-child, td:first-child {
223+
border-radius: 3px;
224+
}
225+
226+
th:last-child, td:last-child {
227+
border-radius: 3px;
228+
}
229+
}
230+
}
231+
232+
.day {
233+
min-width: 20px;
234+
min-height: 20px;
235+
236+
border-radius: 3px;
237+
}
238+
239+
.day:hover {
240+
background-color: rgba(51, 168, 83, 70%);
241+
}
242+
243+
.notInCurrentMonth {
244+
color: var(--txt-tertiary);
245+
}
246+
247+
.edgeDate {
248+
background-color: rgba(51, 168, 83, 70%);
249+
/* background-color: var(--neutral-green); */
250+
}
251+
252+
.inSelectedPeriod {
253+
background-color: var(--btn-secondary-bg);
254+
}
255+
256+
.text_primary {
257+
color: var(--txt-primary);
258+
}
259+
260+
.endDate {
261+
background-color: rgba(51, 168, 83, 70%);
262+
}
263+
</style>

0 commit comments

Comments
 (0)