Merge pull request #211 from shariquerik/UI-fixes
fix: UI fixes & PWA fixes
This commit is contained in:
commit
7f42beea69
@ -18,6 +18,7 @@
|
|||||||
<Draggable
|
<Draggable
|
||||||
:list="columns"
|
:list="columns"
|
||||||
@end="apply"
|
@end="apply"
|
||||||
|
:delay="isTouchScreenDevice() ? 200 : 0"
|
||||||
item-key="key"
|
item-key="key"
|
||||||
class="list-group"
|
class="list-group"
|
||||||
>
|
>
|
||||||
@ -101,14 +102,14 @@
|
|||||||
size="md"
|
size="md"
|
||||||
:label="__('Label')"
|
:label="__('Label')"
|
||||||
v-model="column.label"
|
v-model="column.label"
|
||||||
class="w-full"
|
class="sm:w-full w-52"
|
||||||
:placeholder="__('First Name')"
|
:placeholder="__('First Name')"
|
||||||
/>
|
/>
|
||||||
<FormControl
|
<FormControl
|
||||||
type="text"
|
type="text"
|
||||||
size="md"
|
size="md"
|
||||||
:label="__('Width')"
|
:label="__('Width')"
|
||||||
class="w-full"
|
class="sm:w-full w-52"
|
||||||
v-model="column.width"
|
v-model="column.width"
|
||||||
placeholder="10rem"
|
placeholder="10rem"
|
||||||
:description="
|
:description="
|
||||||
@ -147,6 +148,7 @@ import DragIcon from '@/components/Icons/DragIcon.vue'
|
|||||||
import ReloadIcon from '@/components/Icons/ReloadIcon.vue'
|
import ReloadIcon from '@/components/Icons/ReloadIcon.vue'
|
||||||
import NestedPopover from '@/components/NestedPopover.vue'
|
import NestedPopover from '@/components/NestedPopover.vue'
|
||||||
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
||||||
|
import { isTouchScreenDevice } from '@/utils'
|
||||||
import Draggable from 'vuedraggable'
|
import Draggable from 'vuedraggable'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { watchOnce } from '@vueuse/core'
|
import { watchOnce } from '@vueuse/core'
|
||||||
|
|||||||
@ -1,44 +1,54 @@
|
|||||||
<template>
|
<template>
|
||||||
<NestedPopover>
|
<NestedPopover>
|
||||||
<template #target>
|
<template #target>
|
||||||
<Button
|
<div class="flex items-center">
|
||||||
:label="__('Filter')"
|
<Button
|
||||||
:class="filters?.size ? 'rounded-r-none' : ''"
|
:label="__('Filter')"
|
||||||
>
|
:class="filters?.size ? 'rounded-r-none' : ''"
|
||||||
<template #prefix><FilterIcon class="h-4" /></template>
|
>
|
||||||
<template v-if="filters?.size" #suffix>
|
<template #prefix><FilterIcon class="h-4" /></template>
|
||||||
<div
|
<template v-if="filters?.size" #suffix>
|
||||||
class="flex h-5 w-5 items-center justify-center rounded bg-gray-900 pt-[1px] text-2xs font-medium text-white"
|
<div
|
||||||
>
|
class="flex h-5 w-5 items-center justify-center rounded bg-gray-900 pt-[1px] text-2xs font-medium text-white"
|
||||||
{{ filters.size }}
|
>
|
||||||
|
{{ filters.size }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
<Tooltip v-if="filters?.size" :text="__('Clear all Filter')">
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
class="rounded-l-none border-l"
|
||||||
|
icon="x"
|
||||||
|
@click.stop="clearfilter(false)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</Tooltip>
|
||||||
</Button>
|
</div>
|
||||||
<Tooltip v-if="filters?.size" :text="__('Clear all Filter')">
|
|
||||||
<span>
|
|
||||||
<Button
|
|
||||||
class="rounded-l-none border-l"
|
|
||||||
icon="x"
|
|
||||||
@click.stop="clearfilter(false)"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
</template>
|
</template>
|
||||||
<template #body="{ close }">
|
<template #body="{ close }">
|
||||||
<div class="my-2 rounded-lg border border-gray-100 bg-white shadow-xl">
|
<div class="my-2 rounded-lg border border-gray-100 bg-white shadow-xl">
|
||||||
<div class="min-w-[400px] p-2">
|
<div class="min-w-72 p-2 sm:min-w-[400px]">
|
||||||
<div
|
<div
|
||||||
v-if="filters?.size"
|
v-if="filters?.size"
|
||||||
v-for="(f, i) in filters"
|
v-for="(f, i) in filters"
|
||||||
:key="i"
|
:key="i"
|
||||||
id="filter-list"
|
id="filter-list"
|
||||||
class="mb-3 flex items-center justify-between gap-2"
|
class="sm:mb-3 mb-4"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div v-if="isMobileView" class="flex flex-col gap-2">
|
||||||
<div class="w-13 pl-2 text-end text-base text-gray-600">
|
<div class="flex w-full items-center justify-between -mb-2">
|
||||||
{{ i == 0 ? __('Where') : __('And') }}
|
<div class="text-base text-gray-600">
|
||||||
|
{{ i == 0 ? __('Where') : __('And') }}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
class="flex"
|
||||||
|
variant="ghost"
|
||||||
|
icon="x"
|
||||||
|
@click="removeFilter(i)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="fieldname" class="!min-w-[140px]">
|
<div id="fieldname" class="w-full">
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
:value="f.field.fieldname"
|
:value="f.field.fieldname"
|
||||||
:options="filterableFields.data"
|
:options="filterableFields.data"
|
||||||
@ -55,7 +65,7 @@
|
|||||||
:placeholder="__('Equals')"
|
:placeholder="__('Equals')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="value" class="!min-w-[140px]">
|
<div id="value" class="w-full">
|
||||||
<component
|
<component
|
||||||
:is="getValSelect(f)"
|
:is="getValSelect(f)"
|
||||||
v-model="f.value"
|
v-model="f.value"
|
||||||
@ -64,7 +74,46 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="ghost" icon="x" @click="removeFilter(i)" />
|
<div v-else class="flex items-center justify-between gap-2">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="w-13 pl-2 text-end text-base text-gray-600">
|
||||||
|
{{ i == 0 ? __('Where') : __('And') }}
|
||||||
|
</div>
|
||||||
|
<div id="fieldname" class="!min-w-[140px]">
|
||||||
|
<Autocomplete
|
||||||
|
:value="f.field.fieldname"
|
||||||
|
:options="filterableFields.data"
|
||||||
|
@change="(e) => updateFilter(e, i)"
|
||||||
|
:placeholder="__('First Name')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div id="operator">
|
||||||
|
<FormControl
|
||||||
|
type="select"
|
||||||
|
v-model="f.operator"
|
||||||
|
@change="(e) => updateOperator(e, f)"
|
||||||
|
:options="
|
||||||
|
getOperators(f.field.fieldtype, f.field.fieldname)
|
||||||
|
"
|
||||||
|
:placeholder="__('Equals')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div id="value" class="!min-w-[140px]">
|
||||||
|
<component
|
||||||
|
:is="getValSelect(f)"
|
||||||
|
v-model="f.value"
|
||||||
|
@change.stop="(v) => updateValue(v, f)"
|
||||||
|
:placeholder="__('John Doe')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
class="flex"
|
||||||
|
variant="ghost"
|
||||||
|
icon="x"
|
||||||
|
@click="removeFilter(i)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
@ -115,6 +164,7 @@ import Link from '@/components/Controls/Link.vue'
|
|||||||
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
||||||
import { FormControl, createResource, Tooltip } from 'frappe-ui'
|
import { FormControl, createResource, Tooltip } from 'frappe-ui'
|
||||||
import { h, computed, onMounted } from 'vue'
|
import { h, computed, onMounted } from 'vue'
|
||||||
|
import { isMobileView } from '@/composables/settings'
|
||||||
|
|
||||||
const typeCheck = ['Check']
|
const typeCheck = ['Check']
|
||||||
const typeLink = ['Link', 'Dynamic Link']
|
const typeLink = ['Link', 'Dynamic Link']
|
||||||
|
|||||||
20
frontend/src/components/Icons/AscendingIcon.vue
Normal file
20
frontend/src/components/Icons/AscendingIcon.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="lucide lucide-arrow-down-a-z"
|
||||||
|
>
|
||||||
|
<path d="m3 16 4 4 4-4" />
|
||||||
|
<path d="M7 20V4" />
|
||||||
|
<path d="M20 8h-5" />
|
||||||
|
<path d="M15 10V6.5a2.5 2.5 0 0 1 5 0V10" />
|
||||||
|
<path d="M15 14h5l-5 6h5" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
20
frontend/src/components/Icons/DesendingIcon.vue
Normal file
20
frontend/src/components/Icons/DesendingIcon.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="lucide lucide-arrow-up-z-a"
|
||||||
|
>
|
||||||
|
<path d="m3 8 4-4 4 4" />
|
||||||
|
<path d="M7 4v16" />
|
||||||
|
<path d="M15 4h5l-5 6h5" />
|
||||||
|
<path d="M15 20v-3.5a2.5 2.5 0 0 1 5 0V20" />
|
||||||
|
<path d="M20 18h-5" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
row-key="name"
|
row-key="name"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
>
|
>
|
||||||
<ListHeader class="mx-5" @columnWidthUpdated="emit('columnWidthUpdated')">
|
<ListHeader class="sm:mx-5 mx-3" @columnWidthUpdated="emit('columnWidthUpdated')">
|
||||||
<ListHeaderItem
|
<ListHeaderItem
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
:key="column.key"
|
:key="column.key"
|
||||||
@ -29,9 +29,8 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</ListHeaderItem>
|
</ListHeaderItem>
|
||||||
</ListHeader>
|
</ListHeader>
|
||||||
<ListRows id="list-rows">
|
<ListRows class="mx-3 sm:mx-5" id="list-rows">
|
||||||
<ListRow
|
<ListRow
|
||||||
class="mx-5"
|
|
||||||
v-for="row in rows"
|
v-for="row in rows"
|
||||||
:key="row.name"
|
:key="row.name"
|
||||||
v-slot="{ idx, column, item }"
|
v-slot="{ idx, column, item }"
|
||||||
@ -141,7 +140,7 @@
|
|||||||
</ListSelectBanner>
|
</ListSelectBanner>
|
||||||
</ListView>
|
</ListView>
|
||||||
<ListFooter
|
<ListFooter
|
||||||
class="border-t px-5 py-2"
|
class="border-t sm:px-5 px-3 py-2"
|
||||||
v-model="pageLengthCount"
|
v-model="pageLengthCount"
|
||||||
:options="{
|
:options="{
|
||||||
rowCount: options.rowCount,
|
rowCount: options.rowCount,
|
||||||
|
|||||||
@ -14,7 +14,10 @@
|
|||||||
}"
|
}"
|
||||||
row-key="name"
|
row-key="name"
|
||||||
>
|
>
|
||||||
<ListHeader class="mx-5" @columnWidthUpdated="emit('columnWidthUpdated')">
|
<ListHeader
|
||||||
|
class="mx-3 sm:mx-5"
|
||||||
|
@columnWidthUpdated="emit('columnWidthUpdated')"
|
||||||
|
>
|
||||||
<ListHeaderItem
|
<ListHeaderItem
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
:key="column.key"
|
:key="column.key"
|
||||||
@ -32,9 +35,8 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</ListHeaderItem>
|
</ListHeaderItem>
|
||||||
</ListHeader>
|
</ListHeader>
|
||||||
<ListRows id="list-rows">
|
<ListRows class="mx-3 sm:mx-5" id="list-rows">
|
||||||
<ListRow
|
<ListRow
|
||||||
class="mx-5"
|
|
||||||
v-for="row in rows"
|
v-for="row in rows"
|
||||||
:key="row.name"
|
:key="row.name"
|
||||||
v-slot="{ idx, column, item }"
|
v-slot="{ idx, column, item }"
|
||||||
@ -136,7 +138,7 @@
|
|||||||
</ListView>
|
</ListView>
|
||||||
<ListFooter
|
<ListFooter
|
||||||
v-if="pageLengthCount"
|
v-if="pageLengthCount"
|
||||||
class="border-t px-5 py-2"
|
class="border-t px-3 py-2 sm:px-5"
|
||||||
v-model="pageLengthCount"
|
v-model="pageLengthCount"
|
||||||
:options="{
|
:options="{
|
||||||
rowCount: options.rowCount,
|
rowCount: options.rowCount,
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
}"
|
}"
|
||||||
row-key="name"
|
row-key="name"
|
||||||
>
|
>
|
||||||
<ListHeader class="mx-5" @columnWidthUpdated="emit('columnWidthUpdated')">
|
<ListHeader class="sm:mx-5 mx-3" @columnWidthUpdated="emit('columnWidthUpdated')">
|
||||||
<ListHeaderItem
|
<ListHeaderItem
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
:key="column.key"
|
:key="column.key"
|
||||||
@ -173,7 +173,7 @@
|
|||||||
</ListView>
|
</ListView>
|
||||||
<ListFooter
|
<ListFooter
|
||||||
v-if="pageLengthCount"
|
v-if="pageLengthCount"
|
||||||
class="border-t px-5 py-2"
|
class="border-t sm:px-5 px-3 py-2"
|
||||||
v-model="pageLengthCount"
|
v-model="pageLengthCount"
|
||||||
:options="{
|
:options="{
|
||||||
rowCount: options.rowCount,
|
rowCount: options.rowCount,
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
}"
|
}"
|
||||||
row-key="name"
|
row-key="name"
|
||||||
>
|
>
|
||||||
<ListHeader class="mx-5" @columnWidthUpdated="emit('columnWidthUpdated')">
|
<ListHeader class="sm:mx-5 mx-3" @columnWidthUpdated="emit('columnWidthUpdated')">
|
||||||
<ListHeaderItem
|
<ListHeaderItem
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
:key="column.key"
|
:key="column.key"
|
||||||
@ -28,9 +28,8 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</ListHeaderItem>
|
</ListHeaderItem>
|
||||||
</ListHeader>
|
</ListHeader>
|
||||||
<ListRows id="list-rows">
|
<ListRows class="mx-3 sm:mx-5" id="list-rows">
|
||||||
<ListRow
|
<ListRow
|
||||||
class="mx-5"
|
|
||||||
v-for="row in rows"
|
v-for="row in rows"
|
||||||
:key="row.name"
|
:key="row.name"
|
||||||
v-slot="{ idx, column, item }"
|
v-slot="{ idx, column, item }"
|
||||||
@ -129,7 +128,7 @@
|
|||||||
</ListSelectBanner>
|
</ListSelectBanner>
|
||||||
</ListView>
|
</ListView>
|
||||||
<ListFooter
|
<ListFooter
|
||||||
class="border-t px-5 py-2"
|
class="border-t sm:px-5 px-3 py-2"
|
||||||
v-model="pageLengthCount"
|
v-model="pageLengthCount"
|
||||||
:options="{
|
:options="{
|
||||||
rowCount: options.rowCount,
|
rowCount: options.rowCount,
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
}"
|
}"
|
||||||
row-key="name"
|
row-key="name"
|
||||||
>
|
>
|
||||||
<ListHeader class="mx-5" @columnWidthUpdated="emit('columnWidthUpdated')">
|
<ListHeader class="sm:mx-5 mx-3" @columnWidthUpdated="emit('columnWidthUpdated')">
|
||||||
<ListHeaderItem
|
<ListHeaderItem
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
:key="column.key"
|
:key="column.key"
|
||||||
@ -186,7 +186,7 @@
|
|||||||
</ListView>
|
</ListView>
|
||||||
<ListFooter
|
<ListFooter
|
||||||
v-if="pageLengthCount"
|
v-if="pageLengthCount"
|
||||||
class="border-t px-5 py-2"
|
class="border-t sm:px-5 px-3 py-2"
|
||||||
v-model="pageLengthCount"
|
v-model="pageLengthCount"
|
||||||
:options="{
|
:options="{
|
||||||
rowCount: options.rowCount,
|
rowCount: options.rowCount,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mx-5 mt-2 h-full overflow-y-auto" v-if="showGroupedRows">
|
<div class="mx-3 mt-2 h-full overflow-y-auto sm:mx-5" v-if="showGroupedRows">
|
||||||
<div v-for="group in reactivieRows" :key="group.group">
|
<div v-for="group in reactivieRows" :key="group.group">
|
||||||
<ListGroupHeader :group="group">
|
<ListGroupHeader :group="group">
|
||||||
<div
|
<div
|
||||||
@ -27,7 +27,7 @@
|
|||||||
</ListGroupRows>
|
</ListGroupRows>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ListRows class="mx-5" v-else id="list-rows">
|
<ListRows class="mx-3 sm:mx-5" v-else id="list-rows">
|
||||||
<ListRow
|
<ListRow
|
||||||
v-for="row in reactivieRows"
|
v-for="row in reactivieRows"
|
||||||
:key="row.name"
|
:key="row.name"
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
}"
|
}"
|
||||||
row-key="name"
|
row-key="name"
|
||||||
>
|
>
|
||||||
<ListHeader class="mx-5" @columnWidthUpdated="emit('columnWidthUpdated')">
|
<ListHeader class="sm:mx-5 mx-3" @columnWidthUpdated="emit('columnWidthUpdated')">
|
||||||
<ListHeaderItem
|
<ListHeaderItem
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
:key="column.key"
|
:key="column.key"
|
||||||
@ -31,9 +31,8 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</ListHeaderItem>
|
</ListHeaderItem>
|
||||||
</ListHeader>
|
</ListHeader>
|
||||||
<ListRows id="list-rows">
|
<ListRows class="mx-3 sm:mx-5" id="list-rows">
|
||||||
<ListRow
|
<ListRow
|
||||||
class="mx-5"
|
|
||||||
v-for="row in rows"
|
v-for="row in rows"
|
||||||
:key="row.name"
|
:key="row.name"
|
||||||
v-slot="{ idx, column, item }"
|
v-slot="{ idx, column, item }"
|
||||||
@ -122,7 +121,7 @@
|
|||||||
</ListSelectBanner>
|
</ListSelectBanner>
|
||||||
</ListView>
|
</ListView>
|
||||||
<ListFooter
|
<ListFooter
|
||||||
class="border-t px-5 py-2"
|
class="border-t sm:px-5 px-3 py-2"
|
||||||
v-model="pageLengthCount"
|
v-model="pageLengthCount"
|
||||||
:options="{
|
:options="{
|
||||||
rowCount: options.rowCount,
|
rowCount: options.rowCount,
|
||||||
|
|||||||
@ -10,7 +10,10 @@
|
|||||||
}"
|
}"
|
||||||
row-key="name"
|
row-key="name"
|
||||||
>
|
>
|
||||||
<ListHeader class="mx-5" @columnWidthUpdated="emit('columnWidthUpdated')">
|
<ListHeader
|
||||||
|
class="mx-3 sm:mx-5"
|
||||||
|
@columnWidthUpdated="emit('columnWidthUpdated')"
|
||||||
|
>
|
||||||
<ListHeaderItem
|
<ListHeaderItem
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
:key="column.key"
|
:key="column.key"
|
||||||
@ -28,9 +31,8 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</ListHeaderItem>
|
</ListHeaderItem>
|
||||||
</ListHeader>
|
</ListHeader>
|
||||||
<ListRows id="list-rows">
|
<ListRows class="mx-3 sm:mx-5" id="list-rows">
|
||||||
<ListRow
|
<ListRow
|
||||||
class="mx-5"
|
|
||||||
v-for="row in rows"
|
v-for="row in rows"
|
||||||
:key="row.name"
|
:key="row.name"
|
||||||
v-slot="{ idx, column, item }"
|
v-slot="{ idx, column, item }"
|
||||||
@ -135,7 +137,7 @@
|
|||||||
</ListSelectBanner>
|
</ListSelectBanner>
|
||||||
</ListView>
|
</ListView>
|
||||||
<ListFooter
|
<ListFooter
|
||||||
class="border-t px-5 py-2"
|
class="border-t px-3 py-2 sm:px-5"
|
||||||
v-model="pageLengthCount"
|
v-model="pageLengthCount"
|
||||||
:options="{
|
:options="{
|
||||||
rowCount: options.rowCount,
|
rowCount: options.rowCount,
|
||||||
|
|||||||
@ -1,7 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<NestedPopover>
|
<Autocomplete
|
||||||
<template #target>
|
v-if="!sortValues?.size"
|
||||||
<Button :label="__('Sort')" ref="sortButtonRef">
|
: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>
|
||||||
|
<NestedPopover v-else>
|
||||||
|
<template #target="{ open }">
|
||||||
|
<Button v-if="sortValues.size > 1" :label="__('Sort')">
|
||||||
<template v-if="hideLabel">
|
<template v-if="hideLabel">
|
||||||
<SortIcon class="h-4" />
|
<SortIcon class="h-4" />
|
||||||
</template>
|
</template>
|
||||||
@ -14,10 +32,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
|
<div v-else class="flex items-center justify-center">
|
||||||
|
<Button
|
||||||
|
v-if="sortValues.size"
|
||||||
|
class="rounded-r-none border-r"
|
||||||
|
@click.stop="
|
||||||
|
() => {
|
||||||
|
Array.from(sortValues)[0].direction =
|
||||||
|
Array.from(sortValues)[0].direction == 'asc' ? 'desc' : 'asc'
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<AscendingIcon
|
||||||
|
v-if="Array.from(sortValues)[0].direction == 'asc'"
|
||||||
|
class="h-4"
|
||||||
|
/>
|
||||||
|
<DesendingIcon v-else class="h-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
:label="getSortLabel()"
|
||||||
|
:class="sortValues.size ? 'rounded-l-none' : ''"
|
||||||
|
>
|
||||||
|
<template v-if="!hideLabel && !sortValues?.size" #prefix>
|
||||||
|
<SortIcon class="h-4" />
|
||||||
|
</template>
|
||||||
|
<template v-if="sortValues?.size" #suffix>
|
||||||
|
<FeatherIcon
|
||||||
|
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||||
|
class="h-4 text-gray-600"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #body="{ close }">
|
<template #body="{ close }">
|
||||||
<div class="my-2 rounded-lg border border-gray-100 bg-white shadow-xl">
|
<div class="my-2 rounded-lg border border-gray-100 bg-white shadow-xl">
|
||||||
<div class="min-w-[352px] p-2">
|
<div class="min-w-60 p-2">
|
||||||
<div
|
<div
|
||||||
v-if="sortValues?.size"
|
v-if="sortValues?.size"
|
||||||
id="sort-list"
|
id="sort-list"
|
||||||
@ -26,34 +77,51 @@
|
|||||||
<div
|
<div
|
||||||
v-for="(sort, i) in sortValues"
|
v-for="(sort, i) in sortValues"
|
||||||
:key="sort.fieldname"
|
:key="sort.fieldname"
|
||||||
class="flex items-center gap-2"
|
class="flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<div class="handle flex h-7 w-7 items-center justify-center">
|
<div class="handle flex h-7 w-7 items-center justify-center">
|
||||||
<DragIcon class="h-4 w-4 cursor-grab text-gray-600" />
|
<DragIcon class="h-4 w-4 cursor-grab text-gray-600" />
|
||||||
</div>
|
</div>
|
||||||
<Autocomplete
|
<div class="flex">
|
||||||
class="!w-32"
|
<Button
|
||||||
:value="sort.fieldname"
|
size="md"
|
||||||
:options="sortOptions.data"
|
class="rounded-r-none border-r"
|
||||||
@change="(e) => updateSort(e, i)"
|
@click="
|
||||||
:placeholder="__('First Name')"
|
() => {
|
||||||
/>
|
sort.direction = sort.direction == 'asc' ? 'desc' : 'asc'
|
||||||
<FormControl
|
apply()
|
||||||
class="!w-32"
|
}
|
||||||
type="select"
|
"
|
||||||
v-model="sort.direction"
|
>
|
||||||
:options="[
|
<AscendingIcon v-if="sort.direction == 'asc'" class="h-4" />
|
||||||
{ label: __('Ascending'), value: 'asc' },
|
<DesendingIcon v-else class="h-4" />
|
||||||
{ label: __('Descending'), value: 'desc' },
|
</Button>
|
||||||
]"
|
<Autocomplete
|
||||||
@change="
|
class="!w-32"
|
||||||
(e) => {
|
:value="sort.fieldname"
|
||||||
sort.direction = e.target.value
|
:options="sortOptions.data"
|
||||||
apply()
|
@change="(e) => updateSort(e, i)"
|
||||||
}
|
:placeholder="__('First Name')"
|
||||||
"
|
>
|
||||||
:placeholder="__('Ascending')"
|
<template
|
||||||
/>
|
#target="{ togglePopover, selectedValue, displayValue }"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
class="flex w-full items-center justify-between rounded-l-none !text-gray-600"
|
||||||
|
size="md"
|
||||||
|
@click="togglePopover()"
|
||||||
|
>
|
||||||
|
{{ displayValue(selectedValue) }}
|
||||||
|
<template #suffix>
|
||||||
|
<FeatherIcon
|
||||||
|
name="chevron-down"
|
||||||
|
class="h-4 text-gray-600"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Autocomplete>
|
||||||
|
</div>
|
||||||
<Button variant="ghost" icon="x" @click="removeSort(i)" />
|
<Button variant="ghost" icon="x" @click="removeSort(i)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -98,13 +166,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import AscendingIcon from '@/components/Icons/AscendingIcon.vue'
|
||||||
|
import DesendingIcon from '@/components/Icons/DesendingIcon.vue'
|
||||||
import NestedPopover from '@/components/NestedPopover.vue'
|
import NestedPopover from '@/components/NestedPopover.vue'
|
||||||
import SortIcon from '@/components/Icons/SortIcon.vue'
|
import SortIcon from '@/components/Icons/SortIcon.vue'
|
||||||
import DragIcon from '@/components/Icons/DragIcon.vue'
|
import DragIcon from '@/components/Icons/DragIcon.vue'
|
||||||
import { useSortable } from '@vueuse/integrations/useSortable'
|
|
||||||
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
||||||
|
import { useSortable } from '@vueuse/integrations/useSortable'
|
||||||
import { createResource } from 'frappe-ui'
|
import { createResource } from 'frappe-ui'
|
||||||
import { computed, ref, nextTick, onMounted } from 'vue'
|
import { computed, nextTick, onMounted } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
doctype: {
|
doctype: {
|
||||||
@ -120,8 +190,6 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['update'])
|
const emit = defineEmits(['update'])
|
||||||
const list = defineModel()
|
const list = defineModel()
|
||||||
|
|
||||||
const sortButtonRef = ref(null)
|
|
||||||
|
|
||||||
const sortOptions = createResource({
|
const sortOptions = createResource({
|
||||||
url: 'crm.api.doc.sort_options',
|
url: 'crm.api.doc.sort_options',
|
||||||
cache: ['sortOptions', props.doctype],
|
cache: ['sortOptions', props.doctype],
|
||||||
@ -168,6 +236,16 @@ const sortSortable = useSortable('#sort-list', sortValues, {
|
|||||||
onEnd: () => apply(),
|
onEnd: () => apply(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function getSortLabel() {
|
||||||
|
if (!sortValues.value.size) return __('Sort')
|
||||||
|
let values = Array.from(sortValues.value)
|
||||||
|
let label = sortOptions.data?.find(
|
||||||
|
(option) => option.value === values[0].fieldname
|
||||||
|
)?.label
|
||||||
|
|
||||||
|
return label || sort.fieldname
|
||||||
|
}
|
||||||
|
|
||||||
function setSort(data) {
|
function setSort(data) {
|
||||||
sortValues.value.add({ fieldname: data.value, direction: 'asc' })
|
sortValues.value.add({ fieldname: data.value, direction: 'asc' })
|
||||||
restartSort()
|
restartSort()
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="isMobileView"
|
v-if="isMobileView"
|
||||||
class="flex flex-col justify-between gap-2 px-5 py-4"
|
class="flex flex-col justify-between gap-2 sm:px-5 px-3 py-4"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2 overflow-x-auto">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Dropdown :options="viewsDropdownOptions">
|
<Dropdown :options="viewsDropdownOptions">
|
||||||
<template #default="{ open }">
|
<template #default="{ open }">
|
||||||
@ -41,15 +41,14 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2 overflow-x-auto">
|
||||||
<Filter
|
|
||||||
v-model="list"
|
|
||||||
:doctype="doctype"
|
|
||||||
:default_filters="filters"
|
|
||||||
@update="updateFilter"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
|
<Filter
|
||||||
|
v-model="list"
|
||||||
|
:doctype="doctype"
|
||||||
|
:default_filters="filters"
|
||||||
|
@update="updateFilter"
|
||||||
|
/>
|
||||||
<GroupBy
|
<GroupBy
|
||||||
v-if="route.params.viewType === 'group_by'"
|
v-if="route.params.viewType === 'group_by'"
|
||||||
v-model="list"
|
v-model="list"
|
||||||
@ -57,6 +56,9 @@
|
|||||||
:hideLabel="isMobileView"
|
:hideLabel="isMobileView"
|
||||||
@update="updateGroupBy"
|
@update="updateGroupBy"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
<SortBy
|
<SortBy
|
||||||
v-model="list"
|
v-model="list"
|
||||||
:doctype="doctype"
|
:doctype="doctype"
|
||||||
|
|||||||
@ -2,7 +2,16 @@
|
|||||||
<Combobox v-model="selectedValue" nullable v-slot="{ open: isComboboxOpen }">
|
<Combobox v-model="selectedValue" nullable v-slot="{ open: isComboboxOpen }">
|
||||||
<Popover class="w-full" v-model:show="showOptions">
|
<Popover class="w-full" v-model:show="showOptions">
|
||||||
<template #target="{ open: openPopover, togglePopover }">
|
<template #target="{ open: openPopover, togglePopover }">
|
||||||
<slot name="target" v-bind="{ open: openPopover, togglePopover, isOpen: showOptions }">
|
<slot
|
||||||
|
name="target"
|
||||||
|
v-bind="{
|
||||||
|
open: openPopover,
|
||||||
|
togglePopover,
|
||||||
|
isOpen: showOptions,
|
||||||
|
selectedValue,
|
||||||
|
displayValue,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<button
|
<button
|
||||||
class="flex w-full items-center justify-between focus:outline-none"
|
class="flex w-full items-center justify-between focus:outline-none"
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
<div class="flex-1 overflow-y-auto">
|
<div class="flex-1 overflow-y-auto">
|
||||||
<div
|
<div
|
||||||
v-if="notes.data?.data?.length"
|
v-if="notes.data?.data?.length"
|
||||||
class="grid sm:grid-cols-4 grid-cols-1 gap-4 px-5 pb-3"
|
class="grid grid-cols-1 gap-2 px-3 pb-2 sm:grid-cols-4 sm:gap-4 sm:px-5 sm:pb-3"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="note in notes.data.data"
|
v-for="note in notes.data.data"
|
||||||
@ -75,7 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ListFooter
|
<ListFooter
|
||||||
v-if="notes.data?.data?.length"
|
v-if="notes.data?.data?.length"
|
||||||
class="border-t px-5 py-2"
|
class="border-t px-3 py-2 sm:px-5"
|
||||||
v-model="notes.data.page_length_count"
|
v-model="notes.data.page_length_count"
|
||||||
:options="{
|
:options="{
|
||||||
rowCount: notes.data.row_count,
|
rowCount: notes.data.row_count,
|
||||||
|
|||||||
@ -212,3 +212,7 @@ export function isEmoji(str) {
|
|||||||
const emojiList = gemoji.map((emoji) => emoji.emoji)
|
const emojiList = gemoji.map((emoji) => emoji.emoji)
|
||||||
return emojiList.includes(str)
|
return emojiList.includes(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isTouchScreenDevice() {
|
||||||
|
return "ontouchstart" in document.documentElement;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user