Merge pull request #342 from frappe/develop
chore: Merge develop to main
This commit is contained in:
commit
f452a99b6b
@ -51,7 +51,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 text-base text-gray-800">
|
||||
<div class="flex flex-col gap-1 text-base leading-5 text-gray-800">
|
||||
<div>{{ activity.data.subject }}</div>
|
||||
<div>
|
||||
<span class="mr-1 text-gray-600"> {{ __('To') }}: </span>
|
||||
<span>{{ activity.data.recipients }}</span>
|
||||
@ -66,12 +67,8 @@
|
||||
</span>
|
||||
<span v-if="activity.data.bcc">{{ activity.data.bcc }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="mr-1 text-gray-600"> {{ __('Subject') }}: </span>
|
||||
<span>{{ activity.data.subject }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-0 border-t my-3.5 border-gray-200" />
|
||||
<div class="border-0 border-t mt-3 mb-1 border-gray-200" />
|
||||
<EmailContent :content="activity.data.content" />
|
||||
<div v-if="activity.data?.attachments?.length" class="flex flex-wrap gap-2">
|
||||
<AttachmentItem
|
||||
@ -108,6 +105,8 @@ function reply(email, reply_all = false) {
|
||||
|
||||
if (!email.subject.startsWith('Re:')) {
|
||||
editor.subject = `Re: ${email.subject}`
|
||||
} else {
|
||||
editor.subject = email.subject
|
||||
}
|
||||
|
||||
if (reply_all) {
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
{{ __('Send Invites To') }}
|
||||
</h2>
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<label class="block text-xs text-gray-600 mb-1.5">
|
||||
{{ __('Invite by email') }}
|
||||
</label>
|
||||
<MultiValueInput
|
||||
v-model="invitees"
|
||||
:validate="validateEmail"
|
||||
@ -26,17 +29,17 @@
|
||||
<ErrorMessage class="mt-2" v-if="error" :message="error" />
|
||||
<template v-if="pendingInvitations.data?.length && !invitees.length">
|
||||
<div
|
||||
class="mt-4 flex items-center justify-between border-b py-2 text-base text-gray-600"
|
||||
class="mt-6 flex items-center justify-between py-4 text-base font-semibold"
|
||||
>
|
||||
<div class="w-4/5">{{ __('Pending Invites') }}</div>
|
||||
<div>{{ __('Pending Invites') }}</div>
|
||||
</div>
|
||||
<ul class="divide-y overflow-auto">
|
||||
<ul class="flex flex-col gap-1">
|
||||
<li
|
||||
class="flex items-center justify-between py-2"
|
||||
class="flex items-center justify-between px-2 py-1 rounded-lg bg-gray-50"
|
||||
v-for="user in pendingInvitations.data"
|
||||
:key="user.name"
|
||||
>
|
||||
<div class="w-4/5 text-base">
|
||||
<div class="text-base">
|
||||
<span class="text-gray-900">
|
||||
{{ user.email }}
|
||||
</span>
|
||||
@ -46,6 +49,7 @@
|
||||
<Tooltip text="Delete Invitation">
|
||||
<Button
|
||||
icon="x"
|
||||
variant="ghost"
|
||||
:loading="
|
||||
pendingInvitations.delete.loading &&
|
||||
pendingInvitations.delete.params.name === user.name
|
||||
|
||||
@ -1,5 +1,14 @@
|
||||
import './index.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createDialog } from './utils/dialogs'
|
||||
import { initSocket } from './socket'
|
||||
import router from './router'
|
||||
import translationPlugin from './translation'
|
||||
import { posthogPlugin } from './telemetry'
|
||||
import App from './App.vue'
|
||||
|
||||
import {
|
||||
FrappeUI,
|
||||
Button,
|
||||
@ -14,14 +23,6 @@ import {
|
||||
frappeRequest,
|
||||
FeatherIcon,
|
||||
} from 'frappe-ui'
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createDialog } from './utils/dialogs'
|
||||
import { initSocket } from './socket'
|
||||
import router from './router'
|
||||
import translationPlugin from './translation'
|
||||
import { posthogPlugin } from './telemetry'
|
||||
import App from './App.vue'
|
||||
|
||||
let globalComponents = {
|
||||
Button,
|
||||
|
||||
@ -18,7 +18,9 @@
|
||||
@click="showAssignmentModal = true"
|
||||
/>
|
||||
</component>
|
||||
<Dropdown :options="statusOptions('deal', updateField)">
|
||||
<Dropdown
|
||||
:options="statusOptions('deal', updateField, deal.data._customStatuses)"
|
||||
>
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
:label="deal.data.status"
|
||||
@ -212,7 +214,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Dropdown :options="contactOptions(contact.name)">
|
||||
<Dropdown :options="contactOptions(contact)">
|
||||
<Button
|
||||
icon="more-horizontal"
|
||||
class="text-gray-600"
|
||||
@ -339,6 +341,7 @@ import {
|
||||
createToast,
|
||||
setupAssignees,
|
||||
setupCustomActions,
|
||||
setupCustomStatuses,
|
||||
errorMessage,
|
||||
copyToClipboard,
|
||||
} from '@/utils'
|
||||
@ -380,8 +383,7 @@ const deal = createResource({
|
||||
params: { name: props.dealId },
|
||||
cache: ['deal', props.dealId],
|
||||
onSuccess: (data) => {
|
||||
setupAssignees(data)
|
||||
setupCustomActions(data, {
|
||||
let obj = {
|
||||
doc: data,
|
||||
$dialog,
|
||||
router,
|
||||
@ -389,7 +391,10 @@ const deal = createResource({
|
||||
createToast,
|
||||
deleteDoc: deleteDeal,
|
||||
call,
|
||||
})
|
||||
}
|
||||
setupAssignees(data)
|
||||
setupCustomStatuses(data, obj)
|
||||
setupCustomActions(data, obj)
|
||||
},
|
||||
})
|
||||
|
||||
@ -567,9 +572,9 @@ const _contact = ref({})
|
||||
function contactOptions(contact) {
|
||||
let options = [
|
||||
{
|
||||
label: __('Delete'),
|
||||
label: __('Remove'),
|
||||
icon: 'trash-2',
|
||||
onClick: () => removeContact(contact),
|
||||
onClick: () => removeContact(contact.name),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@ -431,7 +431,7 @@ function parseRows(rows) {
|
||||
...(deal.deal_owner && getUser(deal.deal_owner)),
|
||||
}
|
||||
} else if (row == '_assign') {
|
||||
let assignees = JSON.parse(deal._assign) || []
|
||||
let assignees = JSON.parse(deal._assign || '[]')
|
||||
if (!assignees.length && deal.deal_owner) {
|
||||
assignees = [deal.deal_owner]
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
@click="showAssignmentModal = true"
|
||||
/>
|
||||
</component>
|
||||
<Dropdown :options="statusOptions('lead', updateField)">
|
||||
<Dropdown :options="statusOptions('lead', updateField, lead.data._customStatuses)">
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
:label="lead.data.status"
|
||||
@ -308,6 +308,7 @@ import {
|
||||
createToast,
|
||||
setupAssignees,
|
||||
setupCustomActions,
|
||||
setupCustomStatuses,
|
||||
errorMessage,
|
||||
copyToClipboard,
|
||||
} from '@/utils'
|
||||
@ -354,8 +355,7 @@ const lead = createResource({
|
||||
params: { name: props.leadId },
|
||||
cache: ['lead', props.leadId],
|
||||
onSuccess: (data) => {
|
||||
setupAssignees(data)
|
||||
setupCustomActions(data, {
|
||||
let obj = {
|
||||
doc: data,
|
||||
$dialog,
|
||||
router,
|
||||
@ -363,7 +363,10 @@ const lead = createResource({
|
||||
createToast,
|
||||
deleteDoc: deleteLead,
|
||||
call,
|
||||
})
|
||||
}
|
||||
setupAssignees(data)
|
||||
setupCustomStatuses(data, obj)
|
||||
setupCustomActions(data, obj)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -448,7 +448,7 @@ function parseRows(rows) {
|
||||
...(lead.lead_owner && getUser(lead.lead_owner)),
|
||||
}
|
||||
} else if (row == '_assign') {
|
||||
let assignees = JSON.parse(lead._assign) || []
|
||||
let assignees = JSON.parse(lead._assign || '[]')
|
||||
if (!assignees.length && lead.lead_owner) {
|
||||
assignees = [lead.lead_owner]
|
||||
}
|
||||
|
||||
@ -9,7 +9,11 @@
|
||||
</template>
|
||||
</Breadcrumbs>
|
||||
<div class="absolute right-0">
|
||||
<Dropdown :options="statusOptions('deal', updateField)">
|
||||
<Dropdown
|
||||
:options="
|
||||
statusOptions('deal', updateField, deal.data._customStatuses)
|
||||
"
|
||||
>
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
:label="deal.data.status"
|
||||
@ -274,7 +278,12 @@ import Section from '@/components/Section.vue'
|
||||
import SectionFields from '@/components/SectionFields.vue'
|
||||
import SLASection from '@/components/SLASection.vue'
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import { createToast, setupAssignees, setupCustomActions } from '@/utils'
|
||||
import {
|
||||
createToast,
|
||||
setupAssignees,
|
||||
setupCustomActions,
|
||||
setupCustomStatuses,
|
||||
} from '@/utils'
|
||||
import { getView } from '@/utils/view'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { organizationsStore } from '@/stores/organizations'
|
||||
@ -313,8 +322,7 @@ const deal = createResource({
|
||||
params: { name: props.dealId },
|
||||
cache: ['deal', props.dealId],
|
||||
onSuccess: (data) => {
|
||||
setupAssignees(data)
|
||||
setupCustomActions(data, {
|
||||
let obj = {
|
||||
doc: data,
|
||||
$dialog,
|
||||
router,
|
||||
@ -322,7 +330,10 @@ const deal = createResource({
|
||||
createToast,
|
||||
deleteDoc: deleteDeal,
|
||||
call,
|
||||
})
|
||||
}
|
||||
setupAssignees(data)
|
||||
setupCustomStatuses(data, obj)
|
||||
setupCustomActions(data, obj)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -9,7 +9,11 @@
|
||||
</template>
|
||||
</Breadcrumbs>
|
||||
<div class="absolute right-0">
|
||||
<Dropdown :options="statusOptions('lead', updateField)">
|
||||
<Dropdown
|
||||
:options="
|
||||
statusOptions('lead', updateField, lead.data._customStatuses)
|
||||
"
|
||||
>
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
:label="lead.data.status"
|
||||
@ -195,7 +199,12 @@ import Section from '@/components/Section.vue'
|
||||
import SectionFields from '@/components/SectionFields.vue'
|
||||
import SLASection from '@/components/SLASection.vue'
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import { createToast, setupAssignees, setupCustomActions } from '@/utils'
|
||||
import {
|
||||
createToast,
|
||||
setupAssignees,
|
||||
setupCustomActions,
|
||||
setupCustomStatuses,
|
||||
} from '@/utils'
|
||||
import { getView } from '@/utils/view'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { contactsStore } from '@/stores/contacts'
|
||||
@ -236,8 +245,7 @@ const lead = createResource({
|
||||
params: { name: props.leadId },
|
||||
cache: ['lead', props.leadId],
|
||||
onSuccess: (data) => {
|
||||
setupAssignees(data)
|
||||
setupCustomActions(data, {
|
||||
let obj = {
|
||||
doc: data,
|
||||
$dialog,
|
||||
router,
|
||||
@ -245,7 +253,10 @@ const lead = createResource({
|
||||
createToast,
|
||||
deleteDoc: deleteLead,
|
||||
call,
|
||||
})
|
||||
}
|
||||
setupAssignees(data)
|
||||
setupCustomStatuses(data, obj)
|
||||
setupCustomActions(data, obj)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -64,9 +64,9 @@ export const statusesStore = defineStore('crm-statuses', () => {
|
||||
} else if (['gray', 'green'].includes(color)) {
|
||||
textColor = `!text-${color}-700`
|
||||
}
|
||||
|
||||
|
||||
let bgColor = `!bg-${color}-100 hover:!bg-${color}-200 active:!bg-${color}-300`
|
||||
|
||||
|
||||
return [textColor, onlyIcon ? '' : bgColor]
|
||||
}
|
||||
|
||||
@ -91,9 +91,17 @@ export const statusesStore = defineStore('crm-statuses', () => {
|
||||
return communicationStatuses[name]
|
||||
}
|
||||
|
||||
function statusOptions(doctype, action) {
|
||||
function statusOptions(doctype, action, statuses = []) {
|
||||
let statusesByName =
|
||||
doctype == 'deal' ? dealStatusesByName : leadStatusesByName
|
||||
|
||||
if (statuses.length) {
|
||||
statusesByName = statuses.reduce((acc, status) => {
|
||||
acc[status] = statusesByName[status]
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
let options = []
|
||||
for (const status in statusesByName) {
|
||||
options.push({
|
||||
|
||||
@ -131,6 +131,27 @@ function getActionsFromScript(script, obj) {
|
||||
return formScript?.actions || []
|
||||
}
|
||||
|
||||
function getStatusFromScript(script, obj) {
|
||||
let scriptFn = new Function(script + '\nreturn setupForm')()
|
||||
let formScript = scriptFn(obj)
|
||||
return formScript?.statuses || []
|
||||
}
|
||||
|
||||
export function setupCustomStatuses(data, obj) {
|
||||
if (!data._form_script) return []
|
||||
|
||||
let statuses = []
|
||||
if (Array.isArray(data._form_script)) {
|
||||
data._form_script.forEach((script) => {
|
||||
statuses = statuses.concat(getStatusFromScript(script, obj))
|
||||
})
|
||||
} else {
|
||||
statuses = getStatusFromScript(data._form_script, data)
|
||||
}
|
||||
|
||||
data._customStatuses = statuses
|
||||
}
|
||||
|
||||
export function setupCustomActions(data, obj) {
|
||||
if (!data._form_script) return []
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user