Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
323 changes: 142 additions & 181 deletions packages/web-pkg/src/components/Filters/DateFilter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,15 @@
:is-clearable="true"
:current-date="fromDate"
:is-dark="currentTheme.isDark"
@date-changed="
(value: { date: DateTime; error: boolean }) => setDateRangeDate(value, 'from')
"
@date-changed="(value) => setDateRangeDate(value, 'from')"
/>
<oc-datepicker
:label="$gettext('To')"
:is-clearable="true"
:current-date="toDate"
:min-date="fromDate ? fromDate : undefined"
:is-dark="currentTheme.isDark"
@date-changed="
(value: { date: DateTime; error: boolean }) => setDateRangeDate(value, 'to')
"
@date-changed="(value) => setDateRangeDate(value, 'to')"
/>
</div>
<div class="date-filter-apply-btn text-end">
Expand All @@ -122,8 +118,8 @@
</div>
</template>

<script lang="ts">
import { PropType, computed, defineComponent, onMounted, ref, unref } from 'vue'
<script setup lang="ts">
import { computed, onMounted, ref, unref } from 'vue'
import omit from 'lodash-es/omit'
import { useRoute, useRouteQuery, useRouter, useThemeStore } from '../../composables'
import { formatDateFromDateTime } from '../../helpers'
Expand All @@ -135,206 +131,171 @@ import { storeToRefs } from 'pinia'

type Item = Record<string, string>

