1
0
forked from test/crm

fix: paddings and labels

(cherry picked from commit db577afc568b56b49846773b16b638e0cf1444fa)

# Conflicts:
#	frontend/src/components/Settings/AssignmentRules/AssigneeRules.vue
#	frontend/src/components/Settings/AssignmentRules/AssignmentRuleView.vue
#	frontend/src/components/Settings/AssignmentRules/AssignmentRules.vue
This commit is contained in:
Pratik Badhe 2025-09-09 09:00:31 +00:00 committed by Mergify
parent 49ed1ac174
commit 004923419c
9 changed files with 3203 additions and 85 deletions

View File

@ -0,0 +1,162 @@
<template>
<div>
<div class="flex flex-col gap-1">
<span class="text-lg font-semibold text-ink-gray-8">{{
__('Assignee Rules')
}}</span>
<span class="text-p-sm text-ink-gray-6">
{{
__(
'Define who receives the {0} and how theyre distributed among agents.',
[documentType],
)
}}
</span>
</div>
<div class="mt-8 flex items-center justify-between gap-2">
<div>
<div class="text-base font-medium text-ink-gray-8">
{{
__('{0} Routing', [
assignmentRuleData.documentType == 'CRM Lead'
? __('Lead')
: __('Deal'),
])
}}
</div>
<div class="text-p-sm text-ink-gray-6 mt-1">
{{
__('Choose how {0} are distributed among selected assignees.', [
documentType,
])
}}
</div>
</div>
<div>
<Popover placement="bottom-end">
<template #target="{ togglePopover }">
<div
class="flex items-center justify-between text-base rounded h-7 py-1.5 pl-2 pr-2 border border-[--surface-gray-2] bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3 text-ink-gray-8 transition-colors w-full dark:[color-scheme:dark] select-none min-w-40"
@click="togglePopover()"
>
<div>
{{
documentRoutingOptions.find(
(option) => option.value == assignmentRuleData.rule,
)?.label
}}
</div>
<FeatherIcon name="chevron-down" class="size-4" />
</div>
</template>
<template #body="{ togglePopover }">
<div
class="p-1 text-ink-gray-7 mt-1 w-48 bg-white shadow-xl rounded"
>
<div
v-for="option in documentRoutingOptions"
:key="option.value"
class="p-2 cursor-pointer hover:bg-gray-50 text-sm flex items-center justify-between rounded"
@click="
() => {
assignmentRuleData.rule = option.value
togglePopover()
}
"
>
<span>
{{ option.label }}
</span>
<FeatherIcon
v-if="assignmentRuleData.rule == option.value"
name="check"
class="size-4"
/>
</div>
</div>
</template>
</Popover>
</div>
</div>
<div class="mt-7 flex items-center justify-between gap-2">
<div>
<div class="text-base font-medium text-ink-gray-8">
{{ __('Assignees') }}
</div>
<div class="text-p-sm text-ink-gray-6 mt-1">
{{ __('Choose who receives the {0}.', [documentType]) }}
</div>
</div>
<AssigneeSearch @addAssignee="validateAssignmentRule('users')" />
</div>
<div class="mt-4 flex flex-wrap gap-2">
<div
v-for="user in users"
:key="user.name"
class="flex items-center gap-2 text-sm bg-surface-gray-2 rounded-md p-1 w-max px-2 select-none"
>
<Avatar :image="user.user_image" :label="user.full_name" size="sm" />
<div class="text-ink-gray-7">
{{ user.full_name }}
</div>
<Tooltip
v-if="user.email == assignmentRuleData.lastUser"
:text="__('Last user assigned by this rule')"
:hover-delay="0.35"
:placement="'top'"
>
<div
class="text-xs rounded-full select-none bg-blue-600 text-white p-0.5 px-2"
>
{{ __('Last') }}
</div>
</Tooltip>
<Button variant="ghost" icon="x" @click="removeAssignedUser(user)" />
</div>
</div>
<ErrorMessage :message="assignmentRuleErrors.users" />
</div>
</template>
<script setup>
import { Avatar, Button, ErrorMessage, Popover, Tooltip } from 'frappe-ui'
import AssigneeSearch from './AssigneeSearch.vue'
import { computed, inject } from 'vue'
import { usersStore } from '@/stores/users'
const { getUser } = usersStore()
const assignmentRuleData = inject('assignmentRuleData')
const assignmentRuleErrors = inject('assignmentRuleErrors')
const validateAssignmentRule = inject('validateAssignmentRule')
const documentType = computed(() =>
assignmentRuleData.value.documentType == 'CRM Lead'
? __('leads')
: __('deals'),
)
const documentRoutingOptions = [
{
label: 'Auto-rotate',
value: 'Round Robin',
},
{
label: 'Assign by workload',
value: 'Load Balancing',
},
]
const removeAssignedUser = (user) => {
assignmentRuleData.value.users = assignmentRuleData.value.users.filter(
(u) => u.user !== user.name,
)
validateAssignmentRule('users')
}
const users = computed(() => {
const _users = []
assignmentRuleData.value.users.forEach((user) => {
_users.push(getUser(user.user))
})
return _users
})
</script>

