Merge pull request #623 from frappe/develop
chore: Merge develop to main
This commit is contained in:
commit
cc50319941
@ -15,12 +15,10 @@ def login():
|
||||
frappe.local.response["location"] = frappe.local.response["redirect_to"]
|
||||
|
||||
|
||||
def validate_reset_password(user):
|
||||
def validate_reset_password(doc, event):
|
||||
if frappe.conf.demo_username and frappe.session.user == frappe.conf.demo_username:
|
||||
frappe.throw(
|
||||
_("Password cannot be reset by Demo User {}").format(
|
||||
frappe.bold(frappe.conf.demo_username)
|
||||
),
|
||||
_("Password cannot be reset by Demo User {}").format(frappe.bold(frappe.conf.demo_username)),
|
||||
frappe.PermissionError,
|
||||
)
|
||||
|
||||
@ -28,9 +26,6 @@ def validate_reset_password(user):
|
||||
def validate_user(doc, event):
|
||||
if frappe.conf.demo_username and frappe.session.user == frappe.conf.demo_username and doc.new_password:
|
||||
frappe.throw(
|
||||
_("Password cannot be reset by Demo User {}").format(
|
||||
frappe.bold(frappe.conf.demo_username)
|
||||
),
|
||||
_("Password cannot be reset by Demo User {}").format(frappe.bold(frappe.conf.demo_username)),
|
||||
frappe.PermissionError,
|
||||
)
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.model import no_value_fields
|
||||
from frappe.model.document import get_controller
|
||||
from frappe.utils import make_filter_tuple
|
||||
@ -178,23 +179,39 @@ def get_doctype_fields_meta(DocField, doctype, allowed_fieldtypes, restricted_fi
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_quick_filters(doctype: str):
|
||||
meta = frappe.get_meta(doctype)
|
||||
fields = [field for field in meta.fields if field.in_standard_filter]
|
||||
def get_quick_filters(doctype: str, cached: bool = True):
|
||||
meta = frappe.get_meta(doctype, cached)
|
||||
quick_filters = []
|
||||
|
||||
if global_settings := frappe.db.exists("CRM Global Settings", {"dt": doctype, "type": "Quick Filters"}):
|
||||
_quick_filters = frappe.db.get_value("CRM Global Settings", global_settings, "json")
|
||||
_quick_filters = json.loads(_quick_filters) or []
|
||||
|
||||
fields = []
|
||||
|
||||
for filter in _quick_filters:
|
||||
if filter == "name":
|
||||
fields.append({"label": "Name", "fieldname": "name", "fieldtype": "Data"})
|
||||
else:
|
||||
field = next((f for f in meta.fields if f.fieldname == filter), None)
|
||||
if field:
|
||||
fields.append(field)
|
||||
|
||||
else:
|
||||
fields = [field for field in meta.fields if field.in_standard_filter]
|
||||
|
||||
for field in fields:
|
||||
options = field.options
|
||||
if field.fieldtype == "Select" and options and isinstance(options, str):
|
||||
options = field.get("options")
|
||||
if field.get("fieldtype") == "Select" and options and isinstance(options, str):
|
||||
options = options.split("\n")
|
||||
options = [{"label": option, "value": option} for option in options]
|
||||
if not any([not option.get("value") for option in options]):
|
||||
options.insert(0, {"label": "", "value": ""})
|
||||
quick_filters.append(
|
||||
{
|
||||
"label": _(field.label),
|
||||
"fieldname": field.fieldname,
|
||||
"fieldtype": field.fieldtype,
|
||||
"label": _(field.get("label")),
|
||||
"fieldname": field.get("fieldname"),
|
||||
"fieldtype": field.get("fieldtype"),
|
||||
"options": options,
|
||||
}
|
||||
)
|
||||
@ -205,6 +222,55 @@ def get_quick_filters(doctype: str):
|
||||
return quick_filters
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_quick_filters(quick_filters: str, old_filters: str, doctype: str):
|
||||
quick_filters = json.loads(quick_filters)
|
||||
old_filters = json.loads(old_filters)
|
||||
|
||||
new_filters = [filter for filter in quick_filters if filter not in old_filters]
|
||||
removed_filters = [filter for filter in old_filters if filter not in quick_filters]
|
||||
|
||||
# update or create global quick filter settings
|
||||
create_update_global_settings(doctype, quick_filters)
|
||||
|
||||
# remove old filters
|
||||
for filter in removed_filters:
|
||||
update_in_standard_filter(filter, doctype, 0)
|
||||
|
||||
# add new filters
|
||||
for filter in new_filters:
|
||||
update_in_standard_filter(filter, doctype, 1)
|
||||
|
||||
|
||||
def create_update_global_settings(doctype, quick_filters):
|
||||
if global_settings := frappe.db.exists("CRM Global Settings", {"dt": doctype, "type": "Quick Filters"}):
|
||||
frappe.db.set_value("CRM Global Settings", global_settings, "json", json.dumps(quick_filters))
|
||||
else:
|
||||
# create CRM Global Settings doc
|
||||
doc = frappe.new_doc("CRM Global Settings")
|
||||
doc.dt = doctype
|
||||
doc.type = "Quick Filters"
|
||||
doc.json = json.dumps(quick_filters)
|
||||
doc.insert()
|
||||
|
||||
|
||||
def update_in_standard_filter(fieldname, doctype, value):
|
||||
if property_name := frappe.db.exists(
|
||||
"Property Setter",
|
||||
{"doc_type": doctype, "field_name": fieldname, "property": "in_standard_filter"},
|
||||
):
|
||||
frappe.db.set_value("Property Setter", property_name, "value", value)
|
||||
else:
|
||||
make_property_setter(
|
||||
doctype,
|
||||
fieldname,
|
||||
"in_standard_filter",
|
||||
value,
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_data(
|
||||
doctype: str,
|
||||
@ -382,7 +448,7 @@ def get_data(
|
||||
all_count = frappe.get_list(
|
||||
doctype,
|
||||
filters=convert_filter_to_tuple(doctype, new_filters),
|
||||
fields="count(*) as total_count"
|
||||
fields="count(*) as total_count",
|
||||
)[0].total_count
|
||||
|
||||
kc["all_count"] = all_count
|
||||
@ -485,9 +551,9 @@ def get_data(
|
||||
"page_length_count": page_length_count,
|
||||
"is_default": is_default,
|
||||
"views": get_views(doctype),
|
||||
"total_count": frappe.get_list(
|
||||
doctype, filters=filters, fields="count(*) as total_count"
|
||||
)[0].total_count,
|
||||
"total_count": frappe.get_list(doctype, filters=filters, fields="count(*) as total_count")[
|
||||
0
|
||||
].total_count,
|
||||
"row_count": len(data),
|
||||
"form_script": get_form_script(doctype),
|
||||
"list_script": get_form_script(doctype, "List"),
|
||||
|
||||
0
crm/fcrm/doctype/crm_global_settings/__init__.py
Normal file
0
crm/fcrm/doctype/crm_global_settings/__init__.py
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("CRM Global Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -0,0 +1,74 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "format:{type}-{dt}",
|
||||
"creation": "2025-02-28 14:37:10.002433",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"dt",
|
||||
"column_break_kipp",
|
||||
"type",
|
||||
"section_break_vass",
|
||||
"json"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "DocType",
|
||||
"fieldname": "dt",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "DocType",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_kipp",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
"options": "Quick Filters\nSidebar Items",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_vass",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "json",
|
||||
"fieldtype": "JSON",
|
||||
"label": "JSON"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-02-28 14:55:33.801215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "CRM Global Settings",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CRMGlobalSettings(Document):
|
||||
pass
|
||||
@ -0,0 +1,30 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class UnitTestCRMGlobalSettings(UnitTestCase):
|
||||
"""
|
||||
Unit tests for CRMGlobalSettings.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class IntegrationTestCRMGlobalSettings(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for CRMGlobalSettings.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@ -132,7 +132,6 @@ before_uninstall = "crm.uninstall.before_uninstall"
|
||||
override_doctype_class = {
|
||||
"Contact": "crm.overrides.contact.CustomContact",
|
||||
"Email Template": "crm.overrides.email_template.CustomEmailTemplate",
|
||||
"User": "crm.overrides.user.CustomUser",
|
||||
}
|
||||
|
||||
# Document Events
|
||||
@ -161,6 +160,7 @@ doc_events = {
|
||||
},
|
||||
"User": {
|
||||
"before_validate": ["crm.api.demo.validate_user"],
|
||||
"validate_reset_password": ["crm.api.demo.validate_reset_password"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
# import frappe
|
||||
from frappe import _
|
||||
from frappe.core.doctype.user.user import User
|
||||
from crm.api.demo import validate_reset_password
|
||||
|
||||
|
||||
class CustomUser(User):
|
||||
def validate_reset_password(self):
|
||||
# restrict demo user to reset password
|
||||
validate_reset_password(self)
|
||||
@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<Popover placement="right-start" class="flex w-full">
|
||||
<Popover
|
||||
placement="right-start"
|
||||
trigger="hover"
|
||||
:hoverDelay="0.1"
|
||||
:leaveDelay="0.1"
|
||||
>
|
||||
<template #target="{ togglePopover }">
|
||||
<button
|
||||
:class="[
|
||||
@ -19,19 +24,19 @@
|
||||
</template>
|
||||
<template #body>
|
||||
<div
|
||||
class="grid grid-cols-3 justify-between mx-3 p-2 min-w-40 rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
class="flex w-fit mx-2 min-w-32 max-w-48 flex-col rounded-lg border border-outline-gray-2 bg-surface-white p-1.5 text-sm text-ink-gray-8 shadow-xl auto-fill-[100px]"
|
||||
>
|
||||
<div v-for="app in apps.data" :key="app.name">
|
||||
<a
|
||||
:href="app.route"
|
||||
class="flex flex-col gap-1.5 rounded justify-center items-center py-2 px-1 hover:bg-surface-gray-2"
|
||||
>
|
||||
<img class="size-8" :src="app.logo" />
|
||||
<div class="text-sm text-ink-gray-7" @click="app.onClick">
|
||||
{{ app.title }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
:href="app.route"
|
||||
v-for="app in apps.data"
|
||||
key="name"
|
||||
class="flex items-center gap-2 rounded p-1.5 hover:bg-surface-gray-2"
|
||||
>
|
||||
<img class="size-6" :src="app.logo" />
|
||||
<span class="max-w-18 w-full truncate">
|
||||
{{ app.title }}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
|
||||
@ -94,7 +94,7 @@ import { usersStore } from '@/stores/users'
|
||||
import { TextEditorBubbleMenu, TextEditor, FileUploader } from 'frappe-ui'
|
||||
import { capture } from '@/telemetry'
|
||||
import { EditorContent } from '@tiptap/vue-3'
|
||||
import { ref, computed, defineModel } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
|
||||
@ -183,7 +183,7 @@ import { capture } from '@/telemetry'
|
||||
import { validateEmail } from '@/utils'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import { EditorContent } from '@tiptap/vue-3'
|
||||
import { ref, computed, defineModel, nextTick } from 'vue'
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
|
||||
16
frontend/src/components/Icons/ExportIcon.vue
Normal file
16
frontend/src/components/Icons/ExportIcon.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 10V2M8 2L5.33333 4.66663M8 2L10.6667 4.66663M11.8571 6.99996H13C13.5523 6.99996 14 7.44767 14 7.99996V13C14 13.5522 13.5523 14 13 14H3C2.44772 14 2 13.5522 2 13V7.99996C2 7.44767 2.44772 6.99996 3 6.99996H4.14286"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
20
frontend/src/components/Icons/QuickFilterIcon.vue
Normal file
20
frontend/src/components/Icons/QuickFilterIcon.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.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-list-filter-plus"
|
||||
>
|
||||
<path d="M10 18h4" />
|
||||
<path d="M11 6H3" />
|
||||
<path d="M15 6h6" />
|
||||
<path d="M18 9V3" />
|
||||
<path d="M7 12h8" />
|
||||
</svg>
|
||||
</template>
|
||||
@ -46,7 +46,7 @@ import { Dropdown, Tooltip } from 'frappe-ui'
|
||||
import { timeAgo, formatDate, formatTime } from '@/utils'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { capture } from '@/telemetry'
|
||||
import { computed, defineModel } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const data = defineModel()
|
||||
const emit = defineEmits(['updateField'])
|
||||
|
||||
@ -58,6 +58,79 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="customizeQuickFilter"
|
||||
class="flex items-center justify-between gap-2 p-5"
|
||||
>
|
||||
<div class="flex flex-1 items-center overflow-hidden pl-1 gap-2">
|
||||
<FadedScrollableDiv
|
||||
class="flex items-center gap-2 overflow-x-auto -ml-1"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Draggable
|
||||
class="flex gap-2"
|
||||
:list="newQuickFilters"
|
||||
group="filters"
|
||||
item-key="fieldname"
|
||||
>
|
||||
<template #item="{ element: filter }">
|
||||
<Button class="group whitespace-nowrap cursor-grab">
|
||||
<template #default>
|
||||
<Tooltip :text="filter.fieldname">
|
||||
<span>{{ filter.label }}</span>
|
||||
</Tooltip>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
class="h-3.5 cursor-pointer group-hover:flex hidden"
|
||||
name="x"
|
||||
@click.stop="removeQuickFilter(filter)"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
</Draggable>
|
||||
</FadedScrollableDiv>
|
||||
<Autocomplete
|
||||
value=""
|
||||
:options="quickFilterOptions"
|
||||
@change="(e) => addQuickFilter(e)"
|
||||
>
|
||||
<template #target="{ togglePopover }">
|
||||
<Button
|
||||
class="whitespace-nowrap mr-2"
|
||||
variant="ghost"
|
||||
@click="togglePopover()"
|
||||
:label="__('Add filter')"
|
||||
>
|
||||
<template #prefix>
|
||||
<FeatherIcon name="plus" class="h-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
<template #item-label="{ option }">
|
||||
<Tooltip :text="option.value" :hover-delay="1">
|
||||
<div class="flex-1 truncate text-ink-gray-7">
|
||||
{{ option.label }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</template>
|
||||
</Autocomplete>
|
||||
</div>
|
||||
<div class="-ml-2 h-[70%] border-l" />
|
||||
<div class="flex gap-1">
|
||||
<Button
|
||||
:label="__('Save')"
|
||||
:loading="updateQuickFilters.loading"
|
||||
@click="saveQuickFilters"
|
||||
/>
|
||||
<Button @click="customizeQuickFilter = false">
|
||||
<template #icon>
|
||||
<FeatherIcon name="x" class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-between gap-2 px-5 py-4">
|
||||
<FadedScrollableDiv
|
||||
class="flex flex-1 items-center overflow-x-auto -ml-1"
|
||||
@ -120,9 +193,7 @@
|
||||
@update="(isDefault) => updateColumns(isDefault)"
|
||||
/>
|
||||
<Dropdown
|
||||
v-if="
|
||||
!options.hideColumnsButton && route.params.viewType !== 'kanban'
|
||||
"
|
||||
v-if="route.params.viewType !== 'kanban'"
|
||||
:options="[
|
||||
{
|
||||
group: __('Options'),
|
||||
@ -130,9 +201,15 @@
|
||||
items: [
|
||||
{
|
||||
label: __('Export'),
|
||||
icon: () =>
|
||||
h(FeatherIcon, { name: 'download', class: 'h-4 w-4' }),
|
||||
icon: () => h(ExportIcon, { class: 'h-4 w-4' }),
|
||||
onClick: () => (showExportDialog = true),
|
||||
condition: () => !options.hideColumnsButton,
|
||||
},
|
||||
{
|
||||
label: __('Customize quick filters'),
|
||||
icon: () => h(QuickFilterIcon, { class: 'h-4 w-4' }),
|
||||
onClick: () => showCustomizeQuickFilter(),
|
||||
condition: () => isManager(),
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -218,7 +295,10 @@ import DuplicateIcon from '@/components/Icons/DuplicateIcon.vue'
|
||||
import CheckIcon from '@/components/Icons/CheckIcon.vue'
|
||||
import PinIcon from '@/components/Icons/PinIcon.vue'
|
||||
import UnpinIcon from '@/components/Icons/UnpinIcon.vue'
|
||||
import ExportIcon from '@/components/Icons/ExportIcon.vue'
|
||||
import QuickFilterIcon from '@/components/Icons/QuickFilterIcon.vue'
|
||||
import ViewModal from '@/components/Modals/ViewModal.vue'
|
||||
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
||||
import SortBy from '@/components/SortBy.vue'
|
||||
import Filter from '@/components/Filter.vue'
|
||||
import GroupBy from '@/components/GroupBy.vue'
|
||||
@ -229,8 +309,10 @@ import { getSettings } from '@/stores/settings'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { viewsStore } from '@/stores/views'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { isEmoji } from '@/utils'
|
||||
import { getMeta } from '@/stores/meta'
|
||||
import { isEmoji, createToast } from '@/utils'
|
||||
import {
|
||||
Tooltip,
|
||||
createResource,
|
||||
Dropdown,
|
||||
call,
|
||||
@ -241,6 +323,7 @@ import { computed, ref, onMounted, watch, h, markRaw } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { isMobileView } from '@/composables/settings'
|
||||
import Draggable from 'vuedraggable'
|
||||
import _ from 'lodash'
|
||||
|
||||
const props = defineProps({
|
||||
@ -594,11 +677,102 @@ const viewsDropdownOptions = computed(() => {
|
||||
return _views
|
||||
})
|
||||
|
||||
const quickFilterList = computed(() => {
|
||||
let filters = [{ fieldname: 'name', fieldtype: 'Data', label: __('ID') }]
|
||||
if (quickFilters.data) {
|
||||
filters.push(...quickFilters.data)
|
||||
const { getFields } = getMeta(props.doctype)
|
||||
|
||||
const customizeQuickFilter = ref(false)
|
||||
|
||||
function showCustomizeQuickFilter() {
|
||||
customizeQuickFilter.value = true
|
||||
setupNewQuickFilters(quickFilters.data)
|
||||
}
|
||||
|
||||
const newQuickFilters = ref([])
|
||||
|
||||
function addQuickFilter(f) {
|
||||
if (!newQuickFilters.value.some((filter) => filter.fieldname === f.value)) {
|
||||
newQuickFilters.value.push({
|
||||
label: f.label,
|
||||
fieldname: f.value,
|
||||
fieldtype: f.fieldtype,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function removeQuickFilter(f) {
|
||||
newQuickFilters.value = newQuickFilters.value.filter(
|
||||
(filter) => filter.fieldname !== f.fieldname,
|
||||
)
|
||||
}
|
||||
|
||||
const updateQuickFilters = createResource({
|
||||
url: 'crm.api.doc.update_quick_filters',
|
||||
onSuccess() {
|
||||
customizeQuickFilter.value = false
|
||||
|
||||
quickFilters.update({ params: { doctype: props.doctype, cached: false } })
|
||||
quickFilters.reload()
|
||||
|
||||
createToast({
|
||||
title: __('Quick Filters updated successfully'),
|
||||
icon: 'check',
|
||||
iconClasses: 'text-ink-green-3',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
function saveQuickFilters() {
|
||||
let new_filters =
|
||||
newQuickFilters.value?.map((filter) => filter.fieldname) || []
|
||||
let old_filters = quickFilters.data?.map((filter) => filter.fieldname) || []
|
||||
|
||||
updateQuickFilters.update({
|
||||
params: {
|
||||
quick_filters: JSON.stringify(new_filters),
|
||||
old_filters: JSON.stringify(old_filters),
|
||||
doctype: props.doctype,
|
||||
},
|
||||
})
|
||||
|
||||
updateQuickFilters.fetch()
|
||||
}
|
||||
|
||||
const quickFilterOptions = computed(() => {
|
||||
let fields = getFields()
|
||||
if (!fields) return []
|
||||
|
||||
let restrictedFieldtypes = [
|
||||
'Tab Break',
|
||||
'Section Break',
|
||||
'Column Break',
|
||||
'Table',
|
||||
'Table MultiSelect',
|
||||
'HTML',
|
||||
'Button',
|
||||
'Image',
|
||||
'Fold',
|
||||
'Heading',
|
||||
]
|
||||
let options = fields
|
||||
.filter((f) => f.label && !restrictedFieldtypes.includes(f.fieldtype))
|
||||
.map((field) => ({
|
||||
label: field.label,
|
||||
value: field.fieldname,
|
||||
fieldtype: field.fieldtype,
|
||||
}))
|
||||
|
||||
if (!options.some((f) => f.fieldname === 'name')) {
|
||||
options.push({
|
||||
label: __('Name'),
|
||||
value: 'name',
|
||||
fieldtype: 'Data',
|
||||
})
|
||||
}
|
||||
|
||||
return options
|
||||
})
|
||||
|
||||
const quickFilterList = computed(() => {
|
||||
let filters = quickFilters.data || []
|
||||
|
||||
filters.forEach((filter) => {
|
||||
filter['value'] = filter.fieldtype == 'Check' ? false : ''
|
||||
@ -630,8 +804,19 @@ const quickFilters = createResource({
|
||||
params: { doctype: props.doctype },
|
||||
cache: ['Quick Filters', props.doctype],
|
||||
auto: true,
|
||||
onSuccess(filters) {
|
||||
setupNewQuickFilters(filters)
|
||||
},
|
||||
})
|
||||
|
||||
function setupNewQuickFilters(filters) {
|
||||
newQuickFilters.value = filters.map((f) => ({
|
||||
label: f.label,
|
||||
fieldname: f.fieldname,
|
||||
fieldtype: f.fieldtype,
|
||||
}))
|
||||
}
|
||||
|
||||
function applyQuickFilter(filter, value) {
|
||||
let filters = { ...list.value.params.filters }
|
||||
let field = filter.fieldname
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user