fix: allow uploading file & show on email and email box

This commit is contained in:
Shariq Ansari 2023-12-23 21:24:00 +05:30
parent 9d26985609
commit ccd57e213a
14 changed files with 1645 additions and 737 deletions

View File

@ -2,6 +2,7 @@ import json
import frappe
from frappe import _
from frappe.utils.caching import redis_cache
from frappe.desk.form.load import get_docinfo
@frappe.whitelist()
@ -98,6 +99,7 @@ def get_deal_activities(name):
"recipients": communication.recipients,
"cc": communication.cc,
"bcc": communication.bcc,
"attachments": get_attachments(communication.name),
"read_by_recipient": communication.read_by_recipient,
},
"is_lead": False,
@ -185,6 +187,7 @@ def get_lead_activities(name):
"recipients": communication.recipients,
"cc": communication.cc,
"bcc": communication.bcc,
"attachments": get_attachments(communication.name),
"read_by_recipient": communication.read_by_recipient,
},
"is_lead": True,
@ -196,6 +199,14 @@ def get_lead_activities(name):
return activities
@redis_cache()
def get_attachments(name):
return frappe.db.get_all(
"File",
filters={"attached_to_doctype": "Communication", "attached_to_name": name},
fields=["name", "file_name", "file_url", "file_size", "is_private"],
)
def handle_multiple_versions(versions):
activities = []
grouped_versions = []

View File

@ -14,6 +14,7 @@
"@vueuse/integrations": "^10.3.0",
"feather-icons": "^4.28.0",
"frappe-ui": "^0.1.17",
"mime": "^4.0.1",
"pinia": "^2.0.33",
"socket.io-client": "^4.7.2",
"sortablejs": "^1.15.0",

View File

@ -326,6 +326,14 @@
</div>
</div>
<span class="prose-f" v-html="activity.data.content" />
<div class="flex flex-wrap gap-2">
<AttachmentItem
v-for="a in activity.data.attachments"
:key="a.file_url"
:label="a.file_name"
:url="a.file_url"
/>
</div>
</div>
</div>
<div
@ -588,6 +596,7 @@
v-if="['Emails', 'Activity'].includes(title)"
v-model="doc"
v-model:reload="reload_email"
:doctype="doctype"
@scroll="scroll"
/>
<NoteModal
@ -623,6 +632,7 @@ import EmailAtIcon from '@/components/Icons/EmailAtIcon.vue'
import InboundCallIcon from '@/components/Icons/InboundCallIcon.vue'
import OutboundCallIcon from '@/components/Icons/OutboundCallIcon.vue'
import ReplyIcon from '@/components/Icons/ReplyIcon.vue'
import AttachmentItem from '@/components/AttachmentItem.vue'
import CommunicationArea from '@/components/CommunicationArea.vue'
import NoteModal from '@/components/Modals/NoteModal.vue'
import TaskModal from '@/components/Modals/TaskModal.vue'

View File

@ -0,0 +1,83 @@
<template>
<span>
<a :href="isShowable ? null : url" target="_blank">
<Button
:label="label"
theme="gray"
variant="outline"
@click="toggleDialog()"
>
<template #prefix>
<component :is="getIcon()" class="h-4 w-4" />
</template>
<template #suffix>
<slot name="suffix" />
</template>
</Button>
</a>
<Dialog
v-model="showDialog"
:options="{
title: label,
size: '4xl',
}"
>
<template #body-content>
<div
v-if="isText"
class="prose prose-sm max-w-none whitespace-pre-wrap"
>
{{ content }}
</div>
<img v-if="isImage" :src="url" class="m-auto rounded border" />
</template>
</Dialog>
</span>
</template>
<script setup>
import { ref } from 'vue'
import { Button, Dialog } from 'frappe-ui'
import mime from 'mime'
import FileTypeIcon from '@/components/Icons/FileTypeIcon.vue'
import FileImageIcon from '@/components/Icons/FileImageIcon.vue'
import FileTextIcon from '@/components/Icons/FileTextIcon.vue'
import FileSpreadsheetIcon from '@/components/Icons/FileSpreadsheetIcon.vue'
import FileIcon from '@/components/Icons/FileIcon.vue'
const props = defineProps({
label: {
type: String,
default: null,
},
url: {
type: String,
default: null,
},
})
const showDialog = ref(false)
const mimeType = mime.getType(props.label) || ''
const isImage = mimeType.startsWith('image/')
const isPdf = mimeType === 'application/pdf'
const isSpreadsheet = mimeType.includes('spreadsheet')
const isText = mimeType === 'text/plain'
const isShowable = props.url && (isText || isImage)
const content = ref('')
function getIcon() {
if (isText) return FileTypeIcon
else if (isImage) return FileImageIcon
else if (isPdf) return FileTextIcon
else if (isSpreadsheet) return FileSpreadsheetIcon
else return FileIcon
}
function toggleDialog() {
if (!isShowable) return
if (isText) {
fetch(props.url).then((res) => res.text().then((t) => (content.value = t)))
}
showDialog.value = !showDialog.value
}
</script>