View File

@ -0,0 +1,782 @@
<template>
<div
v-if="getAssignmentRuleData.loading"
class="flex items-center h-full justify-center"
>
<LoadingIndicator class="w-4" />
</div>
<div
v-if="!getAssignmentRuleData.loading"
class="sticky top-0 z-10 bg-white pb-6 px-10 py-8"
>
<div class="flex items-center justify-between w-full">
<div class="flex items-center gap-2">
<Button
variant="ghost"
icon-left="chevron-left"
:label="
assignmentRuleData.assignmentRuleName || __('New Assignment Rule')
"
size="md"
@click="goBack()"
class="cursor-pointer -ml-4 hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5 font-semibold text-xl hover:opacity-70 !pr-0 !max-w-96 !justify-start"
/>
<Badge
:variant="'subtle'"
:theme="'orange'"
size="sm"
:label="__('Unsaved')"
v-if="isDirty"
/>
</div>
<div class="flex items-center gap-4">
<div
class="flex items-center justify-between gap-2"
@click="assignmentRuleData.disabled = !assignmentRuleData.disabled"
>
<Switch size="sm" :model-value="!assignmentRuleData.disabled" />
<span class="text-sm text-ink-gray-7">{{ __('Enabled') }}</span>
</div>
<Button
:disabled="Boolean(!isDirty && step.data)"
:label="__('Save')"
theme="gray"
variant="solid"
@click="saveAssignmentRule()"
:loading="isLoading || getAssignmentRuleData.loading"
/>
</div>
</div>
</div>
<div v-if="!getAssignmentRuleData.loading" class="overflow-y-auto px-10 pb-8">
<div class="grid grid-cols-2 gap-5">
<div>
<FormControl
:type="'text'"
size="sm"
variant="subtle"
:placeholder="__('Name')"
:label="__('Name')"
v-model="assignmentRuleData.assignmentRuleName"
required
maxlength="50"
@change="validateAssignmentRule('assignmentRuleName')"
/>
<ErrorMessage
:message="assignmentRuleErrors.assignmentRuleName"
class="mt-2"
/>
</div>
<div class="flex flex-col gap-1.5">
<FormLabel :label="__('Priority')" />
<Popover>
<template #target="{ togglePopover }">
<div
class="flex items-center justify-between text-base rounded h-7 py-1.5 pl-2 pr-2 border border-[--surface-gray-2] bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3 text-ink-gray-8 transition-colors w-full dark:[color-scheme:dark] cursor-default"
@click="togglePopover()"
>
<div>
{{
priorityOptions.find(
(option) => option.value == assignmentRuleData.priority,
)?.label
}}
</div>
<FeatherIcon name="chevron-down" class="size-4" />
</div>
</template>
<template #body="{ togglePopover }">
<div
class="p-1 text-ink-gray-6 top-1 absolute w-full bg-white shadow-2xl rounded"
>
<div
v-for="option in priorityOptions"
:key="option.value"
class="p-2 cursor-pointer hover:bg-gray-50 text-base flex items-center justify-between rounded"
@click="
() => {
assignmentRuleData.priority = option.value
togglePopover()
}
"
>
{{ option.label }}
<FeatherIcon
v-if="assignmentRuleData.priority == option.value"
name="check"
class="size-4"
/>
</div>
</div>
</template>
</Popover>
</div>
<div>
<FormControl
:type="'textarea'"
size="sm"
variant="subtle"
:placeholder="__('Description')"
:label="__('Description')"
required
maxlength="250"
@change="validateAssignmentRule('description')"
v-model="assignmentRuleData.description"
/>
<ErrorMessage
:message="assignmentRuleErrors.description"
class="mt-2"
/>
</div>
<div class="flex flex-col gap-1.5">
<FormLabel :label="__('Apply on')" />
<Select
:options="[
{
label: 'Lead',
value: 'CRM Lead',
},
{
label: 'Deal',
value: 'CRM Deal',
},
]"
v-model="assignmentRuleData.documentType"
/>
</div>
</div>
<hr class="my-8" />
<div>
<div class="flex flex-col gap-1">
<span class="text-lg font-semibold text-ink-gray-8">{{
__('Assignment condition')
}}</span>
<div class="flex items-center justify-between gap-6">
<span class="text-p-sm text-ink-gray-6">
{{
__('Choose which {0} are affected by this assignment rule.', [
documentType,
])
}}
<a
class="font-medium underline"
href="https://docs.frappe.io/crm/assignment-rule"
target="_blank"
>{{ __('Learn about conditions') }}</a
>
</span>
<div v-if="isOldSla && step.data">
<Popover trigger="hover" :hoverDelay="0.25" placement="top-end">
<template #target>
<div
class="text-sm text-ink-gray-6 flex gap-1 cursor-default text-nowrap items-center"
>
<span>{{ __('Old Condition') }}</span>
<FeatherIcon name="info" class="size-4" />
</div>
</template>
<template #body-main>
<div
class="text-sm text-ink-gray-6 p-2 bg-white rounded-md max-w-96 text-wrap whitespace-pre-wrap leading-5"
>
<code>{{ assignmentRuleData.assignCondition }}</code>
</div>
</template>
</Popover>
</div>
</div>
</div>
<div class="mt-5">
<div
class="flex flex-col gap-3 items-center text-center text-ink-gray-7 text-sm mb-2 border border-gray-300 rounded-md p-3 py-4"
v-if="!useNewUI && assignmentRuleData.assignCondition"
>
<span class="text-p-sm">
{{ __('Conditions for this rule were created from') }}
<a :href="deskUrl" target="_blank" class="underline">{{
__('desk')
}}</a>
{{
__(
'which are not compatible with this UI, you will need to recreate the conditions here if you want to manage and add new conditions from this UI.',
)
}}
</span>
<Button
:label="__('I understand, add conditions')"
variant="subtle"
theme="gray"
@click="useNewUI = true"
/>
</div>
<AssignmentRulesSection
:conditions="assignmentRuleData.assignConditionJson"
name="assignCondition"
:errors="assignmentRuleErrors.assignConditionError"
:doctype="assignmentRuleData.documentType"
v-else
/>
<div class="flex justify-end">
<ErrorMessage
:message="assignmentRuleErrors.assignCondition"
class="mt-2"
/>
</div>
</div>
</div>
<hr class="my-8" />
<div>
<div class="flex flex-col gap-1">
<span class="text-lg font-semibold text-ink-gray-8">{{
__('Unassignment condition')
}}</span>
<div class="flex items-center justify-between gap-6">
<span class="text-p-sm text-ink-gray-6">
{{
__('Choose which {0} are affected by this un-assignment rule.', [
documentType,
])
}}
<a
class="font-medium underline"
href="https://docs.frappe.io/crm/assignment-rule"
target="_blank"
>{{ __('Learn about conditions') }}</a
>
</span>
<div
v-if="isOldSla && step.data && assignmentRuleData.unassignCondition"
>
<Popover trigger="hover" :hoverDelay="0.25" placement="top-end">
<template #target>
<div
class="text-sm text-ink-gray-6 flex gap-1 cursor-default text-nowrap items-center"
>
<span> {{ __('Old Condition') }} </span>
<FeatherIcon name="info" class="size-4" />
</div>
</template>
<template #body-main>
<div
class="text-sm text-ink-gray-6 p-2 bg-white rounded-md max-w-96 text-wrap whitespace-pre-wrap leading-5"
>
<code>{{ assignmentRuleData.unassignCondition }}</code>
</div>
</template>
</Popover>
</div>
</div>
</div>
<div class="mt-5">
<div
class="flex flex-col gap-3 items-center text-center text-ink-gray-7 text-sm mb-2 border border-gray-300 rounded-md p-3 py-4"
v-if="!useNewUI && assignmentRuleData.unassignCondition"
>
<span class="text-p-sm">
{{ __('Conditions for this rule were created from') }}
<a :href="deskUrl" target="_blank" class="underline">{{
__('desk')
}}</a>
{{
__(
'which are not compatible with this UI, you will need to recreate the conditions here if you want to manage and add new conditions from this UI.',
)
}}
</span>
<Button
:label="__('I understand, add conditions')"
variant="subtle"
theme="gray"
@click="useNewUI = true"
/>
</div>
<AssignmentRulesSection
:conditions="assignmentRuleData.unassignConditionJson"
name="unassignCondition"
:errors="assignmentRuleErrors.unassignConditionError"
:doctype="assignmentRuleData.documentType"
v-else
/>
</div>
</div>
<hr class="my-8" />
<div>
<div class="flex flex-col gap-1">
<span class="text-lg font-semibold text-ink-gray-8">{{
__('Assignment Schedule')
}}</span>
<span class="text-p-sm text-ink-gray-6">
{{
__('Choose the days of the week when this rule should be active.')
}}
</span>
</div>
<div class="mt-6">
<AssignmentSchedule />
</div>
</div>
<hr class="my-8" />
<AssigneeRules />
</div>
<ConfirmDialog
v-model="showConfirmDialog.show"
:title="showConfirmDialog.title"
:message="showConfirmDialog.message"
:onConfirm="showConfirmDialog.onConfirm"
:onCancel="() => (showConfirmDialog.show = false)"
/>
</template>
<script setup>
import {
Badge,
Button,
call,
createResource,
ErrorMessage,
FormControl,
FormLabel,
LoadingIndicator,
Popover,
Select,
Switch,
toast,
} from 'frappe-ui'
import {
onMounted,
onUnmounted,
ref,
inject,
watch,
provide,
computed,
} from 'vue'
import AssignmentRulesSection from './AssignmentRulesSection.vue'
import AssignmentSchedule from './AssignmentSchedule.vue'
import AssigneeRules from './AssigneeRules.vue'
import ConfirmDialog from 'frappe-ui/src/components/ConfirmDialog.vue'
import { globalStore } from '@/stores/global'
import { disableSettingModalOutsideClick } from '@/composables/settings'
import { convertToConditions, validateConditions } from '@/utils'
const isDirty = ref(false)
const initialData = ref(null)
const isLoading = ref(false)
const updateStep = inject('updateStep')
const step = inject('step')
const { $dialog } = globalStore()
const showConfirmDialog = ref({
show: false,
title: '',
message: '',
onConfirm: () => {},
})
const useNewUI = ref(true)
const isOldSla = ref(false)
const documentType = computed(() =>
assignmentRuleData.value.documentType == 'CRM Lead'
? __('leads')
: __('deals'),
)
const deskUrl = `${window.location.origin}/app/assignment-rule/${step.value.data?.name}`
const defaultAssignmentDays = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
]
const assignmentRuleData = ref({
assignCondition: '',
unassignCondition: '',
assignConditionJson: [],
unassignConditionJson: [],
rule: 'Round Robin',
priority: 1,
users: [],
disabled: false,
description: '',
name: '',
assignmentRuleName: '',
assignmentDays: defaultAssignmentDays,
documentType: 'CRM Lead',
})
const validateAssignmentRule = (key, skipConditionCheck = false) => {
const validateField = (field) => {
if (key && field !== key) return
switch (field) {
case 'assignmentRuleName':
if (assignmentRuleData.value.assignmentRuleName?.length == 0) {
assignmentRuleErrors.value.assignmentRuleName = __('Name is required')
} else {
assignmentRuleErrors.value.assignmentRuleName = ''
}
break
case 'description':
assignmentRuleErrors.value.description =
assignmentRuleData.value.description?.length > 0
? ''
: __('Description is required')
break
case 'assignCondition':
if (skipConditionCheck) {
break
}
assignmentRuleErrors.value.assignCondition =
assignmentRuleData.value.assignConditionJson?.length > 0
? ''
: __('Assign condition is required')
if (!validateConditions(assignmentRuleData.value.assignConditionJson)) {
assignmentRuleErrors.value.assignConditionError = __(
'Assign conditions are invalid',
)
} else {
assignmentRuleErrors.value.assignConditionError = ''
}
break
case 'unassignCondition':
if (skipConditionCheck) {
break
}
if (
assignmentRuleData.value.unassignConditionJson?.length > 0 &&
!validateConditions(assignmentRuleData.value.unassignConditionJson)
) {
assignmentRuleErrors.value.unassignConditionError = __(
'Unassign conditions are invalid',
)
} else {
assignmentRuleErrors.value.unassignConditionError = ''
}
break
case 'users':
assignmentRuleErrors.value.users =
assignmentRuleData.value.users?.length > 0
? ''
: __('Users are required')
break
case 'assignmentDays':
assignmentRuleErrors.value.assignmentDays =
assignmentRuleData.value.assignmentDays?.length > 0
? ''
: __('Assignment days are required')
break
default:
break
}
}
if (key) {
validateField(key)
} else {
Object.keys(assignmentRuleErrors.value).forEach(validateField)
}
return assignmentRuleErrors.value
}
const resetAssignmentRuleData = () => {
assignmentRuleData.value = {
assignCondition: '',
unassignCondition: '',
assignConditionJson: [],
unassignConditionJson: [],
rule: 'Round Robin',
priority: 1,
users: [],
disabled: false,
description: '',
name: '',
assignmentRuleName: '',
assignmentDays: defaultAssignmentDays,
documentType: 'CRM Lead',
}
}
const assignmentRuleErrors = ref({
assignmentRuleName: '',
assignCondition: '',
assignConditionError: '',
unassignConditionError: '',
users: '',
description: '',
assignmentDays: '',
})
const resetAssignmentRuleErrors = () => {
Object.keys(assignmentRuleErrors.value).forEach((key) => {
assignmentRuleErrors.value[key] = ''
})
}
provide('assignmentRuleData', assignmentRuleData)
provide('assignmentRuleErrors', assignmentRuleErrors)
provide('validateAssignmentRule', validateAssignmentRule)
provide('resetAssignmentRuleData', resetAssignmentRuleData)
provide('resetAssignmentRuleErrors', resetAssignmentRuleErrors)
const getAssignmentRuleData = createResource({
url: 'frappe.client.get',
params: {
doctype: 'Assignment Rule',
name: step.value.data?.name,
},
auto: Boolean(step.value.data),
onSuccess(data) {
assignmentRuleData.value = {
assignCondition: data.assign_condition,
unassignCondition: data.unassign_condition,
assignConditionJson: JSON.parse(data.assign_condition_json || '[]'),
unassignConditionJson: JSON.parse(data.unassign_condition_json || '[]'),
rule: data.rule,
priority: data.priority,
users: data.users,
disabled: data.disabled,
description: data.description,
name: data.name,
assignmentRuleName: data.name,
assignmentDays: data.assignment_days.map((day) => day.day),
documentType: data.document_type,
}
initialData.value = JSON.stringify(assignmentRuleData.value)
const conditionsAvailable =
assignmentRuleData.value.assignCondition?.length > 0
const conditionsJsonAvailable =
assignmentRuleData.value.assignConditionJson?.length > 0
if (conditionsAvailable && !conditionsJsonAvailable) {
useNewUI.value = false
isOldSla.value = true
} else {
useNewUI.value = true
isOldSla.value = false
}
},
})
if (!step.value.data) {
initialData.value = JSON.stringify(assignmentRuleData.value)
}
const goBack = () => {
if (isDirty.value && !showConfirmDialog.value.show) {
$dialog({
title: __('Unsaved changes'),
message: __(
'Are you sure you want to go back? Unsaved changes will be lost.',
),
variant: 'solid',
actions: [
{
label: __('Go back'),
variant: 'solid',
onClick: (close) => {
updateStep('list', null)
close()
},
},
],
})
return
}
updateStep('list', null)
showConfirmDialog.value.show = false
}
const saveAssignmentRule = () => {
const validationErrors = validateAssignmentRule(undefined, !useNewUI.value)
if (Object.values(validationErrors).some((error) => error)) {
toast.error(
__('Invalid fields, check if all are filled in and values are correct.'),
)
return
}
if (step.value.data) {
if (isOldSla.value && useNewUI.value) {
showConfirmDialog.value = {
show: true,
title: __('Confirm overwrite'),
message: __(
'Your old condition will be overwritten. Are you sure you want to save?',
),
onConfirm: () => {
updateAssignmentRule()
showConfirmDialog.value.show = false
},
}
return
}
updateAssignmentRule()
} else {
createAssignmentRule()
}
}
const createAssignmentRule = () => {
isLoading.value = true
createResource({
url: 'frappe.client.insert',
params: {
doc: {
doctype: 'Assignment Rule',
document_type: assignmentRuleData.value.documentType,
rule: assignmentRuleData.value.rule,
priority: assignmentRuleData.value.priority,
users: assignmentRuleData.value.users,
disabled: assignmentRuleData.value.disabled,
description: assignmentRuleData.value.description,
assignment_days: assignmentRuleData.value.assignmentDays.map((day) => ({
day: day,
})),
name: assignmentRuleData.value.assignmentRuleName,
assignment_rule_name: assignmentRuleData.value.assignmentRuleName,
assign_condition: convertToConditions({
conditions: assignmentRuleData.value.assignConditionJson,
}),
unassign_condition: convertToConditions({
conditions: assignmentRuleData.value.unassignConditionJson,
}),
assign_condition_json: JSON.stringify(
assignmentRuleData.value.assignConditionJson,
),
unassign_condition_json: JSON.stringify(
assignmentRuleData.value.unassignConditionJson,
),
},
},
auto: true,
onSuccess(data) {
getAssignmentRuleData
.submit({
doctype: 'Assignment Rule',
name: data.name,
})
.then(() => {
isLoading.value = false
toast.success(__('Assignment rule created'))
})
updateStep('view', data)
},
onError: () => {
isLoading.value = false
},
})
}
const priorityOptions = [
{ label: 'Low', value: '0' },
{ label: 'Low-Medium', value: '1' },
{ label: 'Medium', value: '2' },
{ label: 'Medium-High', value: '3' },
{ label: 'High', value: '4' },
]
const updateAssignmentRule = async () => {
isLoading.value = true
await call('frappe.client.set_value', {
doctype: 'Assignment Rule',
name: assignmentRuleData.value.name,
fieldname: {
rule: assignmentRuleData.value.rule,
priority: assignmentRuleData.value.priority,
users: assignmentRuleData.value.users,
disabled: assignmentRuleData.value.disabled,
description: assignmentRuleData.value.description,
document_type: assignmentRuleData.value.documentType,
assignment_days: assignmentRuleData.value.assignmentDays.map((day) => ({
day: day,
})),
assign_condition: useNewUI.value
? convertToConditions({
conditions: assignmentRuleData.value.assignConditionJson,
})
: assignmentRuleData.value.assignCondition,
unassign_condition: useNewUI.value
? convertToConditions({
conditions: assignmentRuleData.value.unassignConditionJson,
})
: assignmentRuleData.value.unassignCondition,
assign_condition_json: useNewUI.value
? JSON.stringify(assignmentRuleData.value.assignConditionJson)
: null,
unassign_condition_json: useNewUI.value
? JSON.stringify(assignmentRuleData.value.unassignConditionJson)
: null,
},
}).catch((er) => {
const error =
er?.messages?.[0] ||
__('Some error occurred while updating assignment rule')
toast.error(error)
isLoading.value = false
})
if (
assignmentRuleData.value.name !==
assignmentRuleData.value.assignmentRuleName
) {
await call('frappe.client.rename_doc', {
doctype: 'Assignment Rule',
old_name: assignmentRuleData.value.name,
new_name: assignmentRuleData.value.assignmentRuleName,
}).catch(async (er) => {
const error =
er?.messages?.[0] ||
__('Some error occurred while renaming assignment rule')
toast.error(error)
// Reset assignment rule to previous state
await getAssignmentRuleData.reload()
isLoading.value = false
})
await getAssignmentRuleData.submit({
doctype: 'Assignment Rule',
name: assignmentRuleData.value.assignmentRuleName,
})
} else {
getAssignmentRuleData.reload()
}
isLoading.value = false
toast.success(__('Assignment rule updated'))
}
watch(
assignmentRuleData,
(newVal) => {
if (!initialData.value) return
isDirty.value = JSON.stringify(newVal) != initialData.value
if (isDirty.value) {
disableSettingModalOutsideClick.value = true
} else {
disableSettingModalOutsideClick.value = false
}
},
{ deep: true },
)
const beforeUnloadHandler = (event) => {
if (!isDirty.value) return
event.preventDefault()
event.returnValue = true
}
onMounted(() => {
addEventListener('beforeunload', beforeUnloadHandler)
})
onUnmounted(() => {
resetAssignmentRuleErrors()
resetAssignmentRuleData()
removeEventListener('beforeunload', beforeUnloadHandler)
disableSettingModalOutsideClick.value = false
})
</script>

