Merge pull request #133 from frappe/develop
chore: Merge develop to main
This commit is contained in:
commit
e5a2ab4b54
@ -240,7 +240,8 @@ def get_list_data(
|
||||
"views": get_views(doctype),
|
||||
"total_count": len(frappe.get_list(doctype, filters=filters)),
|
||||
"row_count": len(data),
|
||||
"form_script": get_form_script(doctype)
|
||||
"form_script": get_form_script(doctype),
|
||||
"list_script": get_form_script(doctype, "List"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,8 +1,38 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("CRM Form Script", {
|
||||
// refresh(frm) {
|
||||
frappe.ui.form.on("CRM Form Script", {
|
||||
refresh(frm) {
|
||||
frm.set_query("dt", {
|
||||
filters: {
|
||||
istable: 0,
|
||||
},
|
||||
});
|
||||
},
|
||||
view(frm) {
|
||||
let has_form_boilerplate = frm.doc.script.includes(
|
||||
"function setupForm("
|
||||
);
|
||||
let has_list_boilerplate = frm.doc.script.includes(
|
||||
"function setupList("
|
||||
);
|
||||
|
||||
// },
|
||||
// });
|
||||
if (frm.doc.view == "Form" && !has_form_boilerplate) {
|
||||
frm.doc.script = `
|
||||
function setupForm({ doc }) {
|
||||
return {
|
||||
actions: [],
|
||||
}
|
||||
}`.trim();
|
||||
}
|
||||
if (frm.doc.view == "List" && !has_list_boilerplate) {
|
||||
frm.doc.script = `
|
||||
function setupList({ list }) {
|
||||
return {
|
||||
actions: [],
|
||||
bulk_actions: [],
|
||||
}
|
||||
}`.trim();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"dt",
|
||||
"view",
|
||||
"column_break_gboh",
|
||||
"enabled",
|
||||
"section_break_xeox",
|
||||
@ -36,16 +37,26 @@
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"default": "function setupForm({ doc }) {\n return {\n actions: []\n }\n}",
|
||||
"default": "function setupForm({ doc }) {\n return {\n actions: [],\n }\n}",
|
||||
"documentation_url": "https://docs.frappe.io/crm/custom-actions",
|
||||
"fieldname": "script",
|
||||
"fieldtype": "Code",
|
||||
"label": "Script",
|
||||
"options": "JS"
|
||||
},
|
||||
{
|
||||
"default": "Form",
|
||||
"fieldname": "view",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Apply To",
|
||||
"options": "Form\nList",
|
||||
"set_only_once": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-19 21:52:46.078626",
|
||||
"modified": "2024-04-10 18:27:17.617602",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "CRM Form Script",
|
||||
|
||||
@ -14,6 +14,7 @@ class CRMFormScript(Document):
|
||||
if self.dt and self.enabled:
|
||||
filters = {
|
||||
"dt": self.dt,
|
||||
"view": self.view,
|
||||
"enabled": 1,
|
||||
}
|
||||
if self.name:
|
||||
@ -27,13 +28,14 @@ class CRMFormScript(Document):
|
||||
frappe.DuplicateEntryError,
|
||||
)
|
||||
|
||||
def get_form_script(dt):
|
||||
"""Returns the script for the given doctype"""
|
||||
def get_form_script(dt, view="Form"):
|
||||
"""Returns the form script for the given doctype"""
|
||||
FormScript = frappe.qb.DocType("CRM Form Script")
|
||||
query = (
|
||||
frappe.qb.from_(FormScript)
|
||||
.select("script")
|
||||
.where(FormScript.dt == dt)
|
||||
.where(FormScript.view == view)
|
||||
.where(FormScript.enabled == 1)
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 03115629ffdb3712accd57123fb1d6b882a15e67
|
||||
Subproject commit 05a8eca589d23d44f55cfe82ae157fd5de997abb
|
||||
@ -13,7 +13,7 @@
|
||||
"@vueuse/core": "^10.3.0",
|
||||
"@vueuse/integrations": "^10.3.0",
|
||||
"feather-icons": "^4.28.0",
|
||||
"frappe-ui": "^0.1.45",
|
||||
"frappe-ui": "^0.1.51",
|
||||
"mime": "^4.0.1",
|
||||
"pinia": "^2.0.33",
|
||||
"socket.io-client": "^4.7.2",
|
||||
|
||||
@ -162,14 +162,16 @@
|
||||
>
|
||||
<DotIcon class="h-2.5 w-2.5 text-gray-600" :radius="2" />
|
||||
</div>
|
||||
<div v-if="task.due_date" class="flex gap-2">
|
||||
<CalendarIcon />
|
||||
<div v-if="task.due_date">
|
||||
<Tooltip
|
||||
:text="
|
||||
dateFormat(task.due_date, 'ddd, MMM D, YYYY | hh:mm a')
|
||||
"
|
||||
>
|
||||
{{ dateFormat(task.due_date, 'D MMM, hh:mm a') }}
|
||||
<div class="flex gap-2">
|
||||
<CalendarIcon />
|
||||
<div>{{ dateFormat(task.due_date, 'D MMM, hh:mm a') }}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="flex items-center justify-center">
|
||||
@ -258,11 +260,10 @@
|
||||
{{ call.type == 'Incoming' ? 'Inbound' : 'Outbound' }} Call
|
||||
</div>
|
||||
<div>
|
||||
<Tooltip
|
||||
class="text-sm text-gray-600"
|
||||
:text="dateFormat(call.creation, dateTooltipFormat)"
|
||||
>
|
||||
{{ timeAgo(call.creation) }}
|
||||
<Tooltip :text="dateFormat(call.creation, dateTooltipFormat)">
|
||||
<div class="text-sm text-gray-600">
|
||||
{{ timeAgo(call.creation) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@ -374,10 +375,11 @@
|
||||
<span>{{ activity.data.sender_full_name }}</span>
|
||||
<span>·</span>
|
||||
<Tooltip
|
||||
class="text-sm text-gray-600"
|
||||
:text="dateFormat(activity.creation, dateTooltipFormat)"
|
||||
>
|
||||
{{ timeAgo(activity.creation) }}
|
||||
<div class="text-sm text-gray-600">
|
||||
{{ timeAgo(activity.creation) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="flex gap-0.5">
|
||||
@ -456,11 +458,10 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="ml-auto whitespace-nowrap">
|
||||
<Tooltip
|
||||
:text="dateFormat(activity.creation, dateTooltipFormat)"
|
||||
class="text-gray-600"
|
||||
>
|
||||
{{ timeAgo(activity.creation) }}
|
||||
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||
<div class="text-gray-600">
|
||||
{{ timeAgo(activity.creation) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@ -481,11 +482,10 @@
|
||||
{{ activity.type == 'Incoming' ? 'Inbound' : 'Outbound' }} Call
|
||||
</div>
|
||||
<div>
|
||||
<Tooltip
|
||||
class="text-sm text-gray-600"
|
||||
:text="dateFormat(activity.creation, dateTooltipFormat)"
|
||||
>
|
||||
{{ timeAgo(activity.creation) }}
|
||||
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||
<div class="text-sm text-gray-600">
|
||||
{{ timeAgo(activity.creation) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@ -602,11 +602,10 @@
|
||||
</div>
|
||||
|
||||
<div class="ml-auto whitespace-nowrap">
|
||||
<Tooltip
|
||||
:text="dateFormat(activity.creation, dateTooltipFormat)"
|
||||
class="text-gray-600"
|
||||
>
|
||||
{{ timeAgo(activity.creation) }}
|
||||
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||
<div class="text-gray-600">
|
||||
{{ timeAgo(activity.creation) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@ -665,11 +664,10 @@
|
||||
</div>
|
||||
|
||||
<div class="ml-auto whitespace-nowrap">
|
||||
<Tooltip
|
||||
:text="dateFormat(activity.creation, dateTooltipFormat)"
|
||||
class="text-gray-600"
|
||||
>
|
||||
{{ timeAgo(activity.creation) }}
|
||||
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||
<div class="text-gray-600">
|
||||
{{ timeAgo(activity.creation) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,55 +1,65 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between gap-7">
|
||||
<div v-show="!editMode">{{ option.value }}</div>
|
||||
<TextInput
|
||||
ref="inputRef"
|
||||
v-show="editMode"
|
||||
v-model="option.value"
|
||||
class="w-full"
|
||||
:placeholder="option.placeholder"
|
||||
@blur.stop="saveOption"
|
||||
@keydown.enter.stop="(e) => e.target.blur()"
|
||||
/>
|
||||
<div
|
||||
class="group flex w-full items-center justify-between rounded bg-transparent p-1 pl-2 text-base text-gray-800 transition-colors hover:bg-gray-200 active:bg-gray-300"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-7">
|
||||
<div v-show="!editMode">{{ option.value }}</div>
|
||||
<TextInput
|
||||
ref="inputRef"
|
||||
v-show="editMode"
|
||||
v-model="option.value"
|
||||
class="w-full"
|
||||
:placeholder="option.placeholder"
|
||||
@blur.stop="saveOption"
|
||||
@keydown.enter.stop="(e) => e.target.blur()"
|
||||
/>
|
||||
|
||||
<div class="actions flex items-center justify-center">
|
||||
<Tooltip text="Set As Primary" v-if="!isNew && !option.selected">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="opacity-0 hover:bg-gray-300 group-hover:opacity-100"
|
||||
@click="option.onClick"
|
||||
>
|
||||
<SuccessIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Edit">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="opacity-0 hover:bg-gray-300 group-hover:opacity-100"
|
||||
@click="toggleEditMode"
|
||||
>
|
||||
<EditIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Delete">
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="x"
|
||||
size="sm"
|
||||
class="opacity-0 hover:bg-gray-300 group-hover:opacity-100"
|
||||
@click="() => option.onDelete(option, isNew)"
|
||||
/>
|
||||
</Tooltip>
|
||||
<div class="actions flex items-center justify-center">
|
||||
<Tooltip text="Set As Primary" v-if="!isNew && !option.selected">
|
||||
<div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="opacity-0 hover:bg-gray-300 group-hover:opacity-100"
|
||||
@click="option.onClick"
|
||||
>
|
||||
<SuccessIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip text="Edit">
|
||||
<div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="opacity-0 hover:bg-gray-300 group-hover:opacity-100"
|
||||
@click="toggleEditMode"
|
||||
>
|
||||
<EditIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip text="Delete">
|
||||
<div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="x"
|
||||
size="sm"
|
||||
class="opacity-0 hover:bg-gray-300 group-hover:opacity-100"
|
||||
@click="() => option.onDelete(option, isNew)"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<FeatherIcon
|
||||
v-if="option.selected"
|
||||
name="check"
|
||||
class="text-primary-500 h-4 w-6"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<FeatherIcon
|
||||
v-if="option.selected"
|
||||
name="check"
|
||||
class="text-primary-500 h-4 w-6"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -40,13 +40,14 @@
|
||||
<FeatherIcon :name="item.icon" class="h-3 w-3" />
|
||||
</div>
|
||||
</template>
|
||||
<Tooltip
|
||||
:text="item.label"
|
||||
<div
|
||||
v-if="['modified', 'creation'].includes(column.key)"
|
||||
class="truncate text-base"
|
||||
>
|
||||
{{ item.timeAgo }}
|
||||
</Tooltip>
|
||||
<Tooltip :text="item.label">
|
||||
<div>{{ item.timeAgo }}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div v-else-if="column.key === 'status'" class="truncate text-base">
|
||||
<Badge
|
||||
:variant="'subtle'"
|
||||
@ -102,7 +103,7 @@ import {
|
||||
Dropdown,
|
||||
call,
|
||||
} from 'frappe-ui'
|
||||
import { setupBulkActions, createToast } from '@/utils'
|
||||
import { setupListActions, createToast } from '@/utils'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
@ -180,6 +181,7 @@ function deleteValues(selections, unselectAll) {
|
||||
}
|
||||
|
||||
const customBulkActions = ref([])
|
||||
const customListActions = ref([])
|
||||
|
||||
function bulkActions(selections, unselectAll) {
|
||||
let actions = [
|
||||
@ -208,7 +210,18 @@ function bulkActions(selections, unselectAll) {
|
||||
|
||||
onMounted(() => {
|
||||
if (!list.value?.data) return
|
||||
setupBulkActions(list.value.data)
|
||||
setupListActions(list.value.data, {
|
||||
list: list.value,
|
||||
call,
|
||||
createToast,
|
||||
$dialog,
|
||||
router,
|
||||
})
|
||||
customBulkActions.value = list.value?.data?.bulkActions || []
|
||||
customListActions.value = list.value?.data?.listActions || []
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
customListActions,
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -50,13 +50,14 @@
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
</div>
|
||||
</template>
|
||||
<Tooltip
|
||||
:text="item.label"
|
||||
<div
|
||||
v-if="['modified', 'creation'].includes(column.key)"
|
||||
class="truncate text-base"
|
||||
>
|
||||
{{ item.timeAgo }}
|
||||
</Tooltip>
|
||||
<Tooltip :text="item.label">
|
||||
<div>{{ item.timeAgo }}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div v-else-if="column.type === 'Check'">
|
||||
<FormControl
|
||||
type="checkbox"
|
||||
@ -102,7 +103,7 @@
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import EditValueModal from '@/components/Modals/EditValueModal.vue'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { createToast } from '@/utils'
|
||||
import { setupListActions, createToast } from '@/utils'
|
||||
import {
|
||||
Avatar,
|
||||
ListView,
|
||||
@ -116,7 +117,8 @@ import {
|
||||
Dropdown,
|
||||
call,
|
||||
} from 'frappe-ui'
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
@ -149,6 +151,8 @@ const emit = defineEmits([
|
||||
const pageLengthCount = defineModel()
|
||||
const list = defineModel('list')
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const { $dialog } = globalStore()
|
||||
|
||||
watch(pageLengthCount, (val, old_value) => {
|
||||
@ -198,6 +202,8 @@ function deleteValues(selections, unselectAll) {
|
||||
})
|
||||
}
|
||||
|
||||
const customListActions = ref([])
|
||||
|
||||
function bulkActions(selections, unselectAll) {
|
||||
let actions = [
|
||||
{
|
||||
@ -211,4 +217,21 @@ function bulkActions(selections, unselectAll) {
|
||||
]
|
||||
return actions
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!list.value?.data) return
|
||||
setupListActions(list.value.data, {
|
||||
list: list.value,
|
||||
call,
|
||||
createToast,
|
||||
$dialog,
|
||||
router,
|
||||
})
|
||||
// customBulkActions.value = list.value?.data?.bulkActions || []
|
||||
customListActions.value = list.value?.data?.listActions || []
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
customListActions,
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -58,8 +58,7 @@
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
</div>
|
||||
</template>
|
||||
<Tooltip
|
||||
:text="item.label"
|
||||
<div
|
||||
v-if="
|
||||
[
|
||||
'modified',
|
||||
@ -71,8 +70,10 @@
|
||||
"
|
||||
class="truncate text-base"
|
||||
>
|
||||
{{ item.timeAgo }}
|
||||
</Tooltip>
|
||||
<Tooltip :text="item.label">
|
||||
<div>{{ item.timeAgo }}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="column.key === 'sla_status'"
|
||||
class="truncate text-base"
|
||||
@ -145,7 +146,7 @@ import {
|
||||
call,
|
||||
Tooltip,
|
||||
} from 'frappe-ui'
|
||||
import { setupBulkActions, createToast } from '@/utils'
|
||||
import { setupListActions, createToast } from '@/utils'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
@ -233,6 +234,7 @@ function deleteValues(selections, unselectAll) {
|
||||
}
|
||||
|
||||
const customBulkActions = ref([])
|
||||
const customListActions = ref([])
|
||||
|
||||
function bulkActions(selections, unselectAll) {
|
||||
let actions = [
|
||||
@ -265,7 +267,18 @@ function bulkActions(selections, unselectAll) {
|
||||
|
||||
onMounted(() => {
|
||||
if (!list.value?.data) return
|
||||
setupBulkActions(list.value.data)
|
||||
setupListActions(list.value.data, {
|
||||
list: list.value,
|
||||
call,
|
||||
createToast,
|
||||
$dialog,
|
||||
router,
|
||||
})
|
||||
customBulkActions.value = list.value?.data?.bulkActions || []
|
||||
customListActions.value = list.value?.data?.listActions || []
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
customListActions,
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -26,13 +26,14 @@
|
||||
<!-- <template #prefix>
|
||||
|
||||
</template> -->
|
||||
<Tooltip
|
||||
:text="item.label"
|
||||
<div
|
||||
v-if="['modified', 'creation'].includes(column.key)"
|
||||
class="truncate text-base"
|
||||
>
|
||||
{{ item.timeAgo }}
|
||||
</Tooltip>
|
||||
<Tooltip :text="item.label">
|
||||
<div>{{ item.timeAgo }}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div v-else-if="column.key === 'status'" class="truncate text-base">
|
||||
<Badge
|
||||
:variant="'subtle'"
|
||||
@ -84,7 +85,7 @@
|
||||
<script setup>
|
||||
import EditValueModal from '@/components/Modals/EditValueModal.vue'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { createToast } from '@/utils'
|
||||
import { setupListActions, createToast } from '@/utils'
|
||||
import {
|
||||
ListView,
|
||||
ListHeader,
|
||||
@ -97,7 +98,8 @@ import {
|
||||
call,
|
||||
Tooltip,
|
||||
} from 'frappe-ui'
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
@ -131,6 +133,8 @@ const emit = defineEmits([
|
||||
const pageLengthCount = defineModel()
|
||||
const list = defineModel('list')
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const { $dialog } = globalStore()
|
||||
|
||||
watch(pageLengthCount, (val, old_value) => {
|
||||
@ -180,6 +184,8 @@ function deleteValues(selections, unselectAll) {
|
||||
})
|
||||
}
|
||||
|
||||
const customListActions = ref([])
|
||||
|
||||
function bulkActions(selections, unselectAll) {
|
||||
let actions = [
|
||||
{
|
||||
@ -193,4 +199,21 @@ function bulkActions(selections, unselectAll) {
|
||||
]
|
||||
return actions
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!list.value?.data) return
|
||||
setupListActions(list.value.data, {
|
||||
list: list.value,
|
||||
call,
|
||||
createToast,
|
||||
$dialog,
|
||||
router,
|
||||
})
|
||||
// customBulkActions.value = list.value?.data?.bulkActions || []
|
||||
customListActions.value = list.value?.data?.listActions || []
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
customListActions,
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -67,8 +67,7 @@
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
</div>
|
||||
</template>
|
||||
<Tooltip
|
||||
:text="item.label"
|
||||
<div
|
||||
v-if="
|
||||
[
|
||||
'modified',
|
||||
@ -80,8 +79,10 @@
|
||||
"
|
||||
class="truncate text-base"
|
||||
>
|
||||
{{ item.timeAgo }}
|
||||
</Tooltip>
|
||||
<Tooltip :text="item.label">
|
||||
<div>{{ item.timeAgo }}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="column.key === 'sla_status'"
|
||||
class="truncate text-base"
|
||||
@ -154,7 +155,7 @@ import {
|
||||
call,
|
||||
Tooltip,
|
||||
} from 'frappe-ui'
|
||||
import { setupBulkActions, createToast } from '@/utils'
|
||||
import { setupListActions, createToast } from '@/utils'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
@ -242,6 +243,7 @@ function deleteValues(selections, unselectAll) {
|
||||
}
|
||||
|
||||
const customBulkActions = ref([])
|
||||
const customListActions = ref([])
|
||||
|
||||
function bulkActions(selections, unselectAll) {
|
||||
let actions = [
|
||||
@ -274,7 +276,18 @@ function bulkActions(selections, unselectAll) {
|
||||
|
||||
onMounted(() => {
|
||||
if (!list.value?.data) return
|
||||
setupBulkActions(list.value.data)
|
||||
setupListActions(list.value.data, {
|
||||
list: list.value,
|
||||
call,
|
||||
createToast,
|
||||
$dialog,
|
||||
router,
|
||||
})
|
||||
customBulkActions.value = list.value?.data?.bulkActions || []
|
||||
customListActions.value = list.value?.data?.listActions || []
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
customListActions,
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -37,13 +37,14 @@
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<Tooltip
|
||||
:text="item.label"
|
||||
<div
|
||||
v-if="['modified', 'creation'].includes(column.key)"
|
||||
class="truncate text-base"
|
||||
>
|
||||
{{ item.timeAgo }}
|
||||
</Tooltip>
|
||||
<Tooltip :text="item.label">
|
||||
<div>{{ item.timeAgo }}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div v-else-if="column.type === 'Check'">
|
||||
<FormControl
|
||||
type="checkbox"
|
||||
@ -87,7 +88,7 @@
|
||||
<script setup>
|
||||
import EditValueModal from '@/components/Modals/EditValueModal.vue'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { createToast } from '@/utils'
|
||||
import { setupListActions, createToast } from '@/utils'
|
||||
import {
|
||||
Avatar,
|
||||
ListView,
|
||||
@ -101,7 +102,8 @@ import {
|
||||
Dropdown,
|
||||
call,
|
||||
} from 'frappe-ui'
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
@ -134,6 +136,8 @@ const emit = defineEmits([
|
||||
const pageLengthCount = defineModel()
|
||||
const list = defineModel('list')
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const { $dialog } = globalStore()
|
||||
|
||||
watch(pageLengthCount, (val, old_value) => {
|
||||
@ -183,6 +187,8 @@ function deleteValues(selections, unselectAll) {
|
||||
})
|
||||
}
|
||||
|
||||
const customListActions = ref([])
|
||||
|
||||
function bulkActions(selections, unselectAll) {
|
||||
let actions = [
|
||||
{
|
||||
@ -196,4 +202,21 @@ function bulkActions(selections, unselectAll) {
|
||||
]
|
||||
return actions
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!list.value?.data) return
|
||||
setupListActions(list.value.data, {
|
||||
list: list.value,
|
||||
call,
|
||||
createToast,
|
||||
$dialog,
|
||||
router,
|
||||
})
|
||||
// customBulkActions.value = list.value?.data?.bulkActions || []
|
||||
customListActions.value = list.value?.data?.listActions || []
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
customListActions,
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -19,18 +19,16 @@
|
||||
v-slot="{ idx, column, item }"
|
||||
:row="row"
|
||||
>
|
||||
<Tooltip
|
||||
v-if="column.key === 'due_date'"
|
||||
class="flex items-center gap-2 truncate text-base"
|
||||
:text="dateFormat(item, 'ddd, MMM D, YYYY | hh:mm a')"
|
||||
>
|
||||
<div>
|
||||
<CalendarIcon />
|
||||
</div>
|
||||
<div v-if="item" class="truncate">
|
||||
{{ dateFormat(item, 'D MMM, hh:mm a') }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div v-if="column.key === 'due_date'">
|
||||
<Tooltip :text="dateFormat(item, 'ddd, MMM D, YYYY | hh:mm a')">
|
||||
<div class="flex items-center gap-2 truncate text-base">
|
||||
<CalendarIcon />
|
||||
<div v-if="item" class="truncate">
|
||||
{{ dateFormat(item, 'D MMM, hh:mm a') }}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<ListRowItem
|
||||
v-else
|
||||
:item="item"
|
||||
@ -53,13 +51,14 @@
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<Tooltip
|
||||
:text="item.label"
|
||||
<div
|
||||
v-if="['modified', 'creation'].includes(column.key)"
|
||||
class="truncate text-base"
|
||||
>
|
||||
{{ item.timeAgo }}
|
||||
</Tooltip>
|
||||
<Tooltip :text="item.label">
|
||||
<div>{{ item.timeAgo }}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div v-else-if="column.type === 'Check'">
|
||||
<FormControl
|
||||
type="checkbox"
|
||||
@ -107,7 +106,7 @@ import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
|
||||
import EditValueModal from '@/components/Modals/EditValueModal.vue'
|
||||
import { dateFormat } from '@/utils'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { createToast } from '@/utils'
|
||||
import { setupListActions, createToast } from '@/utils'
|
||||
import {
|
||||
Avatar,
|
||||
ListView,
|
||||
@ -121,7 +120,8 @@ import {
|
||||
call,
|
||||
Tooltip,
|
||||
} from 'frappe-ui'
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
@ -155,6 +155,8 @@ const emit = defineEmits([
|
||||
const pageLengthCount = defineModel()
|
||||
const list = defineModel('list')
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const { $dialog } = globalStore()
|
||||
|
||||
watch(pageLengthCount, (val, old_value) => {
|
||||
@ -204,6 +206,8 @@ function deleteValues(selections, unselectAll) {
|
||||
})
|
||||
}
|
||||
|
||||
const customListActions = ref([])
|
||||
|
||||
function bulkActions(selections, unselectAll) {
|
||||
let actions = [
|
||||
{
|
||||
@ -217,4 +221,21 @@ function bulkActions(selections, unselectAll) {
|
||||
]
|
||||
return actions
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!list.value?.data) return
|
||||
setupListActions(list.value.data, {
|
||||
list: list.value,
|
||||
call,
|
||||
createToast,
|
||||
$dialog,
|
||||
router,
|
||||
})
|
||||
// customBulkActions.value = list.value?.data?.bulkActions || []
|
||||
customListActions.value = list.value?.data?.listActions || []
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
customListActions,
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -34,7 +34,9 @@
|
||||
</template>
|
||||
<template #item-label="{ option }">
|
||||
<Tooltip :text="option.value">
|
||||
{{ getUser(option.value).full_name }}
|
||||
<div class="cursor-pointer">
|
||||
{{ getUser(option.value).full_name }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</template>
|
||||
</Link>
|
||||
|
||||
@ -92,15 +92,13 @@
|
||||
<div
|
||||
class="my-2 space-y-1.5 divide-y rounded-lg border border-gray-100 bg-white p-1.5 shadow-xl"
|
||||
>
|
||||
<div class="">
|
||||
<div
|
||||
<div>
|
||||
<DropdownItem
|
||||
v-if="field.options?.length"
|
||||
v-for="option in field.options"
|
||||
:key="option.value"
|
||||
class="group flex w-full items-center justify-between rounded bg-transparent p-1 pl-2 text-base text-gray-800 transition-colors hover:bg-gray-200 active:bg-gray-300"
|
||||
>
|
||||
<DropdownItem :option="option" />
|
||||
</div>
|
||||
:key="option.name"
|
||||
:option="option"
|
||||
/>
|
||||
<div v-else>
|
||||
<div class="p-1.5 px-7 text-base text-gray-500">
|
||||
No {{ field.label }} Available
|
||||
@ -166,7 +164,7 @@ import CertificateIcon from '@/components/Icons/CertificateIcon.vue'
|
||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import Dropdown from '@/components/frappe-ui/Dropdown.vue'
|
||||
import { call } from 'frappe-ui'
|
||||
import { Tooltip, call } from 'frappe-ui'
|
||||
import { ref, nextTick, watch, computed, h } from 'vue'
|
||||
import { createToast } from '@/utils'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@ -21,7 +21,9 @@
|
||||
v-if="task?.reference_docname"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:label="task.reference_doctype == 'CRM Deal' ? 'Open Deal' : 'Open Lead'"
|
||||
:label="
|
||||
task.reference_doctype == 'CRM Deal' ? 'Open Deal' : 'Open Lead'
|
||||
"
|
||||
@click="redirect()"
|
||||
>
|
||||
<template #suffix>
|
||||
@ -77,7 +79,9 @@
|
||||
</template>
|
||||
<template #item-label="{ option }">
|
||||
<Tooltip :text="option.value">
|
||||
{{ getUser(option.value).full_name }}
|
||||
<div class="cursor-pointer">
|
||||
{{ getUser(option.value).full_name }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</template>
|
||||
</Link>
|
||||
|
||||
@ -2,20 +2,20 @@
|
||||
<div
|
||||
v-if="avatars?.length"
|
||||
class="mr-1.5 flex cursor-pointer items-center"
|
||||
:class="[avatars?.length > 1 ? 'flex-row-reverse' : 'truncate [&>div]:truncate']"
|
||||
:class="[
|
||||
avatars?.length > 1 ? 'flex-row-reverse' : 'truncate [&>div]:truncate',
|
||||
]"
|
||||
>
|
||||
<Tooltip
|
||||
v-if="avatars?.length == 1"
|
||||
:text="avatars[0].name"
|
||||
class="flex items-center gap-2 text-base"
|
||||
>
|
||||
<Avatar
|
||||
shape="circle"
|
||||
:image="avatars[0].image"
|
||||
:label="avatars[0].label"
|
||||
size="sm"
|
||||
/>
|
||||
<div class="truncate">{{ avatars[0].label }}</div>
|
||||
<Tooltip v-if="avatars?.length == 1" :text="avatars[0].name">
|
||||
<div class="flex items-center gap-2 text-base">
|
||||
<Avatar
|
||||
shape="circle"
|
||||
:image="avatars[0].image"
|
||||
:label="avatars[0].label"
|
||||
size="sm"
|
||||
/>
|
||||
<div class="truncate">{{ avatars[0].label }}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
v-else
|
||||
|
||||
@ -46,7 +46,9 @@
|
||||
</template>
|
||||
<template #item-label="{ option }">
|
||||
<Tooltip :text="option.value">
|
||||
{{ getUser(option.value).full_name }}
|
||||
<div class="cursor-pointer">
|
||||
{{ getUser(option.value).full_name }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</template>
|
||||
</Link>
|
||||
|
||||
@ -46,7 +46,9 @@
|
||||
</template>
|
||||
<template #item-label="{ option }">
|
||||
<Tooltip :text="option.value">
|
||||
{{ getUser(option.value).full_name }}
|
||||
<div class="cursor-pointer">
|
||||
{{ getUser(option.value).full_name }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</template>
|
||||
</Link>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
'box-shadow': '8px 0px 8px rgba(0, 0, 0, 0.1)',
|
||||
'max-width': '350px',
|
||||
'min-width': '350px',
|
||||
'left': 'calc(100% + 1px)'
|
||||
left: 'calc(100% + 1px)',
|
||||
}"
|
||||
>
|
||||
<div class="flex h-screen flex-col">
|
||||
@ -17,21 +17,25 @@
|
||||
<div class="text-base font-medium">Notifications</div>
|
||||
<div class="flex gap-1">
|
||||
<Tooltip text="Mark all as read">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@click="() => notificationsStore().mark_as_read.reload()"
|
||||
>
|
||||
<template #icon>
|
||||
<MarkAsDoneIcon class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
<div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@click="() => notificationsStore().mark_as_read.reload()"
|
||||
>
|
||||
<template #icon>
|
||||
<MarkAsDoneIcon class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip text="Close">
|
||||
<Button variant="ghost" @click="() => toggleNotificationPanel()">
|
||||
<template #icon>
|
||||
<FeatherIcon name="x" class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
<div>
|
||||
<Button variant="ghost" @click="() => toggleNotificationPanel()">
|
||||
<template #icon>
|
||||
<FeatherIcon name="x" class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -7,19 +7,17 @@
|
||||
>
|
||||
<div class="w-[106px] text-sm text-gray-600">{{ s.label }}</div>
|
||||
<div class="grid min-h-[28px] items-center">
|
||||
<Tooltip
|
||||
v-if="s.tooltipText"
|
||||
:text="s.tooltipText"
|
||||
class="ml-2 cursor-pointer"
|
||||
>
|
||||
<Badge
|
||||
v-if="s.type == 'Badge'"
|
||||
class="-ml-1"
|
||||
:label="s.value"
|
||||
variant="subtle"
|
||||
:theme="s.color"
|
||||
/>
|
||||
<div v-else>{{ s.value }}</div>
|
||||
<Tooltip v-if="s.tooltipText" :text="s.tooltipText">
|
||||
<div class="ml-2 cursor-pointer">
|
||||
<Badge
|
||||
v-if="s.type == 'Badge'"
|
||||
class="-ml-1"
|
||||
:label="s.value"
|
||||
variant="subtle"
|
||||
:theme="s.color"
|
||||
/>
|
||||
<div v-else>{{ s.value }}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Dropdown
|
||||
class="form-control"
|
||||
@ -75,8 +73,8 @@ let slaSection = computed(() => {
|
||||
status = 'less than a minute ago'
|
||||
}
|
||||
}
|
||||
} else if (["Fulfilled", "Failed"].includes(status)) {
|
||||
status = status + " in " + formatTime(data.value.first_response_time)
|
||||
} else if (['Fulfilled', 'Failed'].includes(status)) {
|
||||
status = status + ' in ' + formatTime(data.value.first_response_time)
|
||||
tooltipText = dateFormat(data.value.first_responded_on, dateTooltipFormat)
|
||||
}
|
||||
|
||||
|
||||
@ -13,13 +13,14 @@
|
||||
<div
|
||||
class="grid min-h-[28px] flex-1 items-center overflow-hidden text-base"
|
||||
>
|
||||
<Tooltip
|
||||
<div
|
||||
v-if="field.read_only && field.type !== 'checkbox'"
|
||||
class="flex h-7 cursor-pointer items-center px-2 py-1 text-gray-600"
|
||||
:text="field.tooltip"
|
||||
>
|
||||
{{ data[field.name] }}
|
||||
</Tooltip>
|
||||
<Tooltip :text="field.tooltip">
|
||||
<div>{{ data[field.name] }}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<FormControl
|
||||
v-else-if="field.type == 'checkbox'"
|
||||
class="form-control"
|
||||
@ -71,7 +72,9 @@
|
||||
</template>
|
||||
<template #item-label="{ option }">
|
||||
<Tooltip :text="option.value">
|
||||
{{ getUser(option.value).full_name }}
|
||||
<div class="cursor-pointer">
|
||||
{{ getUser(option.value).full_name }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</template>
|
||||
</Link>
|
||||
|
||||
@ -9,10 +9,14 @@
|
||||
:class="isCollapsed ? 'p-1' : 'px-2 py-1'"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<Tooltip :text="label" placement="right">
|
||||
<Tooltip :text="label" placement="right" :disabled="!isCollapsed">
|
||||
<slot name="icon">
|
||||
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
|
||||
<FeatherIcon v-if="typeof icon == 'string'" :name="icon" class="h-4 w-4 text-gray-700" />
|
||||
<FeatherIcon
|
||||
v-if="typeof icon == 'string'"
|
||||
:name="icon"
|
||||
class="h-4 w-4 text-gray-700"
|
||||
/>
|
||||
<component v-else :is="icon" class="h-4 w-4 text-gray-700" />
|
||||
</span>
|
||||
</slot>
|
||||
|
||||
@ -78,14 +78,11 @@
|
||||
<div class="text-sm text-gray-600">Duration</div>
|
||||
<div class="text-sm">{{ callLog.data.duration }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Tooltip
|
||||
class="text-sm text-gray-600"
|
||||
:text="dateFormat(callLog.data.creation, dateTooltipFormat)"
|
||||
>
|
||||
<Tooltip :text="dateFormat(callLog.data.creation, dateTooltipFormat)">
|
||||
<div class="text-sm text-gray-600">
|
||||
{{ timeAgo(callLog.data.creation) }}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -3,6 +3,12 @@
|
||||
<template #left-header>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
v-if="callLogsListView?.customListActions"
|
||||
:actions="callLogsListView.customListActions"
|
||||
/>
|
||||
</template>
|
||||
</LayoutHeader>
|
||||
<ViewControls
|
||||
ref="viewControls"
|
||||
@ -13,6 +19,7 @@
|
||||
doctype="CRM Call Log"
|
||||
/>
|
||||
<CallLogsListView
|
||||
ref="callLogsListView"
|
||||
v-if="callLogs.data && rows.length"
|
||||
v-model="callLogs.data.page_length_count"
|
||||
v-model:list="callLogs"
|
||||
@ -43,6 +50,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import ViewControls from '@/components/ViewControls.vue'
|
||||
@ -63,6 +71,8 @@ const { getContact, getLeadContact } = contactsStore()
|
||||
|
||||
const breadcrumbs = [{ label: 'Call Logs', route: { name: 'Call Logs' } }]
|
||||
|
||||
const callLogsListView = ref(null)
|
||||
|
||||
// callLogs data is loaded in the ViewControls component
|
||||
const callLogs = ref({})
|
||||
const loadMore = ref(1)
|
||||
|
||||
@ -51,14 +51,12 @@
|
||||
</component>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5 truncate">
|
||||
<Tooltip :text="contact.data.full_name">
|
||||
<div class="truncate text-3xl font-semibold">
|
||||
<span v-if="contact.data.salutation">
|
||||
{{ contact.data.salutation + '. ' }}
|
||||
</span>
|
||||
<span>{{ contact.data.full_name }}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div class="truncate text-3xl font-semibold">
|
||||
<span v-if="contact.data.salutation">
|
||||
{{ contact.data.salutation + '. ' }}
|
||||
</span>
|
||||
<span>{{ contact.data.full_name }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-base text-gray-700">
|
||||
<div
|
||||
v-if="contact.data.email_id"
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
v-if="contactsListView?.customListActions"
|
||||
:actions="contactsListView.customListActions"
|
||||
/>
|
||||
<Button variant="solid" label="Create" @click="showContactModal = true">
|
||||
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
|
||||
</Button>
|
||||
@ -18,6 +22,7 @@
|
||||
doctype="Contact"
|
||||
/>
|
||||
<ContactsListView
|
||||
ref="contactsListView"
|
||||
v-if="contacts.data && rows.length"
|
||||
v-model="contacts.data.page_length_count"
|
||||
v-model:list="contacts"
|
||||
@ -52,6 +57,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import ContactModal from '@/components/Modals/ContactModal.vue'
|
||||
@ -87,6 +93,8 @@ const breadcrumbs = computed(() => {
|
||||
return items
|
||||
})
|
||||
|
||||
const contactsListView = ref(null)
|
||||
|
||||
// contacts data is loaded in the ViewControls component
|
||||
const contacts = ref({})
|
||||
const loadMore = ref(1)
|
||||
|
||||
@ -51,16 +51,15 @@
|
||||
About this Deal
|
||||
</div>
|
||||
<div class="flex items-center justify-start gap-5 border-b p-5">
|
||||
<Tooltip
|
||||
text="Organization logo"
|
||||
class="group relative h-[88px] w-[88px]"
|
||||
>
|
||||
<Avatar
|
||||
size="3xl"
|
||||
class="h-[88px] w-[88px]"
|
||||
:label="organization?.name"
|
||||
:image="organization?.organization_logo"
|
||||
/>
|
||||
<Tooltip text="Organization logo">
|
||||
<div class="group relative h-[88px] w-[88px]">
|
||||
<Avatar
|
||||
size="3xl"
|
||||
class="h-[88px] w-[88px]"
|
||||
:label="organization?.name"
|
||||
:image="organization?.organization_logo"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div class="flex flex-col gap-2.5 truncate">
|
||||
<Tooltip :text="organization?.name">
|
||||
@ -69,22 +68,24 @@
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div class="flex gap-1.5">
|
||||
<Tooltip text="Make a call...">
|
||||
<Tooltip text="Make a call">
|
||||
<Button class="h-7 w-7" @click="triggerCall">
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button class="h-7 w-7">
|
||||
<EmailIcon
|
||||
class="h-4 w-4"
|
||||
@click="
|
||||
deal.data.email
|
||||
? openEmailBox()
|
||||
: errorMessage('No email set')
|
||||
"
|
||||
/>
|
||||
</Button>
|
||||
<Tooltip text="Go to website...">
|
||||
<Tooltip text="Send an email">
|
||||
<Button class="h-7 w-7">
|
||||
<EmailIcon
|
||||
class="h-4 w-4"
|
||||
@click="
|
||||
deal.data.email
|
||||
? openEmailBox()
|
||||
: errorMessage('No email set')
|
||||
"
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Go to website">
|
||||
<Button class="h-7 w-7">
|
||||
<LinkIcon
|
||||
class="h-4 w-4"
|
||||
@ -155,7 +156,9 @@
|
||||
/>
|
||||
<div v-else>
|
||||
<div
|
||||
v-if="deal_contacts?.loading && deal_contacts?.data?.length == 0"
|
||||
v-if="
|
||||
deal_contacts?.loading && deal_contacts?.data?.length == 0
|
||||
"
|
||||
class="flex min-h-20 flex-1 items-center justify-center gap-3 text-base text-gray-500"
|
||||
>
|
||||
<LoadingIndicator class="h-4 w-4" />
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
v-if="dealsListView?.customListActions"
|
||||
:actions="dealsListView.customListActions"
|
||||
/>
|
||||
<Button variant="solid" label="Create" @click="showNewDialog = true">
|
||||
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
|
||||
</Button>
|
||||
@ -18,6 +22,7 @@
|
||||
doctype="CRM Deal"
|
||||
/>
|
||||
<DealsListView
|
||||
ref="dealsListView"
|
||||
v-if="deals.data && rows.length"
|
||||
v-model="deals.data.page_length_count"
|
||||
v-model:list="deals"
|
||||
@ -65,6 +70,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import DealsIcon from '@/components/Icons/DealsIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import DealsListView from '@/components/ListViews/DealsListView.vue'
|
||||
@ -92,6 +98,8 @@ const { getDealStatus } = statusesStore()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const dealsListView = ref(null)
|
||||
|
||||
// deals data is loaded in the ViewControls component
|
||||
const deals = ref({})
|
||||
const loadMore = ref(1)
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
v-if="emailTemplatesListView?.customListActions"
|
||||
:actions="emailTemplatesListView.customListActions"
|
||||
/>
|
||||
<Button variant="solid" label="Create" @click="showEmailTemplateModal = true">
|
||||
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
|
||||
</Button>
|
||||
@ -18,6 +22,7 @@
|
||||
doctype="Email Template"
|
||||
/>
|
||||
<EmailTemplatesListView
|
||||
ref="emailTemplatesListView"
|
||||
v-if="emailTemplates.data && rows.length"
|
||||
v-model="emailTemplates.data.page_length_count"
|
||||
v-model:list="emailTemplates"
|
||||
@ -54,6 +59,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import ViewControls from '@/components/ViewControls.vue'
|
||||
@ -67,6 +73,8 @@ const breadcrumbs = [
|
||||
{ label: 'Email Templates', route: { name: 'Email Templates' } },
|
||||
]
|
||||
|
||||
const emailTemplatesListView = ref(null)
|
||||
|
||||
// emailTemplates data is loaded in the ViewControls component
|
||||
const emailTemplates = ref({})
|
||||
const loadMore = ref(1)
|
||||
|
||||
@ -110,7 +110,7 @@
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div class="flex gap-1.5">
|
||||
<Tooltip text="Make a call...">
|
||||
<Tooltip text="Make a call">
|
||||
<Button
|
||||
class="h-7 w-7"
|
||||
@click="
|
||||
@ -123,17 +123,19 @@
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button class="h-7 w-7">
|
||||
<EmailIcon
|
||||
class="h-4 w-4"
|
||||
@click="
|
||||
lead.data.email
|
||||
? openEmailBox()
|
||||
: errorMessage('No email set')
|
||||
"
|
||||
/>
|
||||
</Button>
|
||||
<Tooltip text="Go to website...">
|
||||
<Tooltip text="Send an email">
|
||||
<Button class="h-7 w-7">
|
||||
<EmailIcon
|
||||
class="h-4 w-4"
|
||||
@click="
|
||||
lead.data.email
|
||||
? openEmailBox()
|
||||
: errorMessage('No email set')
|
||||
"
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Go to website">
|
||||
<Button class="h-7 w-7">
|
||||
<LinkIcon
|
||||
class="h-4 w-4"
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
v-if="leadsListView?.customListActions"
|
||||
:actions="leadsListView.customListActions"
|
||||
/>
|
||||
<Button variant="solid" label="Create" @click="showNewDialog = true">
|
||||
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
|
||||
</Button>
|
||||
@ -19,6 +23,7 @@
|
||||
:filters="{ converted: 0 }"
|
||||
/>
|
||||
<LeadsListView
|
||||
ref="leadsListView"
|
||||
v-if="leads.data && rows.length"
|
||||
v-model="leads.data.page_length_count"
|
||||
v-model:list="leads"
|
||||
@ -66,6 +71,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import LeadsListView from '@/components/ListViews/LeadsListView.vue'
|
||||
@ -87,6 +93,8 @@ const { getLeadStatus } = statusesStore()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const leadsListView = ref(null)
|
||||
|
||||
// leads data is loaded in the ViewControls component
|
||||
const leads = ref({})
|
||||
const loadMore = ref(1)
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
v-if="organizationsListView?.customListActions"
|
||||
:actions="organizationsListView.customListActions"
|
||||
/>
|
||||
<Button
|
||||
variant="solid"
|
||||
label="Create"
|
||||
@ -22,6 +26,7 @@
|
||||
doctype="CRM Organization"
|
||||
/>
|
||||
<OrganizationsListView
|
||||
ref="organizationsListView"
|
||||
v-if="organizations.data && rows.length"
|
||||
v-model="organizations.data.page_length_count"
|
||||
v-model:list="organizations"
|
||||
@ -55,6 +60,7 @@
|
||||
<OrganizationModal v-model="showOrganizationModal" />
|
||||
</template>
|
||||
<script setup>
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
|
||||
@ -72,6 +78,7 @@ import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const organizationsListView = ref(null)
|
||||
const showOrganizationModal = ref(false)
|
||||
|
||||
const currentOrganization = computed(() => {
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
v-if="tasksListView?.customListActions"
|
||||
:actions="tasksListView.customListActions"
|
||||
/>
|
||||
<Button variant="solid" label="Create" @click="createTask">
|
||||
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
|
||||
</Button>
|
||||
@ -18,6 +22,7 @@
|
||||
doctype="CRM Task"
|
||||
/>
|
||||
<TasksListView
|
||||
ref="tasksListView"
|
||||
v-if="tasks.data && rows.length"
|
||||
v-model="tasks.data.page_length_count"
|
||||
v-model:list="tasks"
|
||||
@ -47,6 +52,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import ViewControls from '@/components/ViewControls.vue'
|
||||
@ -61,6 +67,8 @@ const breadcrumbs = [{ label: 'Tasks', route: { name: 'Tasks' } }]
|
||||
|
||||
const { getUser } = usersStore()
|
||||
|
||||
const tasksListView = ref(null)
|
||||
|
||||
// tasks data is loaded in the ViewControls component
|
||||
const tasks = ref({})
|
||||
const loadMore = ref(1)
|
||||
|
||||
@ -131,11 +131,12 @@ export function setupCustomActions(data, obj) {
|
||||
data._customActions = formScript?.actions || []
|
||||
}
|
||||
|
||||
export function setupBulkActions(data, obj = {}) {
|
||||
if (!data.form_script) return []
|
||||
let script = new Function(data.form_script + '\nreturn setupForm')()
|
||||
let formScript = script(obj)
|
||||
data.bulkActions = formScript?.bulk_actions || []
|
||||
export function setupListActions(data, obj = {}) {
|
||||
if (!data.list_script) return []
|
||||
let script = new Function(data.list_script + '\nreturn setupList')()
|
||||
let listScript = script(obj)
|
||||
data.listActions = listScript?.actions || []
|
||||
data.bulkActions = listScript?.bulk_actions || []
|
||||
}
|
||||
|
||||
export function errorMessage(title, message) {
|
||||
|
||||
@ -32,6 +32,7 @@ export default defineConfig({
|
||||
'showdown',
|
||||
'tailwind.config.js',
|
||||
'engine.io-client',
|
||||
'prosemirror-state',
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user