Merge pull request #62 from shariquerik/datetime-in-filter
fix: Allow more fieldtypes in filter
This commit is contained in:
commit
6ffb2e8b37
@ -40,12 +40,16 @@ def get_filterable_fields(doctype: str):
|
||||
"Data",
|
||||
"Float",
|
||||
"Int",
|
||||
"Currency",
|
||||
"Link",
|
||||
"Long Text",
|
||||
"Select",
|
||||
"Small Text",
|
||||
"Text Editor",
|
||||
"Text",
|
||||
"Duration",
|
||||
"Date",
|
||||
"Datetime",
|
||||
]
|
||||
|
||||
c = get_controller(doctype)
|
||||
@ -84,6 +88,8 @@ def get_filterable_fields(doctype: str):
|
||||
{"fieldname": "_liked_by", "fieldtype": "Data", "label": "Liked By"},
|
||||
{"fieldname": "_comments", "fieldtype": "Text", "label": "Comments"},
|
||||
{"fieldname": "_assign", "fieldtype": "Text", "label": "Assigned To"},
|
||||
{"fieldname": "creation", "fieldtype": "Datetime", "label": "Created On"},
|
||||
{"fieldname": "modified", "fieldtype": "Datetime", "label": "Last Updated On"},
|
||||
]
|
||||
for field in standard_fields:
|
||||
if (
|
||||
|
||||
@ -134,15 +134,13 @@
|
||||
"fieldname": "no_of_employees",
|
||||
"fieldtype": "Select",
|
||||
"label": "No. of Employees",
|
||||
"options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+",
|
||||
"read_only": 1
|
||||
"options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
|
||||
},
|
||||
{
|
||||
"fetch_from": "organization.annual_revenue",
|
||||
"fieldname": "annual_revenue",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Annual Revenue",
|
||||
"read_only": 1
|
||||
"label": "Annual Revenue"
|
||||
},
|
||||
{
|
||||
"fieldname": "lead_owner",
|
||||
@ -294,7 +292,7 @@
|
||||
"image_field": "image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-22 14:33:21.980634",
|
||||
"modified": "2024-01-28 18:35:02.604536",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "CRM Lead",
|
||||
|
||||
265
frontend/src/components/Controls/DatePicker.vue
Normal file
265
frontend/src/components/Controls/DatePicker.vue
Normal file
@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<Popover
|
||||
@open="selectCurrentMonthYear"
|
||||
class="flex w-full [&>div:first-child]:w-full"
|
||||
>
|
||||
<template #target="{ togglePopover }">
|
||||
<input
|
||||
readonly
|
||||
type="text"
|
||||
:placeholder="placeholder"
|
||||
:value="value && formatter ? formatter(value) : value"
|
||||
@focus="!readonly ? togglePopover() : null"
|
||||
:class="[
|
||||
'form-input block h-7 w-full cursor-pointer select-none rounded border-gray-400 text-sm placeholder-gray-500',
|
||||
inputClass,
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #body="{ togglePopover }">
|
||||
<div
|
||||
class="my-2 w-fit select-none space-y-3 rounded border border-gray-50 bg-white p-3 text-base shadow"
|
||||
>
|
||||
<div class="flex items-center text-gray-700">
|
||||
<div
|
||||
class="flex h-6 w-6 cursor-pointer items-center justify-center rounded hover:bg-gray-100"
|
||||
>
|
||||
<FeatherIcon
|
||||
@click="prevMonth"
|
||||
name="chevron-left"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 text-center text-lg font-medium text-blue-500">
|
||||
{{ formatMonth }}
|
||||
</div>
|
||||
<div
|
||||
class="flex h-6 w-6 cursor-pointer items-center justify-center rounded hover:bg-gray-100"
|
||||
>
|
||||
<FeatherIcon
|
||||
@click="nextMonth"
|
||||
name="chevron-right"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<Input
|
||||
type="text"
|
||||
:value="value"
|
||||
@change="selectDate(getDate($event)) || togglePopover()"
|
||||
></Input>
|
||||
<Button class="h-7" @click="selectDate(getDate()) || togglePopover()">
|
||||
Today
|
||||
</Button>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-col items-center justify-center text-base">
|
||||
<div class="flex w-full items-center space-x-1 text-gray-600">
|
||||
<div
|
||||
class="flex h-[30px] w-[30px] items-center justify-center text-center"
|
||||
v-for="(d, i) in ['S', 'M', 'T', 'W', 'T', 'F', 'S']"
|
||||
:key="i"
|
||||
>
|
||||
{{ d }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(week, i) in datesAsWeeks" :key="i" class="mt-1">
|
||||
<div class="flex w-full items-center">
|
||||
<div
|
||||
v-for="date in week"
|
||||
:key="toValue(date)"
|
||||
class="mr-1 flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded last:mr-0 hover:bg-blue-50 hover:text-blue-500"
|
||||
:class="{
|
||||
'text-gray-600': date.getMonth() !== currentMonth - 1,
|
||||
'text-blue-500': toValue(date) === toValue(today),
|
||||
'bg-blue-50 font-semibold text-blue-500':
|
||||
toValue(date) === value,
|
||||
}"
|
||||
@click="
|
||||
() => {
|
||||
selectDate(date)
|
||||
togglePopover()
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ date.getDate() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-1 flex w-full justify-end">
|
||||
<div
|
||||
class="cursor-pointer rounded px-2 py-1 hover:bg-gray-100"
|
||||
@click="
|
||||
() => {
|
||||
selectDate('')
|
||||
togglePopover()
|
||||
}
|
||||
"
|
||||
>
|
||||
Clear
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Popover } from 'frappe-ui'
|
||||
export default {
|
||||
name: 'DatePicker',
|
||||
props: ['value', 'placeholder', 'formatter', 'readonly', 'inputClass'],
|
||||
emits: ['change'],
|
||||
components: {
|
||||
Popover,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentYear: null,
|
||||
currentMonth: null,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.selectCurrentMonthYear()
|
||||
},
|
||||
computed: {
|
||||
today() {
|
||||
return this.getDate()
|
||||
},
|
||||
datesAsWeeks() {
|
||||
let datesAsWeeks = []
|
||||
let dates = this.dates.slice()
|
||||
while (dates.length) {
|
||||
let week = dates.splice(0, 7)
|
||||
datesAsWeeks.push(week)
|
||||
}
|
||||
return datesAsWeeks
|
||||
},
|
||||
dates() {
|
||||
if (!(this.currentYear && this.currentMonth)) {
|
||||
return []
|
||||
}
|
||||
let monthIndex = this.currentMonth - 1
|
||||
let year = this.currentYear
|
||||
|
||||
let firstDayOfMonth = this.getDate(year, monthIndex, 1)
|
||||
let lastDayOfMonth = this.getDate(year, monthIndex + 1, 0)
|
||||
let leftPaddingCount = firstDayOfMonth.getDay()
|
||||
let rightPaddingCount = 6 - lastDayOfMonth.getDay()
|
||||
|
||||
let leftPadding = this.getDatesAfter(firstDayOfMonth, -leftPaddingCount)
|
||||
let rightPadding = this.getDatesAfter(lastDayOfMonth, rightPaddingCount)
|
||||
let daysInMonth = this.getDaysInMonth(monthIndex, year)
|
||||
let datesInMonth = this.getDatesAfter(firstDayOfMonth, daysInMonth - 1)
|
||||
|
||||
let dates = [
|
||||
...leftPadding,
|
||||
firstDayOfMonth,
|
||||
...datesInMonth,
|
||||
...rightPadding,
|
||||
]
|
||||
if (dates.length < 42) {
|
||||
const finalPadding = this.getDatesAfter(dates.at(-1), 42 - dates.length)
|
||||
dates = dates.concat(...finalPadding)
|
||||
}
|
||||
return dates
|
||||
},
|
||||
formatMonth() {
|
||||
let date = this.getDate(this.currentYear, this.currentMonth - 1, 1)
|
||||
return date.toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
})
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
selectDate(date) {
|
||||
this.$emit('change', this.toValue(date))
|
||||
},
|
||||
selectCurrentMonthYear() {
|
||||
let date = this.value ? this.getDate(this.value) : this.getDate()
|
||||
if (date === 'Invalid Date') {
|
||||
date = this.getDate()
|
||||
}
|
||||
this.currentYear = date.getFullYear()
|
||||
this.currentMonth = date.getMonth() + 1
|
||||
},
|
||||
prevMonth() {
|
||||
this.changeMonth(-1)
|
||||
},
|
||||
nextMonth() {
|
||||
this.changeMonth(1)
|
||||
},
|
||||
changeMonth(adder) {
|
||||
this.currentMonth = this.currentMonth + adder
|
||||
if (this.currentMonth < 1) {
|
||||
this.currentMonth = 12
|
||||
this.currentYear = this.currentYear - 1
|
||||
}
|
||||
if (this.currentMonth > 12) {
|
||||
this.currentMonth = 1
|
||||
this.currentYear = this.currentYear + 1
|
||||
}
|
||||
},
|
||||
getDatesAfter(date, count) {
|
||||
let incrementer = 1
|
||||
if (count < 0) {
|
||||
incrementer = -1
|
||||
count = Math.abs(count)
|
||||
}
|
||||
let dates = []
|
||||
while (count) {
|
||||
date = this.getDate(
|
||||
date.getFullYear(),
|
||||
date.getMonth(),
|
||||
date.getDate() + incrementer
|
||||
)
|
||||
dates.push(date)
|
||||
count--
|
||||
}
|
||||
if (incrementer === -1) {
|
||||
return dates.reverse()
|
||||
}
|
||||
return dates
|
||||
},
|
||||
|
||||
getDaysInMonth(monthIndex, year) {
|
||||
let daysInMonthMap = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||
let daysInMonth = daysInMonthMap[monthIndex]
|
||||
if (monthIndex === 1 && this.isLeapYear(year)) {
|
||||
return 29
|
||||
}
|
||||
return daysInMonth
|
||||
},
|
||||
|
||||
isLeapYear(year) {
|
||||
if (year % 400 === 0) return true
|
||||
if (year % 100 === 0) return false
|
||||
if (year % 4 === 0) return true
|
||||
return false
|
||||
},
|
||||
|
||||
toValue(date) {
|
||||
if (!date) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// toISOString is buggy and reduces the day by one
|
||||
// this is because it considers the UTC timestamp
|
||||
// in order to circumvent that we need to use luxon/moment
|
||||
// but that refactor could take some time, so fixing the time difference
|
||||
// as suggested in this answer.
|
||||
// https://stackoverflow.com/a/16084846/3541205
|
||||
date.setHours(0, -date.getTimezoneOffset(), 0, 0)
|
||||
return date.toISOString().slice(0, 10)
|
||||
},
|
||||
|
||||
getDate(...args) {
|
||||
let d = new Date(...args)
|
||||
return d
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
313
frontend/src/components/Controls/DateRangePicker.vue
Normal file
313
frontend/src/components/Controls/DateRangePicker.vue
Normal file
@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<Popover
|
||||
@open="selectCurrentMonthYear"
|
||||
class="flex w-full [&>div:first-child]:w-full"
|
||||
>
|
||||
<template #target="{ togglePopover }">
|
||||
<input
|
||||
readonly
|
||||
type="text"
|
||||
:placeholder="placeholder"
|
||||
:value="formatter ? formatDates(value) : value"
|
||||
@focus="!readonly ? togglePopover() : null"
|
||||
:class="[
|
||||
'form-input block h-7 w-full cursor-pointer select-none rounded border-gray-400 text-sm placeholder-gray-500 ',
|
||||
inputClass,
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #body="{ togglePopover }">
|
||||
<div
|
||||
class="my-2 w-[16rem] select-none space-y-3 rounded border border-gray-50 bg-white p-3 text-base shadow"
|
||||
>
|
||||
<div class="flex items-center text-gray-700">
|
||||
<div
|
||||
class="flex h-6 w-6 cursor-pointer items-center justify-center rounded hover:bg-gray-100"
|
||||
>
|
||||
<FeatherIcon
|
||||
@click="prevMonth"
|
||||
name="chevron-left"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 text-center text-lg font-medium text-blue-500">
|
||||
{{ formatMonth }}
|
||||
</div>
|
||||
<div
|
||||
class="flex h-6 w-6 cursor-pointer items-center justify-center rounded hover:bg-gray-100"
|
||||
>
|
||||
<FeatherIcon
|
||||
@click="nextMonth"
|
||||
name="chevron-right"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<Input type="text" v-model="fromDate"></Input>
|
||||
<Input type="text" v-model="toDate"></Input>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-col items-center justify-center text-base">
|
||||
<div
|
||||
class="flex w-full items-center justify-center space-x-1 text-gray-600"
|
||||
>
|
||||
<div
|
||||
class="flex h-[30px] w-[30px] items-center justify-center text-center"
|
||||
v-for="(d, i) in ['S', 'M', 'T', 'W', 'T', 'F', 'S']"
|
||||
:key="i"
|
||||
>
|
||||
{{ d }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(week, i) in datesAsWeeks" :key="i">
|
||||
<div class="flex w-full items-center">
|
||||
<div
|
||||
v-for="date in week"
|
||||
:key="toValue(date)"
|
||||
class="flex h-[30px] w-[30px] cursor-pointer items-center justify-center hover:bg-blue-50 hover:text-blue-500"
|
||||
:class="{
|
||||
'text-gray-600': date.getMonth() !== currentMonth - 1,
|
||||
'text-blue-500': toValue(date) === toValue(today),
|
||||
'bg-blue-50 text-blue-500': isInRange(date),
|
||||
'rounded-l-md bg-blue-100':
|
||||
fromDate && toValue(date) === toValue(fromDate),
|
||||
'rounded-r-md bg-blue-100':
|
||||
toDate && toValue(date) === toValue(toDate),
|
||||
}"
|
||||
@click="() => handleDateClick(date)"
|
||||
>
|
||||
{{ date.getDate() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-1 flex w-full justify-end space-x-2">
|
||||
<Button @click="() => clearDates()" :disabled="!value">
|
||||
Clear
|
||||
</Button>
|
||||
<Button
|
||||
@click="() => selectDates() | togglePopover()"
|
||||
:disabled="!fromDate || !toDate"
|
||||
variant="solid"
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Popover } from 'frappe-ui'
|
||||
export default {
|
||||
name: 'DateRangePicker',
|
||||
props: ['value', 'placeholder', 'formatter', 'readonly', 'inputClass'],
|
||||
emits: ['change'],
|
||||
components: {
|
||||
Popover,
|
||||
},
|
||||
data() {
|
||||
const fromDate = this.value ? this.value.split(',')[0] : ''
|
||||
const toDate = this.value ? this.value.split(',')[1] : ''
|
||||
return {
|
||||
currentYear: null,
|
||||
currentMonth: null,
|
||||
fromDate,
|
||||
toDate,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.selectCurrentMonthYear()
|
||||
},
|
||||
computed: {
|
||||
today() {
|
||||
return this.getDate()
|
||||
},
|
||||
datesAsWeeks() {
|
||||
let datesAsWeeks = []
|
||||
let dates = this.dates.slice()
|
||||
while (dates.length) {
|
||||
let week = dates.splice(0, 7)
|
||||
datesAsWeeks.push(week)
|
||||
}
|
||||
return datesAsWeeks
|
||||
},
|
||||
dates() {
|
||||
if (!(this.currentYear && this.currentMonth)) {
|
||||
return []
|
||||
}
|
||||
let monthIndex = this.currentMonth - 1
|
||||
let year = this.currentYear
|
||||
|
||||
let firstDayOfMonth = this.getDate(year, monthIndex, 1)
|
||||
let lastDayOfMonth = this.getDate(year, monthIndex + 1, 0)
|
||||
let leftPaddingCount = firstDayOfMonth.getDay()
|
||||
let rightPaddingCount = 6 - lastDayOfMonth.getDay()
|
||||
|
||||
let leftPadding = this.getDatesAfter(firstDayOfMonth, -leftPaddingCount)
|
||||
let rightPadding = this.getDatesAfter(lastDayOfMonth, rightPaddingCount)
|
||||
let daysInMonth = this.getDaysInMonth(monthIndex, year)
|
||||
let datesInMonth = this.getDatesAfter(firstDayOfMonth, daysInMonth - 1)
|
||||
|
||||
let dates = [
|
||||
...leftPadding,
|
||||
firstDayOfMonth,
|
||||
...datesInMonth,
|
||||
...rightPadding,
|
||||
]
|
||||
if (dates.length < 42) {
|
||||
const finalPadding = this.getDatesAfter(dates.at(-1), 42 - dates.length)
|
||||
dates = dates.concat(...finalPadding)
|
||||
}
|
||||
return dates
|
||||
},
|
||||
formatMonth() {
|
||||
let date = this.getDate(this.currentYear, this.currentMonth - 1, 1)
|
||||
return date.toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
})
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleDateClick(date) {
|
||||
if (this.fromDate && this.toDate) {
|
||||
this.fromDate = this.toValue(date)
|
||||
this.toDate = ''
|
||||
} else if (this.fromDate && !this.toDate) {
|
||||
this.toDate = this.toValue(date)
|
||||
} else {
|
||||
this.fromDate = this.toValue(date)
|
||||
}
|
||||
this.swapDatesIfNecessary()
|
||||
},
|
||||
selectDates() {
|
||||
if (!this.fromDate && !this.toDate) {
|
||||
return this.$emit('change', '')
|
||||
}
|
||||
this.$emit('change', `${this.fromDate},${this.toDate}`)
|
||||
},
|
||||
swapDatesIfNecessary() {
|
||||
if (!this.fromDate || !this.toDate) {
|
||||
return
|
||||
}
|
||||
// if fromDate is greater than toDate, swap them
|
||||
let fromDate = this.getDate(this.fromDate)
|
||||
let toDate = this.getDate(this.toDate)
|
||||
if (fromDate > toDate) {
|
||||
let temp = fromDate
|
||||
fromDate = toDate
|
||||
toDate = temp
|
||||
}
|
||||
this.fromDate = this.toValue(fromDate)
|
||||
this.toDate = this.toValue(toDate)
|
||||
},
|
||||
selectCurrentMonthYear() {
|
||||
let date = this.toDate ? this.getDate(this.toDate) : this.today
|
||||
this.currentYear = date.getFullYear()
|
||||
this.currentMonth = date.getMonth() + 1
|
||||
},
|
||||
prevMonth() {
|
||||
this.changeMonth(-1)
|
||||
},
|
||||
nextMonth() {
|
||||
this.changeMonth(1)
|
||||
},
|
||||
changeMonth(adder) {
|
||||
this.currentMonth = this.currentMonth + adder
|
||||
if (this.currentMonth < 1) {
|
||||
this.currentMonth = 12
|
||||
this.currentYear = this.currentYear - 1
|
||||
}
|
||||
if (this.currentMonth > 12) {
|
||||
this.currentMonth = 1
|
||||
this.currentYear = this.currentYear + 1
|
||||
}
|
||||
},
|
||||
getDatesAfter(date, count) {
|
||||
let incrementer = 1
|
||||
if (count < 0) {
|
||||
incrementer = -1
|
||||
count = Math.abs(count)
|
||||
}
|
||||
let dates = []
|
||||
while (count) {
|
||||
date = this.getDate(
|
||||
date.getFullYear(),
|
||||
date.getMonth(),
|
||||
date.getDate() + incrementer
|
||||
)
|
||||
dates.push(date)
|
||||
count--
|
||||
}
|
||||
if (incrementer === -1) {
|
||||
return dates.reverse()
|
||||
}
|
||||
return dates
|
||||
},
|
||||
|
||||
getDaysInMonth(monthIndex, year) {
|
||||
let daysInMonthMap = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||
let daysInMonth = daysInMonthMap[monthIndex]
|
||||
if (monthIndex === 1 && this.isLeapYear(year)) {
|
||||
return 29
|
||||
}
|
||||
return daysInMonth
|
||||
},
|
||||
|
||||
isLeapYear(year) {
|
||||
if (year % 400 === 0) return true
|
||||
if (year % 100 === 0) return false
|
||||
if (year % 4 === 0) return true
|
||||
return false
|
||||
},
|
||||
|
||||
toValue(date) {
|
||||
if (!date) {
|
||||
return ''
|
||||
}
|
||||
if (typeof date === 'string') {
|
||||
return date
|
||||
}
|
||||
|
||||
// toISOString is buggy and reduces the day by one
|
||||
// this is because it considers the UTC timestamp
|
||||
// in order to circumvent that we need to use luxon/moment
|
||||
// but that refactor could take some time, so fixing the time difference
|
||||
// as suggested in this answer.
|
||||
// https://stackoverflow.com/a/16084846/3541205
|
||||
date.setHours(0, -date.getTimezoneOffset(), 0, 0)
|
||||
return date.toISOString().slice(0, 10)
|
||||
},
|
||||
|
||||
getDate(...args) {
|
||||
let d = new Date(...args)
|
||||
return d
|
||||
},
|
||||
|
||||
isInRange(date) {
|
||||
if (!this.fromDate || !this.toDate) {
|
||||
return false
|
||||
}
|
||||
return (
|
||||
date >= this.getDate(this.fromDate) && date <= this.getDate(this.toDate)
|
||||
)
|
||||
},
|
||||
|
||||
formatDates(value) {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
const values = value.split(',')
|
||||
return this.formatter(values[0]) + ' to ' + this.formatter(values[1])
|
||||
},
|
||||
clearDates() {
|
||||
this.fromDate = ''
|
||||
this.toDate = ''
|
||||
this.selectDates()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -44,19 +44,10 @@
|
||||
/>
|
||||
</div>
|
||||
<div id="value" class="!min-w-[140px]">
|
||||
<Link
|
||||
v-if="typeLink.includes(f.field.fieldtype)"
|
||||
class="form-control"
|
||||
:value="f.value"
|
||||
:doctype="f.field.options"
|
||||
@change="(v) => updateValue(v, f)"
|
||||
placeholder="Value"
|
||||
/>
|
||||
<component
|
||||
v-else
|
||||
:is="getValSelect(f.field.fieldtype, f.field.options)"
|
||||
:is="getValSelect(f)"
|
||||
:value="f.value"
|
||||
@change="(e) => updateValue(e.target.value, f)"
|
||||
@change="(v) => updateValue(v, f)"
|
||||
placeholder="Value"
|
||||
/>
|
||||
</div>
|
||||
@ -103,6 +94,8 @@
|
||||
</NestedPopover>
|
||||
</template>
|
||||
<script setup>
|
||||
import DatePicker from '@/components/Controls/DatePicker.vue'
|
||||
import DateRangePicker from '@/components/Controls/DateRangePicker.vue'
|
||||
import NestedPopover from '@/components/NestedPopover.vue'
|
||||
import FilterIcon from '@/components/Icons/FilterIcon.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
@ -111,9 +104,10 @@ import { h, defineModel, computed } from 'vue'
|
||||
|
||||
const typeCheck = ['Check']
|
||||
const typeLink = ['Link']
|
||||
const typeNumber = ['Float', 'Int']
|
||||
const typeNumber = ['Float', 'Int', 'Currency', 'Percent']
|
||||
const typeSelect = ['Select']
|
||||
const typeString = ['Data', 'Long Text', 'Small Text', 'Text Editor', 'Text']
|
||||
const typeDate = ['Date', 'Datetime']
|
||||
|
||||
const props = defineProps({
|
||||
doctype: {
|
||||
@ -181,6 +175,10 @@ function convertFilters(data, allFilters) {
|
||||
value = ['equals', value[1] ? 'Yes' : 'No']
|
||||
}
|
||||
}
|
||||
if (value[0] === 'LIKE' || value[0] === 'NOT LIKE') {
|
||||
value[1] = value[1].replace(/%/g, '')
|
||||
}
|
||||
|
||||
if (field) {
|
||||
f.push({
|
||||
field,
|
||||
@ -202,13 +200,16 @@ function getOperators(fieldtype, fieldname) {
|
||||
{ label: 'Not Equals', value: 'not equals' },
|
||||
{ label: 'Like', value: 'like' },
|
||||
{ label: 'Not Like', value: 'not like' },
|
||||
{ label: 'Is', value: 'is' },
|
||||
]
|
||||
)
|
||||
}
|
||||
if (fieldname === '_assign') {
|
||||
// TODO: make equals and not equals work
|
||||
options = [
|
||||
{ label: 'Like', value: 'like' },
|
||||
{ label: 'Not Like', value: 'not like' },
|
||||
{ label: 'Is', value: 'is' },
|
||||
]
|
||||
}
|
||||
if (typeNumber.includes(fieldtype)) {
|
||||
@ -223,22 +224,79 @@ function getOperators(fieldtype, fieldname) {
|
||||
]
|
||||
)
|
||||
}
|
||||
if (typeSelect.includes(fieldtype) || typeLink.includes(fieldtype)) {
|
||||
if (typeSelect.includes(fieldtype)) {
|
||||
options.push(
|
||||
...[
|
||||
{ label: 'Equals', value: 'equals' },
|
||||
{ label: 'Not Equals', value: 'not equals' },
|
||||
{ label: 'Is', value: 'is' },
|
||||
{ label: 'Is Not', value: 'is not' },
|
||||
]
|
||||
)
|
||||
}
|
||||
if (typeLink.includes(fieldtype)) {
|
||||
options.push(
|
||||
...[
|
||||
{ label: 'Equals', value: 'equals' },
|
||||
{ label: 'Not Equals', value: 'not equals' },
|
||||
{ label: 'Is', value: 'is' },
|
||||
{ label: 'Like', value: 'like' },
|
||||
{ label: 'Not Like', value: 'not like' },
|
||||
]
|
||||
)
|
||||
}
|
||||
if (typeCheck.includes(fieldtype)) {
|
||||
options.push(...[{ label: 'Equals', value: 'equals' }])
|
||||
}
|
||||
if (['Duration'].includes(fieldtype)) {
|
||||
options.push(
|
||||
...[
|
||||
{ label: 'Like', value: 'like' },
|
||||
{ label: 'Not Like', value: 'not like' },
|
||||
{ label: 'Is', value: 'is' },
|
||||
]
|
||||
)
|
||||
}
|
||||
if (typeDate.includes(fieldtype)) {
|
||||
options.push(
|
||||
...[
|
||||
{ label: 'Is', value: 'is' },
|
||||
{ label: '>', value: '>' },
|
||||
{ label: '<', value: '<' },
|
||||
{ label: '>=', value: '>=' },
|
||||
{ label: '<=', value: '<=' },
|
||||
{ label: 'Between', value: 'between' },
|
||||
{ label: 'Timespan', value: 'timespan' },
|
||||
]
|
||||
)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
function getValSelect(fieldtype, options) {
|
||||
if (typeSelect.includes(fieldtype) || typeCheck.includes(fieldtype)) {
|
||||
function getValSelect(f) {
|
||||
const { field, operator } = f
|
||||
const { fieldtype, options } = field
|
||||
if (operator == 'is') {
|
||||
return h(FormControl, {
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: 'Set',
|
||||
value: 'set',
|
||||
},
|
||||
{
|
||||
label: 'Not Set',
|
||||
value: 'not set',
|
||||
},
|
||||
],
|
||||
})
|
||||
} else if (operator == 'timespan') {
|
||||
return h(FormControl, {
|
||||
type: 'select',
|
||||
options: timespanOptions,
|
||||
})
|
||||
} else if (operator == 'like') {
|
||||
return h(FormControl, { type: 'text' })
|
||||
} else if (typeSelect.includes(fieldtype) || typeCheck.includes(fieldtype)) {
|
||||
const _options =
|
||||
fieldtype == 'Check' ? ['Yes', 'No'] : getSelectOptions(options)
|
||||
return h(FormControl, {
|
||||
@ -248,6 +306,14 @@ function getValSelect(fieldtype, options) {
|
||||
value: o,
|
||||
})),
|
||||
})
|
||||
} else if (typeLink.includes(fieldtype)) {
|
||||
return h(Link, { class: 'form-control', doctype: options })
|
||||
} else if (typeNumber.includes(fieldtype)) {
|
||||
return h(FormControl, { type: 'number' })
|
||||
} else if (typeDate.includes(fieldtype) && operator == 'between') {
|
||||
return h(DateRangePicker)
|
||||
} else if (typeDate.includes(fieldtype)) {
|
||||
return h(DatePicker)
|
||||
} else {
|
||||
return h(FormControl, { type: 'text' })
|
||||
}
|
||||
@ -260,16 +326,22 @@ function getDefaultValue(field) {
|
||||
if (typeCheck.includes(field.fieldtype)) {
|
||||
return 'Yes'
|
||||
}
|
||||
if (typeDate.includes(field.fieldtype)) {
|
||||
return null
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function getDefaultOperator(fieldtype) {
|
||||
if (typeSelect.includes(fieldtype) || typeLink.includes(fieldtype)) {
|
||||
return 'is'
|
||||
if (typeSelect.includes(fieldtype)) {
|
||||
return 'equals'
|
||||
}
|
||||
if (typeCheck.includes(fieldtype) || typeNumber.includes(fieldtype)) {
|
||||
return 'equals'
|
||||
}
|
||||
if (typeDate.includes(fieldtype)) {
|
||||
return 'between'
|
||||
}
|
||||
return 'like'
|
||||
}
|
||||
|
||||
@ -278,6 +350,7 @@ function getSelectOptions(options) {
|
||||
}
|
||||
|
||||
function setfilter(data) {
|
||||
if (!data) return
|
||||
filters.value.add({
|
||||
field: {
|
||||
label: data.label,
|
||||
@ -320,15 +393,47 @@ function clearfilter(close) {
|
||||
}
|
||||
|
||||
function updateValue(value, filter) {
|
||||
filter.value = value
|
||||
value = value.target ? value.target.value : value
|
||||
if (filter.operator === 'between') {
|
||||
filter.value = [value.split(',')[0], value.split(',')[1]]
|
||||
} else {
|
||||
filter.value = value
|
||||
}
|
||||
apply()
|
||||
}
|
||||
|
||||
function updateOperator(event, filter) {
|
||||
let oldOperatorValue = event.target._value
|
||||
let newOperatorValue = event.target.value
|
||||
filter.operator = event.target.value
|
||||
if (!isSameTypeOperator(oldOperatorValue, newOperatorValue)) {
|
||||
filter.value = getDefaultValue(filter.field)
|
||||
}
|
||||
if (newOperatorValue === 'is' || newOperatorValue === 'is not') {
|
||||
filter.value = 'set'
|
||||
}
|
||||
apply()
|
||||
}
|
||||
|
||||
function isSameTypeOperator(oldOperator, newOperator) {
|
||||
let textOperators = [
|
||||
'like',
|
||||
'not like',
|
||||
'equals',
|
||||
'not equals',
|
||||
'>',
|
||||
'<',
|
||||
'>=',
|
||||
'<=',
|
||||
]
|
||||
if (
|
||||
textOperators.includes(oldOperator) &&
|
||||
textOperators.includes(newOperator)
|
||||
)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
function apply() {
|
||||
let _filters = []
|
||||
filters.value.forEach((f) => {
|
||||
@ -342,8 +447,8 @@ function apply() {
|
||||
}
|
||||
|
||||
function parseFilters(filters) {
|
||||
const l__ = Array.from(filters)
|
||||
const obj = l__.map(transformIn).reduce((p, c) => {
|
||||
const filtersArray = Array.from(filters)
|
||||
const obj = filtersArray.map(transformIn).reduce((p, c) => {
|
||||
if (['equals', '='].includes(c.operator)) {
|
||||
p[c.fieldname] =
|
||||
c.value == 'Yes' ? true : c.value == 'No' ? false : c.value
|
||||
@ -364,8 +469,8 @@ function transformIn(f) {
|
||||
}
|
||||
|
||||
const operatorMap = {
|
||||
is: '=',
|
||||
'is not': '!=',
|
||||
is: 'is',
|
||||
'is not': 'is not',
|
||||
equals: '=',
|
||||
'not equals': '!=',
|
||||
yes: true,
|
||||
@ -376,12 +481,16 @@ const operatorMap = {
|
||||
'<': '<',
|
||||
'>=': '>=',
|
||||
'<=': '<=',
|
||||
between: 'between',
|
||||
timespan: 'timespan',
|
||||
}
|
||||
|
||||
const oppositeOperatorMap = {
|
||||
'=': 'is',
|
||||
is: 'is',
|
||||
'=': 'equals',
|
||||
'!=': 'not equals',
|
||||
equals: 'equals',
|
||||
'!=': 'is not',
|
||||
'is not': 'is not',
|
||||
true: 'yes',
|
||||
false: 'no',
|
||||
LIKE: 'like',
|
||||
@ -390,5 +499,78 @@ const oppositeOperatorMap = {
|
||||
'<': '<',
|
||||
'>=': '>=',
|
||||
'<=': '<=',
|
||||
between: 'between',
|
||||
timespan: 'timespan',
|
||||
}
|
||||
|
||||
const timespanOptions = [
|
||||
{
|
||||
label: 'Last Week',
|
||||
value: 'last week',
|
||||
},
|
||||
{
|
||||
label: 'Last Month',
|
||||
value: 'last month',
|
||||
},
|
||||
{
|
||||
label: 'Last Quarter',
|
||||
value: 'last quarter',
|
||||
},
|
||||
{
|
||||
label: 'Last 6 Months',
|
||||
value: 'last 6 months',
|
||||
},
|
||||
{
|
||||
label: 'Last Year',
|
||||
value: 'last year',
|
||||
},
|
||||
{
|
||||
label: 'Yesterday',
|
||||
value: 'yesterday',
|
||||
},
|
||||
{
|
||||
label: 'Today',
|
||||
value: 'today',
|
||||
},
|
||||
{
|
||||
label: 'Tomorrow',
|
||||
value: 'tomorrow',
|
||||
},
|
||||
{
|
||||
label: 'This Week',
|
||||
value: 'this week',
|
||||
},
|
||||
{
|
||||
label: 'This Month',
|
||||
value: 'this month',
|
||||
},
|
||||
{
|
||||
label: 'This Quarter',
|
||||
value: 'this quarter',
|
||||
},
|
||||
{
|
||||
label: 'This Year',
|
||||
value: 'this year',
|
||||
},
|
||||
{
|
||||
label: 'Next Week',
|
||||
value: 'next week',
|
||||
},
|
||||
{
|
||||
label: 'Next Month',
|
||||
value: 'next month',
|
||||
},
|
||||
{
|
||||
label: 'Next Quarter',
|
||||
value: 'next quarter',
|
||||
},
|
||||
{
|
||||
label: 'Next 6 Months',
|
||||
value: 'next 6 months',
|
||||
},
|
||||
{
|
||||
label: 'Next Year',
|
||||
value: 'next year',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user