View File

@ -0,0 +1,48 @@
<template>
<div class="p-8 sticky top-0">
<div class="flex items-start justify-between">
<div class="flex flex-col gap-1">
<h1 class="text-xl font-semibold text-ink-gray-8">
{{ __('Assignment rules') }}
</h1>
<p class="text-p-base text-ink-gray-6 max-w-md">
{{
__(
'Assignment Rules automatically route leads or deals to the right team members based on predefined conditions.',
)
}}
</p>
</div>
<Button
:label="__('Create new')"
theme="gray"
variant="solid"
@click="goToNew()"
icon-left="plus"
/>
</div>
</div>
<div class="overflow-y-auto px-8 pb-6">
<AssignmentRulesList />
</div>
</template>
<script setup>
import { createResource } from 'frappe-ui'
import AssignmentRulesList from './AssignmentRulesList.vue'
import { inject, provide } from 'vue'
const updateStep = inject('updateStep')
const assignmentRulesListData = createResource({
url: 'crm.api.assignment_rule.get_assignment_rules_list',
cache: ['assignmentRules', 'get_assignment_rules_list'],
auto: true,
})
provide('assignmentRulesList', assignmentRulesListData)
const goToNew = () => {
updateStep('view', null)
}
</script>

View File

@ -9,7 +9,7 @@
:label="__(template.name)"
size="md"
@click="() => emit('updateStep', 'template-list')"
class="text-xl !h-7 font-semibold hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5"
class="cursor-pointer hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5 font-semibold text-xl hover:opacity-70 !pr-0 !max-w-96 !justify-start"
/>
</div>
<div class="flex item-center space-x-2 w-3/12 justify-end">