View File

@ -39,6 +39,8 @@
}"
:editable="showCommunicationBox"
v-model="doc.data"
v-model:attachments="attachments"
:doctype="doctype"
placeholder="Add a reply..."
/>
</div>
@ -51,6 +53,13 @@ import { usersStore } from '@/stores/users'
import { call } from 'frappe-ui'
import { ref, watch, computed, defineModel } from 'vue'
const props = defineProps({
doctype: {
type: String,
default: 'CRM Lead',
},
})
const doc = defineModel()
const reload = defineModel('reload')
@ -62,6 +71,7 @@ const showCommunicationBox = ref(false)
const newEmail = ref('')
const newEmailEditor = ref(null)
const sendEmailRef = ref(null)
const attachments = ref([]);
watch(
() => showCommunicationBox.value,
@ -81,18 +91,14 @@ const onNewEmailChange = (value) => {
}
async function sendMail() {
let doctype = 'CRM Lead'
if (doc.value.data.lead) {
doctype = 'CRM Deal'
}
await call('frappe.core.doctype.communication.email.make', {
recipients: doc.value.data.email,
attachments: attachments.value.map((x) => x.name),
cc: '',
bcc: '',
subject: 'Email from Agent',
content: newEmail.value,
doctype: doctype,
doctype: props.doctype,
name: doc.value.data.name,
send_email: 1,
sender: getUser().name,

View File

@ -85,6 +85,7 @@ const text = ref('')
watchDebounced(
() => autocomplete.value?.query,
(val) => {
val = val || ''
if (text.value === val) return
text.value = val
options.update({

View File

@ -9,7 +9,7 @@
:editable="editable"
>
<template #top>
<div class="mx-10 border-t border-b py-2.5">
<div class="mx-10 border-b border-t py-2.5">
<span class="text-xs text-gray-500">TO:</span>
<span
v-if="modelValue.email"
@ -21,21 +21,60 @@
</template>
<template v-slot:editor="{ editor }">
<EditorContent
:class="[editable && 'mx-10 py-3 max-h-[50vh] overflow-y-auto']"
:class="[editable && 'mx-10 max-h-[50vh] overflow-y-auto py-3']"
:editor="editor"
/>
</template>
<template v-slot:bottom>
<div v-if="editable" class="flex justify-between border-t px-10 py-2.5">
<TextEditorFixedMenu
class="-ml-1 overflow-x-auto"
:buttons="textEditorMenuButtons"
/>
<div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
<Button v-bind="discardButtonProps || {}"> Discard </Button>
<Button variant="solid" v-bind="submitButtonProps || {}">
Submit
</Button>
<div v-if="editable" class="flex flex-col gap-2">
<div class="flex flex-wrap gap-2 px-10">
<AttachmentItem
v-for="a in attachments"
:key="a.file_url"
:label="a.file_name"
>
<template #suffix>
<FeatherIcon
class="h-3.5"
name="x"
@click.stop="removeAttachment(a)"
/>
</template>
</AttachmentItem>
</div>
<div class="flex justify-between border-t px-10 py-2.5">
<div class="flex items-center">
<TextEditorFixedMenu
class="-ml-1 overflow-x-auto"
:buttons="textEditorMenuButtons"
/>
<FileUploader
:upload-args="{
doctype: doctype,
docname: modelValue.name,
private: true,
}"
@success="(f) => attachments.push(f)"
>
<template #default="{ openFileSelector }">
<Button
theme="gray"
variant="ghost"
@click="openFileSelector()"
>
<template #icon>
<AttachmentIcon class="h-4" />
</template>
</Button>
</template>
</FileUploader>
</div>
<div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
<Button v-bind="discardButtonProps || {}"> Discard </Button>
<Button variant="solid" v-bind="submitButtonProps || {}">
Submit
</Button>
</div>
</div>
</div>
</template>
@ -43,7 +82,14 @@
</template>
<script setup>
import { TextEditorFixedMenu, TextEditor } from 'frappe-ui'
import AttachmentIcon from '@/components/Icons/AttachmentIcon.vue'
import AttachmentItem from '@/components/AttachmentItem.vue'
import {
TextEditorFixedMenu,
TextEditor,
FileUploader,
FeatherIcon,
} from 'frappe-ui'
import { EditorContent } from '@tiptap/vue-3'
import { ref, computed, defineModel } from 'vue'
@ -60,6 +106,10 @@ const props = defineProps({
type: Boolean,
default: true,
},
doctype: {
type: String,
default: 'CRM Lead',
},
editorProps: {
type: Object,
default: () => ({}),
@ -76,6 +126,7 @@ const props = defineProps({
const emit = defineEmits(['change'])
const modelValue = defineModel()
const attachments = defineModel('attachments')
const textEditor = ref(null)
@ -83,6 +134,10 @@ const editor = computed(() => {
return textEditor.value.editor
})
function removeAttachment(attachment) {
attachments.value = attachments.value.filter((a) => a !== attachment)
}
defineExpose({ editor })
const textEditorMenuButtons = [

View File

@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12.5684 2.50774C11.5403 1.49742 9.95026 1.49742 8.92215 2.50774L3.62404 7.71417C2.12532 9.18695 2.12532 11.669 3.62404 13.1418C5.12762 14.6194 7.66861 14.6194 9.17219 13.1418L12.1609 10.2049C12.3578 10.0113 12.6744 10.0141 12.8679 10.211C13.0615 10.408 13.0587 10.7246 12.8618 10.9181L9.8731 13.8551C7.98045 15.715 4.81578 15.715 2.92313 13.8551C1.02562 11.9904 1.02562 8.86558 2.92313 7.00091L8.22124 1.79449C9.63842 0.401838 11.8521 0.401838 13.2693 1.79449C14.6914 3.19191 14.6914 5.38225 13.2693 6.77968L13.2668 6.78213L13.2668 6.78212L8.37876 11.5189C8.37834 11.5193 8.37793 11.5197 8.37752 11.5201C7.51767 12.3638 6.11144 12.3939 5.29119 11.5097C4.43611 10.6596 4.40778 9.26893 5.30922 8.46081L7.33823 6.46692C7.53518 6.27337 7.85175 6.27613 8.04531 6.47309C8.23886 6.67005 8.23609 6.98662 8.03913 7.18017L6.0014 9.18264L5.99203 9.19185L5.98219 9.20055C5.5391 9.59243 5.5104 10.3231 6.0014 10.8056L6.01078 10.8148L6.01967 10.8245C6.42299 11.2649 7.18224 11.2926 7.67785 10.8056L7.68034 10.8032L7.68035 10.8032L12.5684 6.06643C12.5688 6.06604 12.5692 6.06565 12.5696 6.06526C13.5917 5.05969 13.5913 3.51289 12.5684 2.50774Z"
fill="currentColor"
/>
</svg>
</template>

View File

@ -0,0 +1,19 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-file"
>
<path
d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"
/>
<polyline points="14 2 14 8 20 8" />
</svg>
</template>

View File

@ -0,0 +1,21 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-file-image"
>
<path
d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"
/>
<polyline points="14 2 14 8 20 8" />
<circle cx="10" cy="13" r="2" />
<path d="m20 17-1.09-1.09a2 2 0 0 0-2.82 0L10 22" />
</svg>
</template>

View File

@ -0,0 +1,23 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-file-spreadsheet"
>
<path
d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"
/>
<polyline points="14 2 14 8 20 8" />
<path d="M8 13h2" />
<path d="M8 17h2" />
<path d="M14 13h2" />
<path d="M14 17h2" />
</svg>
</template>

View File

@ -0,0 +1,22 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-file-text"
>
<path
d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"
/>
<polyline points="14 2 14 8 20 8" />
<line x1="16" x2="8" y1="13" y2="13" />
<line x1="16" x2="8" y1="17" y2="17" />
<line x1="10" x2="8" y1="9" y2="9" />
</svg>
</template>

View File

@ -0,0 +1,22 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-file-type"
>
<path
d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"
/>
<polyline points="14 2 14 8 20 8" />
<path d="M9 13v-1h6v1" />
<path d="M11 18h2" />
<path d="M12 12v6" />
</svg>
</template>

2054
yarn.lock

File diff suppressed because it is too large Load Diff