crm/frontend/src/components/SortBy.vue

287 lines
8.4 KiB
Vue

<template>
<Autocomplete
v-if="!sortValues?.size"
:options="options"
value=""
:placeholder="__('First Name')"
@change="(e) => setSort(e)"
>
<template #target="{ togglePopover }">
<Button :label="__('Sort')" @click="togglePopover()">
<template v-if="hideLabel">
<SortIcon class="h-4" />
</template>
<template v-if="!hideLabel && !sortValues?.size" #prefix>
<SortIcon class="h-4" />
</template>
</Button>
</template>
</Autocomplete>
<Popover placement="bottom-end" v-else>
<template #target="{ isOpen, togglePopover }">
<Button
v-if="sortValues.size > 1"
:label="__('Sort')"
:icon="hideLabel && SortIcon"
:iconLeft="!hideLabel && SortIcon"
@click="togglePopover"
>
<template v-if="sortValues?.size" #suffix>
<div
class="flex h-5 w-5 items-center justify-center rounded-[5px] bg-surface-white pt-px text-xs font-medium text-ink-gray-8 shadow-sm"
>
{{ sortValues.size }}
</div>
</template>
</Button>
<div v-else class="flex items-center justify-center">
<Button
v-if="sortValues.size"
class="rounded-r-none border-r"
:icon="
Array.from(sortValues)[0].direction == 'asc'
? AscendingIcon
: DesendingIcon
"
@click.stop="
() => {
Array.from(sortValues)[0].direction =
Array.from(sortValues)[0].direction == 'asc' ? 'desc' : 'asc'
apply()
}
"
/>
<Button
:label="getSortLabel()"
class="shrink-0 [&_svg]:text-ink-gray-5"
:iconLeft="!hideLabel && !sortValues?.size && SortIcon"
:iconRight="
sortValues?.size && (isOpen ? 'chevron-up' : 'chevron-down')
"
:class="sortValues.size ? 'rounded-l-none' : ''"
@click.stop="togglePopover"
/>
</div>
</template>
<template #body="{ close }">
<div
class="my-2 min-w-40 rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<div class="min-w-60 p-2">
<div
v-if="sortValues?.size"
id="sort-list"
class="mb-3 flex flex-col gap-2"
>
<div
v-for="(sort, i) in sortValues"
:key="sort.fieldname"
class="flex items-center gap-1"
>
<div class="handle flex h-7 w-7 items-center justify-center">
<DragIcon class="h-4 w-4 cursor-grab text-ink-gray-5" />
</div>
<div class="flex flex-1">
<Button
size="md"
class="rounded-r-none border-r"
:icon="
sort.direction == 'asc' ? AscendingIcon : DesendingIcon
"
@click="
() => {
sort.direction = sort.direction == 'asc' ? 'desc' : 'asc'
apply()
}
"
/>
<Autocomplete
class="[&>_div]:w-full"
:value="sort.fieldname"
:options="sortOptions.data"
@change="(e) => updateSort(e, i)"
:placeholder="__('First Name')"
>
<template
#target="{
open,
togglePopover,
selectedValue,
displayValue,
}"
>
<Button
class="flex w-full items-center justify-between rounded-l-none !text-ink-gray-5"
size="md"
:label="displayValue(selectedValue)"
:iconRight="open ? 'chevron-down' : 'chevron-up'"
@click="togglePopover()"
/>
</template>
</Autocomplete>
</div>
<Button variant="ghost" icon="x" @click="removeSort(i)" />
</div>
</div>
<div
v-else
class="mb-3 flex h-7 items-center px-3 text-sm text-ink-gray-5"
>
{{ __('Empty - Choose a field to sort by') }}
</div>
<div class="flex items-center justify-between gap-2">
<Autocomplete
:options="options"
value=""
:placeholder="__('First Name')"
@change="(e) => setSort(e)"
>
<template #target="{ togglePopover }">
<Button
class="!text-ink-gray-5"
:label="__('Add Sort')"
variant="ghost"
iconLeft="plus"
@click="togglePopover()"
/>
</template>
</Autocomplete>
<Button
v-if="sortValues?.size"
class="!text-ink-gray-5"
variant="ghost"
:label="__('Clear Sort')"
@click="clearSort(close)"
/>
</div>
</div>
</div>
</template>
</Popover>
</template>
<script setup>
import AscendingIcon from '@/components/Icons/AscendingIcon.vue'
import DesendingIcon from '@/components/Icons/DesendingIcon.vue'
import SortIcon from '@/components/Icons/SortIcon.vue'
import DragIcon from '@/components/Icons/DragIcon.vue'
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
import { useSortable } from '@vueuse/integrations/useSortable'
import { createResource, Popover } from 'frappe-ui'
import { computed, nextTick, onMounted } from 'vue'
const props = defineProps({
doctype: {
type: String,
required: true,
},
hideLabel: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update'])
const list = defineModel()
const sortOptions = createResource({
url: 'crm.api.doc.sort_options',
cache: ['sortOptions', props.doctype],
params: { doctype: props.doctype },
})
onMounted(() => {
if (sortOptions.data?.length) return
sortOptions.fetch()
})
const sortValues = computed({
get: () => {
if (!list.value?.data) return new Set()
let allSortValues = list.value?.params?.order_by
if (!allSortValues || !sortOptions.data) return new Set()
if (allSortValues.trim() === 'modified desc') return new Set()
allSortValues = allSortValues.split(', ').map((sortValue) => {
const [fieldname, direction] = sortValue.split(' ')
return { fieldname, direction }
})
return new Set(allSortValues)
},
set: (value) => {
list.value.params.order_by = convertToString(value)
},
})
const options = computed(() => {
if (!sortOptions.data) return []
if (!sortValues.value.size) return sortOptions.data
const selectedOptions = [...sortValues.value].map((sort) => sort.fieldname)
restartSort()
return sortOptions.data.filter((option) => {
return !selectedOptions.includes(option.fieldname)
})
})
const sortSortable = useSortable('#sort-list', sortValues, {
handle: '.handle',
animation: 200,
onEnd: () => apply(),
})
function getSortLabel() {
if (!sortValues.value.size) return __('Sort')
let values = Array.from(sortValues.value)
let label = sortOptions.data?.find(
(option) => option.fieldname === values[0].fieldname,
)?.label
return label || values[0].fieldname
}
function setSort(data) {
sortValues.value.add({ fieldname: data.fieldname, direction: 'asc' })
restartSort()
apply()
}
function updateSort(data, index) {
let oldSort = Array.from(sortValues.value)[index]
sortValues.value.delete(oldSort)
sortValues.value.add({
fieldname: data.fieldname,
direction: oldSort.direction,
})
apply()
}
function removeSort(index) {
sortValues.value.delete(Array.from(sortValues.value)[index])
apply()
}
function clearSort(close) {
sortValues.value.clear()
apply()
close()
}
function apply() {
nextTick(() => {
emit('update', convertToString(sortValues.value))
})
}
function convertToString(values) {
let _sortValues = ''
values.forEach((f) => {
_sortValues += `${f.fieldname} ${f.direction}, `
})
_sortValues = _sortValues.slice(0, -2)
return _sortValues
}
function restartSort() {
sortSortable.stop()
sortSortable.start()
}
</script>