View File

@ -11,7 +11,7 @@
"
size="md"
@click="() => emit('updateStep', 'template-list')"
class="text-xl !h-7 font-semibold hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5"
class="cursor-pointer hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5 font-semibold text-xl hover:opacity-70 !pr-0 !max-w-96 !justify-start"
/>
</div>
<div class="flex item-center space-x-2 w-3/12 justify-end">

View File

@ -9,7 +9,7 @@
:label="__('Brand settings')"
size="md"
@click="() => emit('updateStep', 'general-settings')"
class="text-xl !h-7 font-semibold hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5"
class="cursor-pointer hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5 font-semibold text-xl hover:opacity-70 !justify-start"
/>
<Badge
v-if="settings.isDirty"

View File

@ -9,7 +9,7 @@
:label="__('Currency & Exchange rate provider')"
size="md"
@click="() => emit('updateStep', 'general-settings')"
class="text-xl !h-7 font-semibold hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5"
class="cursor-pointer hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5 font-semibold text-xl hover:opacity-70 !justify-start"
/>
<Badge
v-if="settings.isDirty"

View File

@ -9,7 +9,7 @@
:label="__('Home actions')"
size="md"
@click="() => emit('updateStep', 'general-settings')"
class="text-xl !h-7 font-semibold hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5"
class="cursor-pointer hover:bg-transparent focus:bg-transparent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:none active:bg-transparent active:outline-none active:ring-0 active:ring-offset-0 active:text-ink-gray-5 font-semibold text-xl hover:opacity-70 !pr-0 !max-w-96 !justify-start"
/>
</div>
<div class="flex item-center space-x-2 w-3/12 justify-end">

2286
yarn.lock

File diff suppressed because it is too large Load Diff