export default defineComponent({
name: 'DateFilter',
props: {
filterLabel: {
type: String,
required: true
},
filterName: {
type: String,
required: true
},
items: {
type: Array as PropType<Item[]>,
required: true
},
idAttribute: {
type: String,
required: false,
default: 'id'
},
displayNameAttribute: {
type: String,
required: false,
default: 'name'
}
},
emits: ['selectionChange'],
setup: function (props, { emit, expose }) {
const router = useRouter()
const { current: currentLanguage } = useGettext()
const currentRoute = useRoute()
const themeStore = useThemeStore()
const { currentTheme } = storeToRefs(themeStore)
const selectedItem = ref<Item>()
const displayedItems = ref(props.items)
const fromDate = ref<DateTime>()
const toDate = ref<DateTime>()
const dateRangeClicked = ref(false)
const filterChip = ref<typeof OcFilterChip>()
const {
filterLabel,
filterName,
items,
idAttribute = 'id',
displayNameAttribute = 'name'
} = defineProps<{
filterLabel: string
filterName: string
items: Item[]
idAttribute?: string
displayNameAttribute?: string
}>()

const queryParam = `q_${props.filterName}`
const currentRouteQuery = useRouteQuery(queryParam)
const emit = defineEmits<{
(e: 'selectionChange', value: Item | undefined): void
}>()

const getId = (item: Item) => {
return item[props.idAttribute as keyof Item]
}
const router = useRouter()
const { current: currentLanguage } = useGettext()
const currentRoute = useRoute()
const themeStore = useThemeStore()
const { currentTheme } = storeToRefs(themeStore)
const selectedItem = ref<Item>()
const displayedItems = ref(items)
const fromDate = ref<DateTime>()
const toDate = ref<DateTime>()
const dateRangeClicked = ref(false)
const filterChip = ref<typeof OcFilterChip>()

const setRouteQuery = () => {
return router.push({
query: {
...omit(unref(currentRoute).query, [queryParam]),
...(unref(selectedItem) && {
[queryParam]: getId(unref(selectedItem))
})
}
})
}
const queryParam = `q_${filterName}`
const currentRouteQuery = useRouteQuery(queryParam)

const isItemSelected = (item: Item) => {
return unref(selectedItem) && getId(unref(selectedItem)) === getId(item)
}
const getId = (item: Item) => {
return item[idAttribute as keyof Item]
}

const resetDateRange = () => {
fromDate.value = undefined
toDate.value = undefined
const setRouteQuery = () => {
return router.push({
query: {
...omit(unref(currentRoute).query, [queryParam]),
...(unref(selectedItem) && {
[queryParam]: getId(unref(selectedItem))
})
}
})
}

const toggleItemSelection = async (item: Item) => {
resetDateRange()
if (isItemSelected(item)) {
selectedItem.value = undefined
} else {
selectedItem.value = item
unref(filterChip).hideDrop()
}
await setRouteQuery()
emit('selectionChange', unref(selectedItem))
}
const isItemSelected = (item: Item) => {
return unref(selectedItem) && getId(unref(selectedItem)) === getId(item)
}

const clearFilter = () => {
selectedItem.value = undefined
dateRangeClicked.value = false
resetDateRange()
emit('selectionChange', unref(selectedItem))
setRouteQuery()
}
const resetDateRange = () => {
fromDate.value = undefined
toDate.value = undefined
}

const onShowDrop = () => {
displayedItems.value = props.items
}
const toggleItemSelection = async (item: Item) => {
resetDateRange()
if (isItemSelected(item)) {
selectedItem.value = undefined
} else {
selectedItem.value = item
unref(filterChip).hideDrop()
}
await setRouteQuery()
emit('selectionChange', unref(selectedItem))
}

const onHideDrop = () => {
dateRangeClicked.value = false
}
const clearFilter = () => {
selectedItem.value = undefined
dateRangeClicked.value = false
resetDateRange()
emit('selectionChange', unref(selectedItem))
setRouteQuery()
}

const setSelectedItemsBasedOnQuery = () => {
const id = queryItemAsString(unref(currentRouteQuery))
if (!id) {
return
}
const onShowDrop = () => {
displayedItems.value = items
}

const selected = props.items.find((s) => getId(s) === id)
if (selected) {
selectedItem.value = selected
return
}
const onHideDrop = () => {
dateRangeClicked.value = false
}

if (unref(dateRangeApplied)) {
const dateRange = id.replace('range:', '')
const from = Number(dateRange.split(' - ')[0])
const to = Number(dateRange.split(' - ')[1])
fromDate.value = DateTime.fromMillis(from)
toDate.value = DateTime.fromMillis(to)
selectedItem.value = unref(dateRangeOption)
}
}
const setSelectedItemsBasedOnQuery = () => {
const id = queryItemAsString(unref(currentRouteQuery))
if (!id) {
return
}

const dateRangeApplied = computed(() =>
queryItemAsString(unref(currentRouteQuery))?.startsWith('range:')
)
const selected = items.find((s) => getId(s) === id)
if (selected) {
selectedItem.value = selected
return
}

const dateRangeValid = computed(() => {
if (!unref(fromDate) || !unref(toDate)) {
return false
}
return unref(fromDate) <= unref(toDate)
})
if (unref(dateRangeApplied)) {
const dateRange = id.replace('range:', '')
const from = Number(dateRange.split(' - ')[0])
const to = Number(dateRange.split(' - ')[1])
fromDate.value = DateTime.fromMillis(from)
toDate.value = DateTime.fromMillis(to)
selectedItem.value = unref(dateRangeOption)
}
}

const dateRangeOption = computed(() => {
if (!unref(fromDate) || !unref(toDate)) {
return null
}
const dateRangeApplied = computed(() =>
queryItemAsString(unref(currentRouteQuery))?.startsWith('range:')
)

const from = formatDateFromDateTime(unref(fromDate), currentLanguage, DateTime.DATE_SHORT)
const to = formatDateFromDateTime(unref(toDate), currentLanguage, DateTime.DATE_SHORT)
const fromDateMillis = unref(fromDate).toMillis()
const toDateMillis = unref(toDate).toMillis()
const dateRangeValid = computed(() => {
if (!unref(fromDate) || !unref(toDate)) {
return false
}
return unref(fromDate) <= unref(toDate)
})

return {
[props.idAttribute]: `range:${fromDateMillis} - ${toDateMillis}`,
[props.displayNameAttribute]: `${from} - ${to}`
}
})
const dateRangeOption = computed(() => {
if (!unref(fromDate) || !unref(toDate)) {
return null
}

const applyDateRangeFilter = async () => {
selectedItem.value = unref(dateRangeOption)
await setRouteQuery()
unref(filterChip).hideDrop()
emit('selectionChange', unref(selectedItem))
}
const from = formatDateFromDateTime(unref(fromDate), currentLanguage, DateTime.DATE_SHORT)
const to = formatDateFromDateTime(unref(toDate), currentLanguage, DateTime.DATE_SHORT)
const fromDateMillis = unref(fromDate).toMillis()
const toDateMillis = unref(toDate).toMillis()

const setDateRangeDate = (
{ date, error }: { date: DateTime; error: boolean },
type: 'from' | 'to'
) => {
if (error) {
console.error('date could not be set')
return
}
return {
[idAttribute]: `range:${fromDateMillis} - ${toDateMillis}`,
[displayNameAttribute]: `${from} - ${to}`
}
})

const prop = type === 'from' ? fromDate : toDate
const applyDateRangeFilter = async () => {
selectedItem.value = unref(dateRangeOption)
await setRouteQuery()
unref(filterChip).hideDrop()
emit('selectionChange', unref(selectedItem))
}

if (!date) {
prop.value = undefined
return
}
const setDateRangeDate = (
{ date, error }: { date: DateTime; error: boolean },
type: 'from' | 'to'
) => {
if (error) {
console.error('date could not be set')
return
}

const formattedDate = type === 'from' ? date.startOf('day') : date.endOf('day')
prop.value = formattedDate
}
const prop = type === 'from' ? fromDate : toDate

expose({ setSelectedItemsBasedOnQuery })
if (!date) {
prop.value = undefined
return
}

onMounted(() => {
setSelectedItemsBasedOnQuery()
})
const formattedDate = type === 'from' ? date.startOf('day') : date.endOf('day')
prop.value = formattedDate
}

return {
clearFilter,
displayedItems,
isItemSelected,
selectedItem,
onShowDrop,
onHideDrop,
filterChip,
toggleItemSelection,
setSelectedItemsBasedOnQuery,
fromDate,
toDate,
dateRangeValid,
applyDateRangeFilter,
dateRangeApplied,
setDateRangeDate,
dateRangeClicked,
currentTheme,
defineExpose({ setSelectedItemsBasedOnQuery })

// for unit tests
queryParam
}
}
onMounted(() => {
setSelectedItemsBasedOnQuery()
})
</script>
<style scoped>
Expand Down
Loading