Merge pull request #613 from frappe/develop
This commit is contained in:
commit
2c36ddb0ce
@ -379,9 +379,11 @@ def get_data(
|
|||||||
new_filters = filters.copy()
|
new_filters = filters.copy()
|
||||||
new_filters.update({column_field: kc.get("name")})
|
new_filters.update({column_field: kc.get("name")})
|
||||||
|
|
||||||
all_count = len(
|
all_count = frappe.get_list(
|
||||||
frappe.get_list(doctype, filters=convert_filter_to_tuple(doctype, new_filters))
|
doctype,
|
||||||
)
|
filters=convert_filter_to_tuple(doctype, new_filters),
|
||||||
|
fields="count(*) as total_count"
|
||||||
|
)[0].total_count
|
||||||
|
|
||||||
kc["all_count"] = all_count
|
kc["all_count"] = all_count
|
||||||
kc["count"] = len(column_data)
|
kc["count"] = len(column_data)
|
||||||
@ -483,7 +485,9 @@ def get_data(
|
|||||||
"page_length_count": page_length_count,
|
"page_length_count": page_length_count,
|
||||||
"is_default": is_default,
|
"is_default": is_default,
|
||||||
"views": get_views(doctype),
|
"views": get_views(doctype),
|
||||||
"total_count": len(frappe.get_list(doctype, filters=filters)),
|
"total_count": frappe.get_list(
|
||||||
|
doctype, filters=filters, fields="count(*) as total_count"
|
||||||
|
)[0].total_count,
|
||||||
"row_count": len(data),
|
"row_count": len(data),
|
||||||
"form_script": get_form_script(doctype),
|
"form_script": get_form_script(doctype),
|
||||||
"list_script": get_form_script(doctype, "List"),
|
"list_script": get_form_script(doctype, "List"),
|
||||||
|
|||||||
@ -82,7 +82,7 @@ const props = defineProps({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
type: [Array, String],
|
type: [Array, Object, String],
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@ -133,7 +133,7 @@ watchDebounced(
|
|||||||
|
|
||||||
const options = createResource({
|
const options = createResource({
|
||||||
url: 'frappe.desk.search.search_link',
|
url: 'frappe.desk.search.search_link',
|
||||||
cache: [props.doctype, text.value, props.hideMe],
|
cache: [props.doctype, text.value, props.hideMe, props.filters],
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params: {
|
params: {
|
||||||
txt: text.value,
|
txt: text.value,
|
||||||
|
|||||||
@ -39,7 +39,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { TextInput } from 'frappe-ui'
|
|
||||||
import { ref, nextTick } from 'vue'
|
import { ref, nextTick } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
140
frontend/src/components/Controls/TableMultiselectInput.vue
Normal file
140
frontend/src/components/Controls/TableMultiselectInput.vue
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="group flex flex-wrap gap-1 min-h-20 p-1.5 rounded text-base bg-surface-gray-2 hover:bg-surface-gray-3 focus:border-outline-gray-4 focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3 text-ink-gray-8 transition-colors w-full"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
ref="valuesRef"
|
||||||
|
v-for="value in parsedValues"
|
||||||
|
:key="value"
|
||||||
|
:label="value"
|
||||||
|
theme="gray"
|
||||||
|
variant="subtle"
|
||||||
|
class="rounded bg-surface-white hover:!bg-surface-gray-1 focus-visible:ring-outline-gray-4"
|
||||||
|
@keydown.delete.capture.stop="removeLastValue"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<FeatherIcon
|
||||||
|
class="h-3.5"
|
||||||
|
name="x"
|
||||||
|
@click.stop="removeValue(value)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
<div class="w-full">
|
||||||
|
<Link
|
||||||
|
v-if="linkField"
|
||||||
|
class="form-control flex-1 truncate cursor-text"
|
||||||
|
:value="query"
|
||||||
|
:filters="filters"
|
||||||
|
:doctype="linkField.options"
|
||||||
|
@change="(v) => addValue(v)"
|
||||||
|
:hideMe="true"
|
||||||
|
>
|
||||||
|
<template #target="{ togglePopover }">
|
||||||
|
<button
|
||||||
|
class="w-full h-7 cursor-text"
|
||||||
|
@click.stop="togglePopover"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ErrorMessage class="mt-2 pl-2" v-if="error" :message="error" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
import { getMeta } from '@/stores/meta'
|
||||||
|
import { ref, computed, nextTick } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
doctype: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
type: Function,
|
||||||
|
default: (value) => `${value} is an Invalid value`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { getFields } = getMeta(props.doctype)
|
||||||
|
|
||||||
|
const values = defineModel()
|
||||||
|
|
||||||
|
const valuesRef = ref([])
|
||||||
|
const error = ref(null)
|
||||||
|
const query = ref('')
|
||||||
|
|
||||||
|
const linkField = ref('')
|
||||||
|
|
||||||
|
const filters = computed(() => {
|
||||||
|
if (!linkField.value) return []
|
||||||
|
return {
|
||||||
|
name: ['not in', parsedValues.value],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const parsedValues = computed(() => {
|
||||||
|
error.value = ''
|
||||||
|
getLinkField()
|
||||||
|
if (!linkField.value) return []
|
||||||
|
return values.value.map((row) => row[linkField.value.fieldname])
|
||||||
|
})
|
||||||
|
|
||||||
|
const getLinkField = () => {
|
||||||
|
error.value = ''
|
||||||
|
if (!linkField.value) {
|
||||||
|
let fields = getFields()
|
||||||
|
linkField.value = fields?.find((df) =>
|
||||||
|
['Link', 'User'].includes(df.fieldtype),
|
||||||
|
)
|
||||||
|
if (!linkField.value) {
|
||||||
|
error.value =
|
||||||
|
'Table MultiSelect requires a Table with atleast one Link field'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return linkField.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const addValue = (value) => {
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
if (values.value.some((row) => row[linkField.value.fieldname] === value)) {
|
||||||
|
error.value = 'Value already exists'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
values.value.push({ [linkField.value.fieldname]: value })
|
||||||
|
!error.value && (query.value = '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeValue = (value) => {
|
||||||
|
values.value = values.value.filter(
|
||||||
|
(row) => row[linkField.value.fieldname] !== value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeLastValue = () => {
|
||||||
|
if (query.value) return
|
||||||
|
|
||||||
|
let valueRef = valuesRef.value[valuesRef.value.length - 1]?.$el
|
||||||
|
if (document.activeElement === valueRef) {
|
||||||
|
values.value.pop()
|
||||||
|
nextTick(() => {
|
||||||
|
if (values.value.length) {
|
||||||
|
valueRef = valuesRef.value[valuesRef.value.length - 1].$el
|
||||||
|
valueRef?.focus()
|
||||||
|
} else {
|
||||||
|
setFocus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
valueRef?.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -81,6 +81,12 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<TableMultiselectInput
|
||||||
|
v-else-if="field.fieldtype === 'Table MultiSelect'"
|
||||||
|
v-model="data[field.fieldname]"
|
||||||
|
:doctype="field.options"
|
||||||
|
/>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
v-else-if="field.fieldtype === 'User'"
|
v-else-if="field.fieldtype === 'User'"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@ -177,6 +183,7 @@
|
|||||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||||
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
|
import TableMultiselectInput from '@/components/Controls/TableMultiselectInput.vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import Grid from '@/components/Controls/Grid.vue'
|
import Grid from '@/components/Controls/Grid.vue'
|
||||||
import { getFormat, evaluateDependsOnValue } from '@/utils'
|
import { getFormat, evaluateDependsOnValue } from '@/utils'
|
||||||
|
|||||||
@ -158,6 +158,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
const restrictedFieldTypes = [
|
const restrictedFieldTypes = [
|
||||||
'Table',
|
'Table',
|
||||||
|
'Table MultiSelect',
|
||||||
'Geolocation',
|
'Geolocation',
|
||||||
'Attach',
|
'Attach',
|
||||||
'Attach Image',
|
'Attach Image',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user