Merge pull request #238 from frappe/develop

chore: Merge develop to main
This commit is contained in:
Shariq Ansari 2024-07-01 19:15:50 +05:30 committed by GitHub
commit fc0bca117c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 428 additions and 321 deletions

View File

@ -332,7 +332,8 @@ def get_data(
for kc in kanban_columns: for kc in kanban_columns:
column_filters = { column_field: kc.get('name') } column_filters = { column_field: kc.get('name') }
if column_field in filters and filters.get(column_field) != kc.name: order = kc.get("order")
if column_field in filters and filters.get(column_field) != kc.name or kc.get('delete'):
column_data = [] column_data = []
else: else:
column_filters.update(filters.copy()) column_filters.update(filters.copy())
@ -341,7 +342,6 @@ def get_data(
if kc.get("page_length"): if kc.get("page_length"):
page_length = kc.get("page_length") page_length = kc.get("page_length")
order = kc.get("order")
if order: if order:
column_data = get_records_based_on_order(doctype, rows, column_filters, page_length, order) column_data = get_records_based_on_order(doctype, rows, column_filters, page_length, order)
else: else:
@ -667,9 +667,6 @@ def get_fields(doctype: str, allow_all_fieldtypes: bool = False):
for field in fields: for field in fields:
if ( if (
field.fieldtype not in not_allowed_fieldtypes field.fieldtype not in not_allowed_fieldtypes
and not field.hidden
and not field.read_only
and not field.is_virtual
and field.fieldname and field.fieldname
): ):
_fields.append({ _fields.append({
@ -678,6 +675,8 @@ def get_fields(doctype: str, allow_all_fieldtypes: bool = False):
"value": field.fieldname, "value": field.fieldname,
"options": field.options, "options": field.options,
"mandatory": field.reqd, "mandatory": field.reqd,
"read_only": field.read_only,
"hidden": field.hidden,
}) })
return _fields return _fields

View File

@ -11,13 +11,15 @@
"enabled", "enabled",
"column_break_avmt", "column_break_avmt",
"record_calls", "record_calls",
"section_break_malx", "section_break_eklq",
"account_sid", "account_sid",
"api_key", "column_break_yqvr",
"api_secret",
"column_break_idds",
"auth_token", "auth_token",
"twiml_sid" "section_break_malx",
"api_key",
"twiml_sid",
"column_break_idds",
"api_secret"
], ],
"fields": [ "fields": [
{ {
@ -31,13 +33,15 @@
"fieldname": "api_key", "fieldname": "api_key",
"fieldtype": "Data", "fieldtype": "Data",
"label": "API Key", "label": "API Key",
"permlevel": 1 "permlevel": 1,
"read_only": 1
}, },
{ {
"fieldname": "api_secret", "fieldname": "api_secret",
"fieldtype": "Password", "fieldtype": "Password",
"label": "API Secret", "label": "API Secret",
"permlevel": 1 "permlevel": 1,
"read_only": 1
}, },
{ {
"fieldname": "column_break_idds", "fieldname": "column_break_idds",
@ -54,7 +58,8 @@
"fieldname": "twiml_sid", "fieldname": "twiml_sid",
"fieldtype": "Data", "fieldtype": "Data",
"label": "TwiML SID", "label": "TwiML SID",
"permlevel": 1 "permlevel": 1,
"read_only": 1
}, },
{ {
"fieldname": "section_break_ssqj", "fieldname": "section_break_ssqj",
@ -79,12 +84,20 @@
"fieldname": "enabled", "fieldname": "enabled",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Enabled" "label": "Enabled"
},
{
"fieldname": "section_break_eklq",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_yqvr",
"fieldtype": "Column Break"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2024-06-11 17:42:38.256260", "modified": "2024-07-01 17:55:25.003703",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "FCRM", "module": "FCRM",
"name": "Twilio Settings", "name": "Twilio Settings",

View File

@ -16,6 +16,8 @@ def after_install():
add_default_fields_layout() add_default_fields_layout()
add_property_setter() add_property_setter()
add_email_template_custom_fields() add_email_template_custom_fields()
add_default_industries()
add_default_lead_sources()
frappe.db.commit() frappe.db.commit()
def add_default_lead_statuses(): def add_default_lead_statuses():
@ -196,3 +198,28 @@ def add_email_template_custom_fields():
) )
frappe.clear_cache(doctype="Email Template") frappe.clear_cache(doctype="Email Template")
def add_default_industries():
industries = ["Accounting", "Advertising", "Aerospace", "Agriculture", "Airline", "Apparel & Accessories", "Automotive", "Banking", "Biotechnology", "Broadcasting", "Brokerage", "Chemical", "Computer", "Consulting", "Consumer Products", "Cosmetics", "Defense", "Department Stores", "Education", "Electronics", "Energy", "Entertainment & Leisure, Executive Search", "Financial Services", "Food", "Beverage & Tobacco", "Grocery", "Health Care", "Internet Publishing", "Investment Banking", "Legal", "Manufacturing", "Motion Picture & Video", "Music", "Newspaper Publishers", "Online Auctions", "Pension Funds", "Pharmaceuticals", "Private Equity", "Publishing", "Real Estate", "Retail & Wholesale", "Securities & Commodity Exchanges", "Service", "Soap & Detergent", "Software", "Sports", "Technology", "Telecommunications", "Television", "Transportation", "Venture Capital"]
for industry in industries:
if frappe.db.exists("CRM Industry", industry):
continue
doc = frappe.new_doc("CRM Industry")
doc.industry = industry
doc.insert()
def add_default_lead_sources():
lead_sources = ["Existing Customer", "Reference", "Advertisement", "Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing", "Customer's Vendor", "Campaign", "Walk In"]
for source in lead_sources:
if frappe.db.exists("CRM Lead Source", source):
continue
doc = frappe.new_doc("CRM Lead Source")
doc.source_name = source
doc.insert()

@ -1 +1 @@
Subproject commit 0c0212cc5bbac151cffc6dc73dfbdf7b69d45ec2 Subproject commit b5cc6c0cd36ee25e8e945e8c91d8b5c035fb5e44

View File

@ -13,7 +13,7 @@
"@vueuse/core": "^10.3.0", "@vueuse/core": "^10.3.0",
"@vueuse/integrations": "^10.3.0", "@vueuse/integrations": "^10.3.0",
"feather-icons": "^4.28.0", "feather-icons": "^4.28.0",
"frappe-ui": "^0.1.61", "frappe-ui": "^0.1.63",
"gemoji": "^8.1.0", "gemoji": "^8.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mime": "^4.0.1", "mime": "^4.0.1",

View File

@ -21,153 +21,174 @@
" "
> >
<div v-for="field in section.fields" :key="field.name"> <div v-for="field in section.fields" :key="field.name">
<div v-if="field.type != 'Check'" class="mb-2 text-sm text-gray-600">
{{ __(field.label) }}
<span class="text-red-500" v-if="field.mandatory">*</span>
</div>
<FormControl
v-if="field.type === 'Select'"
type="select"
class="form-control"
:class="field.prefix ? 'prefix' : ''"
:options="field.options"
v-model="data[field.name]"
:placeholder="__(field.placeholder || field.label)"
>
<template v-if="field.prefix" #prefix>
<IndicatorIcon :class="field.prefix" />
</template>
</FormControl>
<div <div
v-else-if="field.type == 'Check'" v-if="
class="flex items-center gap-2" field.type == 'Check' ||
(field.read_only && data[field.name]) ||
!field.read_only || !field.hidden
"
> >
<FormControl <div
class="form-control" v-if="field.type != 'Check'"
type="checkbox" class="mb-2 text-sm text-gray-600"
v-model="data[field.name]"
@change="(e) => (data[field.name] = e.target.checked)"
:disabled="Boolean(field.read_only)"
/>
<label
class="text-sm text-gray-600"
@click="data[field.name] = !data[field.name]"
> >
{{ __(field.label) }} {{ __(field.label) }}
<span class="text-red-500" v-if="field.mandatory">*</span> <span class="text-red-500" v-if="field.mandatory">*</span>
</label> </div>
</div> <FormControl
<Link v-if="field.read_only && field.type !== 'Check'"
v-else-if="field.type === 'Link'" type="text"
class="form-control" :placeholder="__(field.placeholder || field.label)"
:value="data[field.name]" v-model="data[field.name]"
:doctype="field.options" :disabled="true"
@change="(v) => (data[field.name] = v)" />
:placeholder="__(field.placeholder || field.label)" <FormControl
:onCreate="field.create" v-else-if="field.type === 'Select'"
/> type="select"
<Link class="form-control"
v-else-if="field.type === 'User'" :class="field.prefix ? 'prefix' : ''"
class="form-control" :options="field.options"
:value="getUser(data[field.name]).full_name" v-model="data[field.name]"
:doctype="field.options" :placeholder="__(field.placeholder || field.label)"
@change="(v) => (data[field.name] = v)" >
:placeholder="__(field.placeholder || field.label)" <template v-if="field.prefix" #prefix>
:hideMe="true" <IndicatorIcon :class="field.prefix" />
>
<template #prefix>
<UserAvatar class="mr-2" :user="data[field.name]" size="sm" />
</template>
<template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.value" size="sm" />
</template>
<template #item-label="{ option }">
<Tooltip :text="option.value">
<div class="cursor-pointer">
{{ getUser(option.value).full_name }}
</div>
</Tooltip>
</template>
</Link>
<div v-else-if="field.type === 'Dropdown'">
<NestedPopover>
<template #target="{ open }">
<Button
:label="data[field.name]"
class="dropdown-button flex w-full items-center justify-between rounded border border-gray-100 bg-gray-100 px-2 py-1.5 text-base text-gray-800 placeholder-gray-500 transition-colors hover:border-gray-200 hover:bg-gray-200 focus:border-gray-500 focus:bg-white focus:shadow-sm focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-gray-400"
>
<div class="truncate">{{ data[field.name] }}</div>
<template #suffix>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
class="h-4 text-gray-600"
/>
</template>
</Button>
</template> </template>
<template #body> </FormControl>
<div <div
class="my-2 space-y-1.5 divide-y rounded-lg border border-gray-100 bg-white p-1.5 shadow-xl" v-else-if="field.type == 'Check'"
> class="flex items-center gap-2"
<div> >
<DropdownItem <FormControl
v-if="field.options?.length" class="form-control"
v-for="option in field.options" type="checkbox"
:key="option.name" v-model="data[field.name]"
:option="option" @change="(e) => (data[field.name] = e.target.checked)"
/> :disabled="Boolean(field.read_only)"
<div v-else> />
<div class="p-1.5 px-7 text-base text-gray-500"> <label
{{ __('No {0} Available', [field.label]) }} class="text-sm text-gray-600"
@click="data[field.name] = !data[field.name]"
>
{{ __(field.label) }}
<span class="text-red-500" v-if="field.mandatory">*</span>
</label>
</div>
<Link
v-else-if="field.type === 'Link'"
class="form-control"
:value="data[field.name]"
:doctype="field.options"
@change="(v) => (data[field.name] = v)"
:placeholder="__(field.placeholder || field.label)"
:onCreate="field.create"
/>
<Link
v-else-if="field.type === 'User'"
class="form-control"
:value="getUser(data[field.name]).full_name"
:doctype="field.options"
@change="(v) => (data[field.name] = v)"
:placeholder="__(field.placeholder || field.label)"
:hideMe="true"
>
<template #prefix>
<UserAvatar class="mr-2" :user="data[field.name]" size="sm" />
</template>
<template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.value" size="sm" />
</template>
<template #item-label="{ option }">
<Tooltip :text="option.value">
<div class="cursor-pointer">
{{ getUser(option.value).full_name }}
</div>
</Tooltip>
</template>
</Link>
<div v-else-if="field.type === 'Dropdown'">
<NestedPopover>
<template #target="{ open }">
<Button
:label="data[field.name]"
class="dropdown-button flex w-full items-center justify-between rounded border border-gray-100 bg-gray-100 px-2 py-1.5 text-base text-gray-800 placeholder-gray-500 transition-colors hover:border-gray-200 hover:bg-gray-200 focus:border-gray-500 focus:bg-white focus:shadow-sm focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-gray-400"
>
<div class="truncate">{{ data[field.name] }}</div>
<template #suffix>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
class="h-4 text-gray-600"
/>
</template>
</Button>
</template>
<template #body>
<div
class="my-2 space-y-1.5 divide-y rounded-lg border border-gray-100 bg-white p-1.5 shadow-xl"
>
<div>
<DropdownItem
v-if="field.options?.length"
v-for="option in field.options"
:key="option.name"
:option="option"
/>
<div v-else>
<div class="p-1.5 px-7 text-base text-gray-500">
{{ __('No {0} Available', [field.label]) }}
</div>
</div> </div>
</div> </div>
<div class="pt-1.5">
<Button
variant="ghost"
class="w-full !justify-start"
:label="__('Create New')"
@click="field.create()"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
</div>
</div> </div>
<div class="pt-1.5"> </template>
<Button </NestedPopover>
variant="ghost" </div>
class="w-full !justify-start" <DateTimePicker
:label="__('Create New')" v-else-if="field.type === 'Datetime'"
@click="field.create()" v-model="data[field.name]"
> :placeholder="__(field.placeholder || field.label)"
<template #prefix> input-class="border-none"
<FeatherIcon name="plus" class="h-4" /> />
</template> <DatePicker
</Button> v-else-if="field.type === 'Date'"
</div> v-model="data[field.name]"
</div> :placeholder="__(field.placeholder || field.label)"
</template> input-class="border-none"
</NestedPopover> />
<FormControl
v-else-if="
['Small Text', 'Text', 'Long Text'].includes(field.type)
"
type="textarea"
:placeholder="__(field.placeholder || field.label)"
v-model="data[field.name]"
/>
<FormControl
v-else-if="['Int'].includes(field.type)"
type="number"
:placeholder="__(field.placeholder || field.label)"
v-model="data[field.name]"
/>
<FormControl
v-else
type="text"
:placeholder="__(field.placeholder || field.label)"
v-model="data[field.name]"
:disabled="Boolean(field.read_only)"
/>
</div> </div>
<DateTimePicker
v-else-if="field.type === 'Datetime'"
v-model="data[field.name]"
:placeholder="__(field.placeholder || field.label)"
input-class="border-none"
/>
<DatePicker
v-else-if="field.type === 'Date'"
v-model="data[field.name]"
:placeholder="__(field.placeholder || field.label)"
input-class="border-none"
/>
<FormControl
v-else-if="['Small Text', 'Text', 'Long Text'].includes(field.type)"
type="textarea"
:placeholder="__(field.placeholder || field.label)"
v-model="data[field.name]"
/>
<FormControl
v-else-if="['Int'].includes(field.type)"
type="number"
:placeholder="__(field.placeholder || field.label)"
v-model="data[field.name]"
/>
<FormControl
v-else
type="text"
:placeholder="__(field.placeholder || field.label)"
v-model="data[field.name]"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -67,7 +67,7 @@
</div> </div>
<div id="value" class="w-full"> <div id="value" class="w-full">
<component <component
:is="getValSelect(f)" :is="getValueControl(f)"
v-model="f.value" v-model="f.value"
@change.stop="(v) => updateValue(v, f)" @change.stop="(v) => updateValue(v, f)"
:placeholder="__('John Doe')" :placeholder="__('John Doe')"
@ -100,7 +100,7 @@
</div> </div>
<div id="value" class="!min-w-[140px]"> <div id="value" class="!min-w-[140px]">
<component <component
:is="getValSelect(f)" :is="getValueControl(f)"
v-model="f.value" v-model="f.value"
@change="(v) => updateValue(v, f)" @change="(v) => updateValue(v, f)"
:placeholder="__('John Doe')" :placeholder="__('John Doe')"
@ -242,7 +242,7 @@ function convertFilters(data, allFilters) {
let f = [] let f = []
for (let [key, value] of Object.entries(allFilters)) { for (let [key, value] of Object.entries(allFilters)) {
let field = data.find((f) => f.fieldname === key) let field = data.find((f) => f.fieldname === key)
if (typeof value !== 'object') { if (typeof value !== 'object' || !value) {
value = ['=', value] value = ['=', value]
if (field?.fieldtype === 'Check') { if (field?.fieldtype === 'Check') {
value = ['equals', value[1] ? 'Yes' : 'No'] value = ['equals', value[1] ? 'Yes' : 'No']
@ -273,7 +273,7 @@ function getOperators(fieldtype, fieldname) {
{ label: __('In'), value: 'in' }, { label: __('In'), value: 'in' },
{ label: __('Not In'), value: 'not in' }, { label: __('Not In'), value: 'not in' },
{ label: __('Is'), value: 'is' }, { label: __('Is'), value: 'is' },
] ],
) )
} }
if (fieldname === '_assign') { if (fieldname === '_assign') {
@ -298,7 +298,7 @@ function getOperators(fieldtype, fieldname) {
{ label: __('>'), value: '>' }, { label: __('>'), value: '>' },
{ label: __('<='), value: '<=' }, { label: __('<='), value: '<=' },
{ label: __('>='), value: '>=' }, { label: __('>='), value: '>=' },
] ],
) )
} }
if (typeSelect.includes(fieldtype)) { if (typeSelect.includes(fieldtype)) {
@ -309,7 +309,7 @@ function getOperators(fieldtype, fieldname) {
{ label: __('In'), value: 'in' }, { label: __('In'), value: 'in' },
{ label: __('Not In'), value: 'not in' }, { label: __('Not In'), value: 'not in' },
{ label: __('Is'), value: 'is' }, { label: __('Is'), value: 'is' },
] ],
) )
} }
if (typeLink.includes(fieldtype)) { if (typeLink.includes(fieldtype)) {
@ -322,7 +322,7 @@ function getOperators(fieldtype, fieldname) {
{ label: __('In'), value: 'in' }, { label: __('In'), value: 'in' },
{ label: __('Not In'), value: 'not in' }, { label: __('Not In'), value: 'not in' },
{ label: __('Is'), value: 'is' }, { label: __('Is'), value: 'is' },
] ],
) )
} }
if (typeCheck.includes(fieldtype)) { if (typeCheck.includes(fieldtype)) {
@ -336,7 +336,7 @@ function getOperators(fieldtype, fieldname) {
{ label: __('In'), value: 'in' }, { label: __('In'), value: 'in' },
{ label: __('Not In'), value: 'not in' }, { label: __('Not In'), value: 'not in' },
{ label: __('Is'), value: 'is' }, { label: __('Is'), value: 'is' },
] ],
) )
} }
if (typeDate.includes(fieldtype)) { if (typeDate.includes(fieldtype)) {
@ -351,13 +351,13 @@ function getOperators(fieldtype, fieldname) {
{ label: __('<='), value: '<=' }, { label: __('<='), value: '<=' },
{ label: __('Between'), value: 'between' }, { label: __('Between'), value: 'between' },
{ label: __('Timespan'), value: 'timespan' }, { label: __('Timespan'), value: 'timespan' },
] ],
) )
} }
return options return options
} }
function getValSelect(f) { function getValueControl(f) {
const { field, operator } = f const { field, operator } = f
const { fieldtype, options } = field const { fieldtype, options } = field
if (operator == 'is') { if (operator == 'is') {

View File

@ -1,145 +1,169 @@
<template> <template>
<Draggable <div class="flex overflow-x-auto">
v-if="columns" <Draggable
:list="columns" v-if="columns"
item-key="column" :list="columns"
@end="updateColumn" item-key="column"
class="flex sm:mx-2.5 mx-2 pb-3.5 overflow-x-auto" @end="updateColumn"
> class="flex sm:mx-2.5 mx-2 pb-3.5"
<template #item="{ element: column }"> >
<div <template #item="{ element: column }">
v-if="!column.delete" <div
class="flex flex-col gap-2.5 min-w-72 w-72 hover:bg-gray-100 rounded-lg p-2.5" v-if="!column.column.delete"
> class="flex flex-col gap-2.5 min-w-72 w-72 hover:bg-gray-100 rounded-lg p-2.5"
<div class="flex gap-2 items-center group justify-between"> >
<div class="flex items-center text-base"> <div class="flex gap-2 items-center group justify-between">
<NestedPopover> <div class="flex items-center text-base">
<template #target> <NestedPopover>
<Button variant="ghost" size="sm" class="hover:!bg-gray-100"> <template #target>
<IndicatorIcon <Button variant="ghost" size="sm" class="hover:!bg-gray-100">
:class="colorClasses(column.column.color, true)" <IndicatorIcon
/> :class="colorClasses(column.column.color, true)"
</Button>
</template>
<template #body="{ close }">
<div
class="flex flex-col gap-3 px-3 py-2.5 rounded-lg border border-gray-100 bg-white shadow-xl"
>
<div class="flex gap-1">
<Button
:class="colorClasses(color)"
variant="ghost"
v-for="color in colors"
:key="color"
@click="() => (column.column.color = color)"
>
<IndicatorIcon />
</Button>
</div>
<div class="flex flex-row-reverse">
<Button
variant="solid"
:label="__('Apply')"
@click="updateColumn"
/> />
</div> </Button>
</div> </template>
</template> <template #body="{ close }">
</NestedPopover> <div
<div>{{ column.column.name }}</div> class="flex flex-col gap-3 px-3 py-2.5 rounded-lg border border-gray-100 bg-white shadow-xl"
</div> >
<div class="flex"> <div class="flex gap-1">
<Dropdown :options="actions(column)"> <Button
<template #default> :class="colorClasses(color)"
<Button variant="ghost"
class="hidden group-hover:flex" v-for="color in colors"
icon="more-horizontal" :key="color"
variant="ghost" @click="() => (column.column.color = color)"
/> >
</template> <IndicatorIcon />
</Dropdown> </Button>
<Button </div>
icon="plus" <div class="flex flex-row-reverse">
variant="ghost" <Button
@click="options.onNewClick(column)" variant="solid"
/> :label="__('Apply')"
</div> @click="updateColumn"
</div> />
<div class="overflow-y-auto flex flex-col gap-2 h-full">
<Draggable
:list="column.data"
group="fields"
item-key="name"
class="flex flex-col gap-3.5 flex-1"
@end="updateColumn"
:data-column="column.column.name"
>
<template #item="{ element: fields }">
<component
:is="options.getRoute ? 'router-link' : 'div'"
class="pt-3 px-3.5 pb-2.5 rounded-lg border bg-white text-base flex flex-col"
:data-name="fields.name"
v-bind="{
to: options.getRoute ? options.getRoute(fields) : undefined,
onClick: options.onClick
? () => options.onClick(fields)
: undefined,
}"
>
<slot
name="title"
v-bind="{ fields, titleField, itemName: fields.name }"
>
<div class="h-5 flex items-center">
<div v-if="fields[titleField]">
{{ fields[titleField] }}
</div> </div>
<div class="text-gray-500" v-else>{{ __('No Title') }}</div>
</div> </div>
</slot> </template>
<div class="border-b h-px my-2.5" /> </NestedPopover>
<div>{{ column.column.name }}</div>
<div class="flex flex-col gap-3.5"> </div>
<template v-for="value in column.fields" :key="value"> <div class="flex">
<slot <Dropdown :options="actions(column)">
name="fields" <template #default>
v-bind="{ <Button
fields, class="hidden group-hover:flex"
fieldName: value, icon="more-horizontal"
itemName: fields.name, variant="ghost"
}" />
> </template>
<div v-if="fields[value]" class="truncate"> </Dropdown>
{{ fields[value] }} <Button
icon="plus"
variant="ghost"
@click="options.onNewClick(column)"
/>
</div>
</div>
<div class="overflow-y-auto flex flex-col gap-2 h-full">
<Draggable
:list="column.data"
group="fields"
item-key="name"
class="flex flex-col gap-3.5 flex-1"
@end="updateColumn"
:data-column="column.column.name"
>
<template #item="{ element: fields }">
<component
:is="options.getRoute ? 'router-link' : 'div'"
class="pt-3 px-3.5 pb-2.5 rounded-lg border bg-white text-base flex flex-col"
:data-name="fields.name"
v-bind="{
to: options.getRoute ? options.getRoute(fields) : undefined,
onClick: options.onClick
? () => options.onClick(fields)
: undefined,
}"
>
<slot
name="title"
v-bind="{ fields, titleField, itemName: fields.name }"
>
<div class="h-5 flex items-center">
<div v-if="fields[titleField]">
{{ fields[titleField] }}
</div> </div>
</slot> <div class="text-gray-500" v-else>
</template> {{ __('No Title') }}
</div> </div>
<div class="border-b h-px mt-2.5 mb-2" /> </div>
<slot name="actions" v-bind="{ itemName: fields.name }"> </slot>
<div class="flex gap-2 items-center justify-between"> <div class="border-b h-px my-2.5" />
<div></div>
<Button icon="plus" variant="ghost" @click.stop.prevent /> <div class="flex flex-col gap-3.5">
<template v-for="value in column.fields" :key="value">
<slot
name="fields"
v-bind="{
fields,
fieldName: value,
itemName: fields.name,
}"
>
<div v-if="fields[value]" class="truncate">
{{ fields[value] }}
</div>
</slot>
</template>
</div> </div>
</slot> <div class="border-b h-px mt-2.5 mb-2" />
</component> <slot name="actions" v-bind="{ itemName: fields.name }">
</template> <div class="flex gap-2 items-center justify-between">
</Draggable> <div></div>
<div <Button icon="plus" variant="ghost" @click.stop.prevent />
v-if="column.column.count < column.column.all_count" </div>
class="flex items-center justify-center" </slot>
> </component>
<Button </template>
:label="__('Load More')" </Draggable>
@click="emit('loadMore', column.column.name)" <div
/> v-if="column.column.count < column.column.all_count"
class="flex items-center justify-center"
>
<Button
:label="__('Load More')"
@click="emit('loadMore', column.column.name)"
/>
</div>
</div> </div>
</div> </div>
</div> </template>
</template> </Draggable>
</Draggable> <div v-if="deletedColumns.length" class="shrink-0 min-w-64">
<Autocomplete
value=""
:options="deletedColumns"
@change="(e) => addColumn(e)"
>
<template #target="{ togglePopover }">
<Button
class="w-full mt-2.5 mb-1 mr-5"
@click="togglePopover()"
:label="__('Add Column')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
</template>
</Autocomplete>
</div>
</div>
</template> </template>
<script setup> <script setup>
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
import NestedPopover from '@/components/NestedPopover.vue' import NestedPopover from '@/components/NestedPopover.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import Draggable from 'vuedraggable' import Draggable from 'vuedraggable'
@ -179,6 +203,14 @@ const columns = computed(() => {
return _columns return _columns
}) })
const deletedColumns = computed(() => {
return columns.value
.filter((col) => col.column['delete'])
.map((col) => {
return { label: col.column.name, value: col.column.name }
})
})
function actions(column) { function actions(column) {
return [ return [
{ {
@ -189,7 +221,7 @@ function actions(column) {
label: __('Delete'), label: __('Delete'),
icon: 'trash-2', icon: 'trash-2',
onClick: () => { onClick: () => {
column['delete'] = true column.column['delete'] = true
updateColumn() updateColumn()
}, },
}, },
@ -198,14 +230,19 @@ function actions(column) {
] ]
} }
function updateColumn({ item, from, to }) { function addColumn(e) {
let toColumn = to?.dataset.column let column = columns.value.find((col) => col.column.name == e.value)
let fromColumn = from?.dataset.column column.column['delete'] = false
let itemName = item?.dataset.name updateColumn()
}
function updateColumn(d) {
let toColumn = d?.to?.dataset.column
let fromColumn = d?.from?.dataset.column
let itemName = d?.item?.dataset.name
let _columns = [] let _columns = []
columns.value.forEach((col) => { columns.value.forEach((col) => {
if (col.delete) return
col.column['order'] = col.data.map((d) => d.name) col.column['order'] = col.data.map((d) => d.name)
if (col.column.page_length) { if (col.column.page_length) {
delete col.column.page_length delete col.column.page_length

View File

@ -1,16 +1,17 @@
<template> <template>
<EditValueModal <EditValueModal
v-if="showEditModal"
v-model="showEditModal" v-model="showEditModal"
:doctype="doctype" :doctype="doctype"
:selectedValues="selectedValues" :selectedValues="selectedValues"
@reload="reload" @reload="reload"
/> />
<AssignmentModal <AssignmentModal
v-if="selectedValues" v-if="showAssignmentModal"
:docs="selectedValues"
:doctype="doctype"
v-model="showAssignmentModal" v-model="showAssignmentModal"
v-model:assignees="bulkAssignees" v-model:assignees="bulkAssignees"
:docs="selectedValues"
:doctype="doctype"
@reload="reload" @reload="reload"
/> />
</template> </template>

View File

@ -85,8 +85,8 @@ const props = defineProps({
default: null, default: null,
}, },
docs: { docs: {
type: Array, type: Set,
default: () => [], default: new Set(),
}, },
doctype: { doctype: {
type: String, type: String,

View File

@ -52,7 +52,7 @@ const props = defineProps({
required: true, required: true,
}, },
selectedValues: { selectedValues: {
type: Array, type: Set,
required: true, required: true,
}, },
}) })
@ -67,6 +67,9 @@ const fields = createResource({
params: { params: {
doctype: props.doctype, doctype: props.doctype,
}, },
transform: (data) => {
return data.filter((f) => f.hidden == 0 && f.read_only == 0)
}
}) })
onMounted(() => { onMounted(() => {

View File

@ -208,7 +208,7 @@ async function updateTask() {
function render() { function render() {
editMode.value = false editMode.value = false
nextTick(() => { nextTick(() => {
title.value.el.focus() title.value?.el?.focus?.()
_task.value = { ...props.task } _task.value = { ...props.task }
if (_task.value.title) { if (_task.value.title) {
editMode.value = true editMode.value = true

View File

@ -181,7 +181,9 @@
@update="(isDefault) => updateColumns(isDefault)" @update="(isDefault) => updateColumns(isDefault)"
/> />
<Dropdown <Dropdown
v-if="!options.hideColumnsButton" v-if="
!options.hideColumnsButton && route.params.viewType !== 'kanban'
"
:options="[ :options="[
{ {
group: __('Options'), group: __('Options'),

View File

@ -278,11 +278,11 @@
}" }"
/> />
<AssignmentModal <AssignmentModal
v-if="deal.data" v-if="showAssignmentModal"
:doc="deal.data"
doctype="CRM Deal"
v-model="showAssignmentModal" v-model="showAssignmentModal"
v-model:assignees="deal.data._assignedTo" v-model:assignees="deal.data._assignedTo"
:doc="deal.data"
doctype="CRM Deal"
/> />
</template> </template>
<script setup> <script setup>

View File

@ -237,12 +237,14 @@
:defaults="defaults" :defaults="defaults"
/> />
<NoteModal <NoteModal
v-if="showNoteModal"
v-model="showNoteModal" v-model="showNoteModal"
:note="note" :note="note"
doctype="CRM Deal" doctype="CRM Deal"
:doc="docname" :doc="docname"
/> />
<TaskModal <TaskModal
v-if="showTaskModal"
v-model="showTaskModal" v-model="showTaskModal"
:task="task" :task="task"
doctype="CRM Deal" doctype="CRM Deal"

View File

@ -185,11 +185,11 @@
</Resizer> </Resizer>
</div> </div>
<AssignmentModal <AssignmentModal
v-if="lead.data" v-if="showAssignmentModal"
:doc="lead.data"
doctype="CRM Lead"
v-model="showAssignmentModal" v-model="showAssignmentModal"
v-model:assignees="lead.data._assignedTo" v-model:assignees="lead.data._assignedTo"
:doc="lead.data"
doctype="CRM Lead"
/> />
<Dialog <Dialog
v-model="showConvertToDealModal" v-model="showConvertToDealModal"

View File

@ -263,12 +263,14 @@
:defaults="defaults" :defaults="defaults"
/> />
<NoteModal <NoteModal
v-if="showNoteModal"
v-model="showNoteModal" v-model="showNoteModal"
:note="note" :note="note"
doctype="CRM Lead" doctype="CRM Lead"
:doc="docname" :doc="docname"
/> />
<TaskModal <TaskModal
v-if="showTaskModal"
v-model="showTaskModal" v-model="showTaskModal"
:task="task" :task="task"
doctype="CRM Lead" doctype="CRM Lead"

View File

@ -237,11 +237,11 @@
}" }"
/> />
<AssignmentModal <AssignmentModal
v-if="deal.data" v-if="showAssignmentModal"
:doc="deal.data"
doctype="CRM Deal"
v-model="showAssignmentModal" v-model="showAssignmentModal"
v-model:assignees="deal.data._assignedTo" v-model:assignees="deal.data._assignedTo"
:doc="deal.data"
doctype="CRM Deal"
/> />
</template> </template>
<script setup> <script setup>

View File

@ -96,11 +96,11 @@
</Tabs> </Tabs>
</div> </div>
<AssignmentModal <AssignmentModal
v-if="lead.data" v-if="showAssignmentModal"
:doc="lead.data"
doctype="CRM Lead"
v-model="showAssignmentModal" v-model="showAssignmentModal"
v-model:assignees="lead.data._assignedTo" v-model:assignees="lead.data._assignedTo"
:doc="lead.data"
doctype="CRM Lead"
/> />
<Dialog <Dialog
v-model="showConvertToDealModal" v-model="showConvertToDealModal"