Merge pull request #274 from frappe/develop
chore: Merge develop to main
This commit is contained in:
commit
2b9131adb4
File diff suppressed because it is too large
Load Diff
744
frontend/src/components/Activities/Activities.vue
Normal file
744
frontend/src/components/Activities/Activities.vue
Normal file
@ -0,0 +1,744 @@
|
||||
<template>
|
||||
<ActivityHeader
|
||||
v-model="tabIndex"
|
||||
v-model:showWhatsappTemplates="showWhatsappTemplates"
|
||||
:tabs="tabs"
|
||||
:title="title"
|
||||
:doc="doc"
|
||||
:emailBox="emailBox"
|
||||
:whatsappBox="whatsappBox"
|
||||
:modalRef="modalRef"
|
||||
/>
|
||||
<FadedScrollableDiv
|
||||
:maskHeight="30"
|
||||
class="flex flex-col flex-1 overflow-y-auto"
|
||||
>
|
||||
<div
|
||||
v-if="all_activities?.loading"
|
||||
class="flex flex-1 flex-col items-center justify-center gap-3 text-xl font-medium text-gray-500"
|
||||
>
|
||||
<LoadingIndicator class="h-6 w-6" />
|
||||
<span>{{ __('Loading...') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
activities?.length ||
|
||||
(whatsappMessages.data?.length && title == 'WhatsApp')
|
||||
"
|
||||
class="activities"
|
||||
>
|
||||
<div v-if="title == 'WhatsApp' && whatsappMessages.data?.length">
|
||||
<WhatsAppArea
|
||||
class="px-3 sm:px-10"
|
||||
v-model="whatsappMessages"
|
||||
v-model:reply="replyMessage"
|
||||
:messages="whatsappMessages.data"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="title == 'Notes'"
|
||||
class="grid grid-cols-1 gap-4 px-3 pb-3 sm:px-10 sm:pb-5 lg:grid-cols-2 xl:grid-cols-3"
|
||||
>
|
||||
<div v-for="note in activities" @click="modalRef.showNote(note)">
|
||||
<NoteArea :note="note" v-model="all_activities" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="title == 'Comments'" class="pb-5">
|
||||
<div v-for="(comment, i) in activities">
|
||||
<div
|
||||
class="activity grid grid-cols-[30px_minmax(auto,_1fr)] gap-2 px-3 sm:gap-4 sm:px-10"
|
||||
>
|
||||
<div
|
||||
class="relative flex justify-center after:absolute after:left-[50%] after:top-0 after:-z-10 after:border-l after:border-gray-200"
|
||||
:class="i != activities.length - 1 ? 'after:h-full' : 'after:h-4'"
|
||||
>
|
||||
<div
|
||||
class="z-10 flex h-8 w-7 items-center justify-center bg-white"
|
||||
>
|
||||
<CommentIcon class="text-gray-800" />
|
||||
</div>
|
||||
</div>
|
||||
<CommentArea class="mb-4" :activity="comment" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="title == 'Tasks'"
|
||||
class="px-3 pb-3 sm:px-10 sm:pb-5 overflow-x-auto sm:w-full w-max"
|
||||
>
|
||||
<TaskArea
|
||||
v-model="all_activities"
|
||||
v-model:doc="doc"
|
||||
:modalRef="modalRef"
|
||||
:tasks="activities"
|
||||
:doctype="doctype"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="title == 'Calls'" class="activity">
|
||||
<div v-for="(call, i) in activities">
|
||||
<div
|
||||
class="activity grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-3 sm:px-10"
|
||||
>
|
||||
<div
|
||||
class="relative flex justify-center after:absolute after:left-[50%] after:top-0 after:-z-10 after:border-l after:border-gray-200"
|
||||
:class="i != activities.length - 1 ? 'after:h-full' : 'after:h-4'"
|
||||
>
|
||||
<div
|
||||
class="z-10 flex h-8 w-7 items-center justify-center bg-white text-gray-800"
|
||||
>
|
||||
<MissedCallIcon
|
||||
v-if="call.status == 'No Answer'"
|
||||
class="text-red-600"
|
||||
/>
|
||||
<DeclinedCallIcon v-else-if="call.status == 'Busy'" />
|
||||
<component
|
||||
v-else
|
||||
:is="
|
||||
call.type == 'Incoming' ? InboundCallIcon : OutboundCallIcon
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CallArea class="mb-4" :activity="call" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
v-for="(activity, i) in activities"
|
||||
class="activity px-3 sm:px-10"
|
||||
:class="
|
||||
['Activity', 'Emails'].includes(title)
|
||||
? 'grid grid-cols-[30px_minmax(auto,_1fr)] gap-2 sm:gap-4'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-if="['Activity', 'Emails'].includes(title)"
|
||||
class="relative flex justify-center before:absolute before:left-[50%] before:top-0 before:-z-10 before:border-l before:border-gray-200"
|
||||
:class="[i != activities.length - 1 ? 'before:h-full' : 'before:h-4']"
|
||||
>
|
||||
<div
|
||||
class="z-10 flex h-7 w-7 items-center justify-center bg-white"
|
||||
:class="{
|
||||
'mt-2.5': ['communication'].includes(activity.activity_type),
|
||||
'bg-white': ['added', 'removed', 'changed'].includes(
|
||||
activity.activity_type,
|
||||
),
|
||||
'h-8': [
|
||||
'comment',
|
||||
'communication',
|
||||
'incoming_call',
|
||||
'outgoing_call',
|
||||
].includes(activity.activity_type),
|
||||
}"
|
||||
>
|
||||
<UserAvatar
|
||||
v-if="activity.activity_type == 'communication'"
|
||||
:user="activity.data.sender"
|
||||
size="md"
|
||||
/>
|
||||
<MissedCallIcon
|
||||
v-else-if="
|
||||
['incoming_call', 'outgoing_call'].includes(
|
||||
activity.activity_type,
|
||||
) && activity.status == 'No Answer'
|
||||
"
|
||||
class="text-red-600"
|
||||
/>
|
||||
<DeclinedCallIcon
|
||||
v-else-if="
|
||||
['incoming_call', 'outgoing_call'].includes(
|
||||
activity.activity_type,
|
||||
) && activity.status == 'Busy'
|
||||
"
|
||||
/>
|
||||
<component
|
||||
v-else
|
||||
:is="activity.icon"
|
||||
:class="
|
||||
['added', 'removed', 'changed'].includes(activity.activity_type)
|
||||
? 'text-gray-500'
|
||||
: 'text-gray-800'
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="activity.activity_type == 'communication'"
|
||||
class="pb-5 mt-px"
|
||||
>
|
||||
<EmailArea :activity="activity" :emailBox="emailBox" />
|
||||
</div>
|
||||
<div
|
||||
class="mb-4"
|
||||
:id="activity.name"
|
||||
v-else-if="activity.activity_type == 'comment'"
|
||||
>
|
||||
<CommentArea :activity="activity" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
activity.activity_type == 'incoming_call' ||
|
||||
activity.activity_type == 'outgoing_call'
|
||||
"
|
||||
class="mb-4"
|
||||
>
|
||||
<CallArea :activity="activity" />
|
||||
</div>
|
||||
<div v-else class="mb-4 flex flex-col gap-2 py-1.5">
|
||||
<div class="flex items-center justify-stretch gap-2 text-base">
|
||||
<div
|
||||
v-if="activity.other_versions"
|
||||
class="inline-flex flex-wrap gap-1.5 text-gray-800 font-medium"
|
||||
>
|
||||
<span>{{ activity.show_others ? __('Hide') : __('Show') }}</span>
|
||||
<span> +{{ activity.other_versions.length + 1 }} </span>
|
||||
<span>{{ __('changes from') }}</span>
|
||||
<span>{{ activity.owner_name }}</span>
|
||||
<Button
|
||||
class="!size-4"
|
||||
variant="ghost"
|
||||
@click="activity.show_others = !activity.show_others"
|
||||
>
|
||||
<template #icon>
|
||||
<SelectIcon />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="inline-flex items-center flex-wrap gap-1 text-gray-600"
|
||||
>
|
||||
<span class="font-medium text-gray-800">
|
||||
{{ activity.owner_name }}
|
||||
</span>
|
||||
<span v-if="activity.type">{{ __(activity.type) }}</span>
|
||||
<span
|
||||
v-if="activity.data.field_label"
|
||||
class="max-w-xs truncate font-medium text-gray-800"
|
||||
>
|
||||
{{ __(activity.data.field_label) }}
|
||||
</span>
|
||||
<span v-if="activity.value">{{ __(activity.value) }}</span>
|
||||
<span
|
||||
v-if="activity.data.old_value"
|
||||
class="max-w-xs font-medium text-gray-800"
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-1"
|
||||
v-if="activity.options == 'User'"
|
||||
>
|
||||
<UserAvatar :user="activity.data.old_value" size="xs" />
|
||||
{{ getUser(activity.data.old_value).full_name }}
|
||||
</div>
|
||||
<div class="truncate" v-else>
|
||||
{{ activity.data.old_value }}
|
||||
</div>
|
||||
</span>
|
||||
<span v-if="activity.to">{{ __('to') }}</span>
|
||||
<span
|
||||
v-if="activity.data.value"
|
||||
class="max-w-xs font-medium text-gray-800"
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-1"
|
||||
v-if="activity.options == 'User'"
|
||||
>
|
||||
<UserAvatar :user="activity.data.value" size="xs" />
|
||||
{{ getUser(activity.data.value).full_name }}
|
||||
</div>
|
||||
<div class="truncate" v-else>
|
||||
{{ activity.data.value }}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto whitespace-nowrap">
|
||||
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||
<div class="text-sm text-gray-600">
|
||||
{{ __(timeAgo(activity.creation)) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="activity.other_versions && activity.show_others"
|
||||
class="flex flex-col gap-0.5"
|
||||
>
|
||||
<div
|
||||
v-for="activity in [activity, ...activity.other_versions]"
|
||||
class="flex items-start justify-stretch gap-2 py-1.5 text-base"
|
||||
>
|
||||
<div class="inline-flex flex-wrap gap-1 text-gray-600">
|
||||
<span
|
||||
v-if="activity.data.field_label"
|
||||
class="max-w-xs truncate text-gray-600"
|
||||
>
|
||||
{{ __(activity.data.field_label) }}
|
||||
</span>
|
||||
<FeatherIcon
|
||||
name="arrow-right"
|
||||
class="mx-1 h-4 w-4 text-gray-600"
|
||||
/>
|
||||
<span v-if="activity.type">
|
||||
{{ startCase(__(activity.type)) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="activity.data.old_value"
|
||||
class="max-w-xs font-medium text-gray-800"
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-1"
|
||||
v-if="activity.options == 'User'"
|
||||
>
|
||||
<UserAvatar :user="activity.data.old_value" size="xs" />
|
||||
{{ getUser(activity.data.old_value).full_name }}
|
||||
</div>
|
||||
<div class="truncate" v-else>
|
||||
{{ activity.data.old_value }}
|
||||
</div>
|
||||
</span>
|
||||
<span v-if="activity.to">{{ __('to') }}</span>
|
||||
<span
|
||||
v-if="activity.data.value"
|
||||
class="max-w-xs font-medium text-gray-800"
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-1"
|
||||
v-if="activity.options == 'User'"
|
||||
>
|
||||
<UserAvatar :user="activity.data.value" size="xs" />
|
||||
{{ getUser(activity.data.value).full_name }}
|
||||
</div>
|
||||
<div class="truncate" v-else>
|
||||
{{ activity.data.value }}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto whitespace-nowrap">
|
||||
<Tooltip
|
||||
:text="dateFormat(activity.creation, dateTooltipFormat)"
|
||||
>
|
||||
<div class="text-sm text-gray-600">
|
||||
{{ __(timeAgo(activity.creation)) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-1 flex-col items-center justify-center gap-3 text-xl font-medium text-gray-500"
|
||||
>
|
||||
<component :is="emptyTextIcon" class="h-10 w-10" />
|
||||
<span>{{ __(emptyText) }}</span>
|
||||
<Button
|
||||
v-if="title == 'Calls'"
|
||||
:label="__('Make a Call')"
|
||||
@click="makeCall(doc.data.mobile_no)"
|
||||
/>
|
||||
<Button
|
||||
v-else-if="title == 'Notes'"
|
||||
:label="__('Create Note')"
|
||||
@click="modalRef.showNote()"
|
||||
/>
|
||||
<Button
|
||||
v-else-if="title == 'Emails'"
|
||||
:label="__('New Email')"
|
||||
@click="emailBox.show = true"
|
||||
/>
|
||||
<Button
|
||||
v-else-if="title == 'Comments'"
|
||||
:label="__('New Comment')"
|
||||
@click="emailBox.showComment = true"
|
||||
/>
|
||||
<Button
|
||||
v-else-if="title == 'Tasks'"
|
||||
:label="__('Create Task')"
|
||||
@click="modalRef.showTask()"
|
||||
/>
|
||||
</div>
|
||||
</FadedScrollableDiv>
|
||||
<div>
|
||||
<CommunicationArea
|
||||
ref="emailBox"
|
||||
v-if="['Emails', 'Comments', 'Activity'].includes(title)"
|
||||
v-model="doc"
|
||||
v-model:reload="reload_email"
|
||||
:doctype="doctype"
|
||||
@scroll="scroll"
|
||||
/>
|
||||
<WhatsAppBox
|
||||
ref="whatsappBox"
|
||||
v-if="title == 'WhatsApp'"
|
||||
v-model="doc"
|
||||
v-model:reply="replyMessage"
|
||||
v-model:whatsapp="whatsappMessages"
|
||||
:doctype="doctype"
|
||||
@scroll="scroll"
|
||||
/>
|
||||
</div>
|
||||
<WhatsappTemplateSelectorModal
|
||||
v-if="whatsappEnabled"
|
||||
v-model="showWhatsappTemplates"
|
||||
:doctype="doctype"
|
||||
@send="(t) => sendTemplate(t)"
|
||||
/>
|
||||
<AllModals
|
||||
ref="modalRef"
|
||||
v-model="all_activities"
|
||||
:doctype="doctype"
|
||||
:doc="doc"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import ActivityHeader from '@/components/Activities/ActivityHeader.vue'
|
||||
import EmailArea from '@/components/Activities/EmailArea.vue'
|
||||
import CommentArea from '@/components/Activities/CommentArea.vue'
|
||||
import CallArea from '@/components/Activities/CallArea.vue'
|
||||
import NoteArea from '@/components/Activities/NoteArea.vue'
|
||||
import TaskArea from '@/components/Activities/TaskArea.vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import NoteIcon from '@/components/Icons/NoteIcon.vue'
|
||||
import TaskIcon from '@/components/Icons/TaskIcon.vue'
|
||||
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
|
||||
import WhatsAppArea from '@/components/Activities/WhatsAppArea.vue'
|
||||
import WhatsAppBox from '@/components/Activities/WhatsAppBox.vue'
|
||||
import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
|
||||
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
||||
import DealsIcon from '@/components/Icons/DealsIcon.vue'
|
||||
import DotIcon from '@/components/Icons/DotIcon.vue'
|
||||
import CommentIcon from '@/components/Icons/CommentIcon.vue'
|
||||
import SelectIcon from '@/components/Icons/SelectIcon.vue'
|
||||
import MissedCallIcon from '@/components/Icons/MissedCallIcon.vue'
|
||||
import DeclinedCallIcon from '@/components/Icons/DeclinedCallIcon.vue'
|
||||
import InboundCallIcon from '@/components/Icons/InboundCallIcon.vue'
|
||||
import OutboundCallIcon from '@/components/Icons/OutboundCallIcon.vue'
|
||||
import FadedScrollableDiv from '@/components/FadedScrollableDiv.vue'
|
||||
import CommunicationArea from '@/components/CommunicationArea.vue'
|
||||
import WhatsappTemplateSelectorModal from '@/components/Modals/WhatsappTemplateSelectorModal.vue'
|
||||
import AllModals from '@/components/Activities/AllModals.vue'
|
||||
import {
|
||||
timeAgo,
|
||||
dateFormat,
|
||||
dateTooltipFormat,
|
||||
secondsToDuration,
|
||||
startCase,
|
||||
} from '@/utils'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { contactsStore } from '@/stores/contacts'
|
||||
import { whatsappEnabled } from '@/composables/settings'
|
||||
import { Button, Tooltip, createResource } from 'frappe-ui'
|
||||
import { useElementVisibility } from '@vueuse/core'
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
h,
|
||||
markRaw,
|
||||
watch,
|
||||
nextTick,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
} from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const { makeCall, $socket } = globalStore()
|
||||
const { getUser } = usersStore()
|
||||
const { getContact, getLeadContact } = contactsStore()
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Activity',
|
||||
},
|
||||
doctype: {
|
||||
type: String,
|
||||
default: 'CRM Lead',
|
||||
},
|
||||
tabs: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
|
||||
const doc = defineModel()
|
||||
const reload = defineModel('reload')
|
||||
const tabIndex = defineModel('tabIndex')
|
||||
|
||||
const reload_email = ref(false)
|
||||
const modalRef = ref(null)
|
||||
|
||||
const all_activities = createResource({
|
||||
url: 'crm.api.activities.get_activities',
|
||||
params: { name: doc.value.data.name },
|
||||
cache: ['activity', doc.value.data.name],
|
||||
auto: true,
|
||||
transform: ([versions, calls, notes, tasks]) => {
|
||||
if (calls?.length) {
|
||||
calls.forEach((doc) => {
|
||||
doc.show_recording = false
|
||||
doc.activity_type =
|
||||
doc.type === 'Incoming' ? 'incoming_call' : 'outgoing_call'
|
||||
doc.duration = secondsToDuration(doc.duration)
|
||||
if (doc.type === 'Incoming') {
|
||||
doc.caller = {
|
||||
label:
|
||||
getContact(doc.from)?.full_name ||
|
||||
getLeadContact(doc.from)?.full_name ||
|
||||
'Unknown',
|
||||
image:
|
||||
getContact(doc.from)?.image || getLeadContact(doc.from)?.image,
|
||||
}
|
||||
doc.receiver = {
|
||||
label: getUser(doc.receiver).full_name,
|
||||
image: getUser(doc.receiver).user_image,
|
||||
}
|
||||
} else {
|
||||
doc.caller = {
|
||||
label: getUser(doc.caller).full_name,
|
||||
image: getUser(doc.caller).user_image,
|
||||
}
|
||||
doc.receiver = {
|
||||
label:
|
||||
getContact(doc.to)?.full_name ||
|
||||
getLeadContact(doc.to)?.full_name ||
|
||||
'Unknown',
|
||||
image: getContact(doc.to)?.image || getLeadContact(doc.to)?.image,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return { versions, calls, notes, tasks }
|
||||
},
|
||||
})
|
||||
|
||||
const showWhatsappTemplates = ref(false)
|
||||
|
||||
const whatsappMessages = createResource({
|
||||
url: 'crm.api.whatsapp.get_whatsapp_messages',
|
||||
cache: ['whatsapp_messages', doc.value.data.name],
|
||||
params: {
|
||||
reference_doctype: props.doctype,
|
||||
reference_name: doc.value.data.name,
|
||||
},
|
||||
auto: true,
|
||||
transform: (data) => sortByCreation(data),
|
||||
onSuccess: () => nextTick(() => scroll()),
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
$socket.off('whatsapp_message')
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
$socket.on('whatsapp_message', (data) => {
|
||||
if (
|
||||
data.reference_doctype === props.doctype &&
|
||||
data.reference_name === doc.value.data.name
|
||||
) {
|
||||
whatsappMessages.reload()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function sendTemplate(template) {
|
||||
showWhatsappTemplates.value = false
|
||||
createResource({
|
||||
url: 'crm.api.whatsapp.send_whatsapp_template',
|
||||
params: {
|
||||
reference_doctype: props.doctype,
|
||||
reference_name: doc.value.data.name,
|
||||
to: doc.value.data.mobile_no,
|
||||
template,
|
||||
},
|
||||
auto: true,
|
||||
})
|
||||
}
|
||||
|
||||
const replyMessage = ref({})
|
||||
|
||||
function get_activities() {
|
||||
if (!all_activities.data?.versions) return []
|
||||
if (!all_activities.data?.calls.length)
|
||||
return all_activities.data.versions || []
|
||||
return [...all_activities.data.versions, ...all_activities.data.calls]
|
||||
}
|
||||
|
||||
const activities = computed(() => {
|
||||
let activities = []
|
||||
if (props.title == 'Activity') {
|
||||
activities = get_activities()
|
||||
} else if (props.title == 'Emails') {
|
||||
if (!all_activities.data?.versions) return []
|
||||
activities = all_activities.data.versions.filter(
|
||||
(activity) => activity.activity_type === 'communication',
|
||||
)
|
||||
} else if (props.title == 'Comments') {
|
||||
if (!all_activities.data?.versions) return []
|
||||
activities = all_activities.data.versions.filter(
|
||||
(activity) => activity.activity_type === 'comment',
|
||||
)
|
||||
} else if (props.title == 'Calls') {
|
||||
if (!all_activities.data?.calls) return []
|
||||
return sortByCreation(all_activities.data.calls)
|
||||
} else if (props.title == 'Tasks') {
|
||||
if (!all_activities.data?.tasks) return []
|
||||
return sortByCreation(all_activities.data.tasks)
|
||||
} else if (props.title == 'Notes') {
|
||||
if (!all_activities.data?.notes) return []
|
||||
return sortByCreation(all_activities.data.notes)
|
||||
}
|
||||
|
||||
activities.forEach((activity) => {
|
||||
activity.icon = timelineIcon(activity.activity_type, activity.is_lead)
|
||||
|
||||
if (
|
||||
activity.activity_type == 'incoming_call' ||
|
||||
activity.activity_type == 'outgoing_call' ||
|
||||
activity.activity_type == 'communication'
|
||||
)
|
||||
return
|
||||
|
||||
update_activities_details(activity)
|
||||
|
||||
if (activity.other_versions) {
|
||||
activity.show_others = false
|
||||
activity.other_versions.forEach((other_version) => {
|
||||
update_activities_details(other_version)
|
||||
})
|
||||
}
|
||||
})
|
||||
return sortByCreation(activities)
|
||||
})
|
||||
|
||||
function sortByCreation(list) {
|
||||
return list.sort((a, b) => new Date(a.creation) - new Date(b.creation))
|
||||
}
|
||||
|
||||
function update_activities_details(activity) {
|
||||
activity.owner_name = getUser(activity.owner).full_name
|
||||
activity.type = ''
|
||||
activity.value = ''
|
||||
activity.to = ''
|
||||
|
||||
if (activity.activity_type == 'creation') {
|
||||
activity.type = activity.data
|
||||
} else if (activity.activity_type == 'added') {
|
||||
activity.type = 'added'
|
||||
activity.value = 'as'
|
||||
} else if (activity.activity_type == 'removed') {
|
||||
activity.type = 'removed'
|
||||
activity.value = 'value'
|
||||
} else if (activity.activity_type == 'changed') {
|
||||
activity.type = 'changed'
|
||||
activity.value = 'from'
|
||||
activity.to = 'to'
|
||||
}
|
||||
}
|
||||
|
||||
const emptyText = computed(() => {
|
||||
let text = 'No Activities'
|
||||
if (props.title == 'Emails') {
|
||||
text = 'No Email Communications'
|
||||
} else if (props.title == 'Comments') {
|
||||
text = 'No Comments'
|
||||
} else if (props.title == 'Calls') {
|
||||
text = 'No Call Logs'
|
||||
} else if (props.title == 'Notes') {
|
||||
text = 'No Notes'
|
||||
} else if (props.title == 'Tasks') {
|
||||
text = 'No Tasks'
|
||||
} else if (props.title == 'WhatsApp') {
|
||||
text = 'No WhatsApp Messages'
|
||||
}
|
||||
return text
|
||||
})
|
||||
|
||||
const emptyTextIcon = computed(() => {
|
||||
let icon = ActivityIcon
|
||||
if (props.title == 'Emails') {
|
||||
icon = Email2Icon
|
||||
} else if (props.title == 'Comments') {
|
||||
icon = CommentIcon
|
||||
} else if (props.title == 'Calls') {
|
||||
icon = PhoneIcon
|
||||
} else if (props.title == 'Notes') {
|
||||
icon = NoteIcon
|
||||
} else if (props.title == 'Tasks') {
|
||||
icon = TaskIcon
|
||||
} else if (props.title == 'WhatsApp') {
|
||||
icon = WhatsAppIcon
|
||||
}
|
||||
return h(icon, { class: 'text-gray-500' })
|
||||
})
|
||||
|
||||
function timelineIcon(activity_type, is_lead) {
|
||||
let icon
|
||||
switch (activity_type) {
|
||||
case 'creation':
|
||||
icon = is_lead ? LeadsIcon : DealsIcon
|
||||
break
|
||||
case 'deal':
|
||||
icon = DealsIcon
|
||||
break
|
||||
case 'comment':
|
||||
icon = CommentIcon
|
||||
break
|
||||
case 'incoming_call':
|
||||
icon = InboundCallIcon
|
||||
break
|
||||
case 'outgoing_call':
|
||||
icon = OutboundCallIcon
|
||||
break
|
||||
default:
|
||||
icon = DotIcon
|
||||
}
|
||||
|
||||
return markRaw(icon)
|
||||
}
|
||||
|
||||
const emailBox = ref(null)
|
||||
const whatsappBox = ref(null)
|
||||
|
||||
watch([reload, reload_email], ([reload_value, reload_email_value]) => {
|
||||
if (reload_value || reload_email_value) {
|
||||
all_activities.reload()
|
||||
reload.value = false
|
||||
reload_email.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function scroll(hash) {
|
||||
setTimeout(() => {
|
||||
let el
|
||||
if (!hash) {
|
||||
let e = document.getElementsByClassName('activity')
|
||||
el = e[e.length - 1]
|
||||
} else {
|
||||
el = document.getElementById(hash)
|
||||
}
|
||||
if (el && !useElementVisibility(el).value) {
|
||||
el.scrollIntoView({ behavior: 'smooth' })
|
||||
el.focus()
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
defineExpose({ emailBox })
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
nextTick(() => {
|
||||
const hash = route.hash.slice(1) || null
|
||||
scroll(hash)
|
||||
})
|
||||
</script>
|
||||
157
frontend/src/components/Activities/ActivityHeader.vue
Normal file
157
frontend/src/components/Activities/ActivityHeader.vue
Normal file
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div
|
||||
class="mx-4 my-3 flex items-center justify-between text-lg font-medium sm:mx-10 sm:mb-4 sm:mt-8"
|
||||
>
|
||||
<div class="flex h-8 items-center text-xl font-semibold text-gray-800">
|
||||
{{ __(title) }}
|
||||
</div>
|
||||
<Button
|
||||
v-if="title == 'Emails'"
|
||||
variant="solid"
|
||||
@click="emailBox.show = true"
|
||||
>
|
||||
<template #prefix>
|
||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
||||
</template>
|
||||
<span>{{ __('New Email') }}</span>
|
||||
</Button>
|
||||
<Button
|
||||
v-else-if="title == 'Comments'"
|
||||
variant="solid"
|
||||
@click="emailBox.showComment = true"
|
||||
>
|
||||
<template #prefix>
|
||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
||||
</template>
|
||||
<span>{{ __('New Comment') }}</span>
|
||||
</Button>
|
||||
<Button
|
||||
v-else-if="title == 'Calls'"
|
||||
variant="solid"
|
||||
@click="makeCall(doc.data.mobile_no)"
|
||||
>
|
||||
<template #prefix>
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
</template>
|
||||
<span>{{ __('Make a Call') }}</span>
|
||||
</Button>
|
||||
<Button
|
||||
v-else-if="title == 'Notes'"
|
||||
variant="solid"
|
||||
@click="modalRef.showNote()"
|
||||
>
|
||||
<template #prefix>
|
||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
||||
</template>
|
||||
<span>{{ __('New Note') }}</span>
|
||||
</Button>
|
||||
<Button
|
||||
v-else-if="title == 'Tasks'"
|
||||
variant="solid"
|
||||
@click="modalRef.showTask()"
|
||||
>
|
||||
<template #prefix>
|
||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
||||
</template>
|
||||
<span>{{ __('New Task') }}</span>
|
||||
</Button>
|
||||
<div class="flex gap-2 shrink-0" v-else-if="title == 'WhatsApp'">
|
||||
<Button
|
||||
:label="__('Send Template')"
|
||||
@click="showWhatsappTemplates = true"
|
||||
/>
|
||||
<Button variant="solid" @click="whatsappBox.show()">
|
||||
<template #prefix>
|
||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
||||
</template>
|
||||
<span>{{ __('New Message') }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<Dropdown v-else :options="defaultActions" @click.stop>
|
||||
<template v-slot="{ open }">
|
||||
<Button variant="solid" class="flex items-center gap-1">
|
||||
<template #prefix>
|
||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
||||
</template>
|
||||
<span>{{ __('New') }}</span>
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import CommentIcon from '@/components/Icons/CommentIcon.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import NoteIcon from '@/components/Icons/NoteIcon.vue'
|
||||
import TaskIcon from '@/components/Icons/TaskIcon.vue'
|
||||
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { whatsappEnabled, callEnabled } from '@/composables/settings'
|
||||
import { Dropdown } from 'frappe-ui'
|
||||
import { computed, h } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
tabs: Array,
|
||||
title: String,
|
||||
doc: Object,
|
||||
modalRef: Object,
|
||||
emailBox: Object,
|
||||
whatsappBox: Object,
|
||||
})
|
||||
|
||||
const { makeCall } = globalStore()
|
||||
|
||||
const tabIndex = defineModel()
|
||||
const showWhatsappTemplates = defineModel('showWhatsappTemplates')
|
||||
|
||||
const defaultActions = computed(() => {
|
||||
let actions = [
|
||||
{
|
||||
icon: h(Email2Icon, { class: 'h-4 w-4' }),
|
||||
label: __('New Email'),
|
||||
onClick: () => (props.emailBox.show = true),
|
||||
},
|
||||
{
|
||||
icon: h(CommentIcon, { class: 'h-4 w-4' }),
|
||||
label: __('New Comment'),
|
||||
onClick: () => (props.emailBox.showComment = true),
|
||||
},
|
||||
{
|
||||
icon: h(PhoneIcon, { class: 'h-4 w-4' }),
|
||||
label: __('Make a Call'),
|
||||
onClick: () => makeCall(props.doc.data.mobile_no),
|
||||
condition: () => callEnabled.value,
|
||||
},
|
||||
{
|
||||
icon: h(NoteIcon, { class: 'h-4 w-4' }),
|
||||
label: __('New Note'),
|
||||
onClick: () => props.modalRef.showNote(),
|
||||
},
|
||||
{
|
||||
icon: h(TaskIcon, { class: 'h-4 w-4' }),
|
||||
label: __('New Task'),
|
||||
onClick: () => props.modalRef.showTask(),
|
||||
},
|
||||
{
|
||||
icon: h(WhatsAppIcon, { class: 'h-4 w-4' }),
|
||||
label: __('New WhatsApp Message'),
|
||||
onClick: () => (tabIndex.value = getTabIndex('WhatsApp')),
|
||||
condition: () => whatsappEnabled.value,
|
||||
},
|
||||
]
|
||||
return actions.filter((action) =>
|
||||
action.condition ? action.condition() : true,
|
||||
)
|
||||
})
|
||||
|
||||
function getTabIndex(name) {
|
||||
return props.tabs.findIndex((tab) => tab.name === name)
|
||||
}
|
||||
</script>
|
||||
83
frontend/src/components/Activities/AllModals.vue
Normal file
83
frontend/src/components/Activities/AllModals.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<TaskModal
|
||||
v-model="showTaskModal"
|
||||
v-model:reloadTasks="activities"
|
||||
:task="task"
|
||||
:doctype="doctype"
|
||||
:doc="doc.data?.name"
|
||||
/>
|
||||
<NoteModal
|
||||
v-model="showNoteModal"
|
||||
v-model:reloadNotes="activities"
|
||||
:note="note"
|
||||
:doctype="doctype"
|
||||
:doc="doc.data?.name"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import TaskModal from '@/components/Modals/TaskModal.vue'
|
||||
import NoteModal from '@/components/Modals/NoteModal.vue'
|
||||
import { call } from 'frappe-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
doctype: String,
|
||||
})
|
||||
|
||||
const activities = defineModel()
|
||||
const doc = defineModel('doc')
|
||||
|
||||
// Tasks
|
||||
const showTaskModal = ref(false)
|
||||
const task = ref({})
|
||||
|
||||
function showTask(t) {
|
||||
task.value = t || {
|
||||
title: '',
|
||||
description: '',
|
||||
assigned_to: '',
|
||||
due_date: '',
|
||||
priority: 'Low',
|
||||
status: 'Backlog',
|
||||
}
|
||||
showTaskModal.value = true
|
||||
}
|
||||
|
||||
async function deleteTask(name) {
|
||||
await call('frappe.client.delete', {
|
||||
doctype: 'CRM Task',
|
||||
name,
|
||||
})
|
||||
activities.value.reload()
|
||||
}
|
||||
|
||||
function updateTaskStatus(status, task) {
|
||||
call('frappe.client.set_value', {
|
||||
doctype: 'CRM Task',
|
||||
name: task.name,
|
||||
fieldname: 'status',
|
||||
value: status,
|
||||
}).then(() => {
|
||||
activities.value.reload()
|
||||
})
|
||||
}
|
||||
|
||||
// Notes
|
||||
const showNoteModal = ref(false)
|
||||
const note = ref({})
|
||||
|
||||
function showNote(n) {
|
||||
note.value = n || {
|
||||
title: '',
|
||||
content: '',
|
||||
}
|
||||
showNoteModal.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
showTask,
|
||||
deleteTask,
|
||||
updateTaskStatus,
|
||||
showNote,
|
||||
})
|
||||
</script>
|
||||
233
frontend/src/components/Activities/AudioPlayer.vue
Normal file
233
frontend/src/components/Activities/AudioPlayer.vue
Normal file
@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div class="w-full text-sm text-gray-600">
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="ghost" @click="playPause">
|
||||
<template #icon>
|
||||
<PlayIcon v-if="isPaused" class="size-4 text-gray-600" />
|
||||
<PauseIcon v-else class="size-4 text-gray-600" />
|
||||
</template>
|
||||
</Button>
|
||||
<div class="flex gap-2 items-center justify-between flex-1">
|
||||
<input
|
||||
class="w-full slider !h-[0.5] bg-gray-200 [&::-webkit-slider-thumb]:shadow [&::-webkit-slider-thumb:hover]:outline [&::-webkit-slider-thumb:hover]:outline-[0.5px]"
|
||||
:style="{
|
||||
background: `linear-gradient(to right, #171717 ${progress}%, #ededed ${progress}%)`,
|
||||
}"
|
||||
type="range"
|
||||
id="track"
|
||||
min="0"
|
||||
:max="duration"
|
||||
:value="currentTime"
|
||||
step="0.01"
|
||||
@input="(e) => (audio.currentTime = e.target.value)"
|
||||
/>
|
||||
<div class="shrink-0">
|
||||
{{ formatTime(currentTime) }} / {{ formatTime(duration) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex group gap-2 items-center">
|
||||
<input
|
||||
class="slider opacity-0 group-hover:opacity-100 w-0 group-hover:w-20 !h-[0.5] [&::-webkit-slider-thumb]:shadow [&::-webkit-slider-thumb:hover]:outline [&::-webkit-slider-thumb:hover]:outline-[0.5px]"
|
||||
:style="{
|
||||
background: `linear-gradient(to right, #171717 ${volumnProgress}%, #ededed ${volumnProgress}%)`,
|
||||
}"
|
||||
type="range"
|
||||
id="volume"
|
||||
min="0"
|
||||
max="1"
|
||||
:value="currentVolumn"
|
||||
step="0.01"
|
||||
@input="(e) => updateVolumnProgress(e.target.value)"
|
||||
/>
|
||||
<Button variant="ghost">
|
||||
<template #icon>
|
||||
<MuteIcon
|
||||
v-if="volumnProgress == 0"
|
||||
class="size-4"
|
||||
@click="updateVolumnProgress('1')"
|
||||
/>
|
||||
<VolumnLowIcon
|
||||
v-else-if="volumnProgress <= 40"
|
||||
class="size-4"
|
||||
@click="updateVolumnProgress('0')"
|
||||
/>
|
||||
<VolumnHighIcon
|
||||
v-else-if="volumnProgress > 20"
|
||||
class="size-4"
|
||||
@click="updateVolumnProgress('0')"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
<Dropdown :options="options">
|
||||
<Button variant="ghost" @click="showPlaybackSpeed = false">
|
||||
<template #icon>
|
||||
<FeatherIcon class="size-4" name="more-horizontal" />
|
||||
</template>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<audio
|
||||
ref="audio"
|
||||
:src="src"
|
||||
crossorigin="anonymous"
|
||||
@loadedmetadata="setupDuration"
|
||||
@timeupdate="updateCurrentTime"
|
||||
@ended="isPaused = true"
|
||||
></audio>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import PlayIcon from '@/components/Icons/PlayIcon.vue'
|
||||
import PauseIcon from '@/components/Icons/PauseIcon.vue'
|
||||
import VolumnLowIcon from '@/components/Icons/VolumnLowIcon.vue'
|
||||
import VolumnHighIcon from '@/components/Icons/VolumnHighIcon.vue'
|
||||
import MuteIcon from '@/components/Icons/MuteIcon.vue'
|
||||
import PlaybackSpeedIcon from '@/components/Icons/PlaybackSpeedIcon.vue'
|
||||
import PlaybackSpeedOption from '@/components/Activities/PlaybackSpeedOption.vue'
|
||||
import Dropdown from '@/components/frappe-ui/Dropdown.vue'
|
||||
import { computed, h, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
src: String,
|
||||
})
|
||||
|
||||
const audio = ref(null)
|
||||
const isPaused = ref(true)
|
||||
|
||||
const duration = ref(0)
|
||||
const currentTime = ref(0)
|
||||
const progress = computed(() => (currentTime.value / duration.value) * 100)
|
||||
const currentVolumn = ref(1)
|
||||
const volumnProgress = ref(100)
|
||||
|
||||
function setupDuration() {
|
||||
duration.value = audio.value.duration
|
||||
}
|
||||
|
||||
function updateCurrentTime() {
|
||||
currentTime.value = audio.value.currentTime
|
||||
}
|
||||
|
||||
function playPause() {
|
||||
if (audio.value.paused) {
|
||||
audio.value.play()
|
||||
isPaused.value = false
|
||||
} else {
|
||||
audio.value.pause()
|
||||
isPaused.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(time) {
|
||||
if (isNaN(time)) return '00:00'
|
||||
const minutes = Math.floor(time / 60)
|
||||
const seconds = Math.floor(time % 60)
|
||||
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function updateVolumnProgress(value) {
|
||||
audio.value.volume = value
|
||||
currentVolumn.value = value
|
||||
volumnProgress.value = value * 100
|
||||
}
|
||||
|
||||
const showPlaybackSpeed = ref(false)
|
||||
const currentPlaybackSpeed = ref(1)
|
||||
|
||||
const options = computed(() => {
|
||||
let playbackSpeeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]
|
||||
|
||||
let playbackSpeedOptions = playbackSpeeds.map((speed) => {
|
||||
let label = `${speed}x`
|
||||
if (speed === 1) {
|
||||
label = __('Normal')
|
||||
}
|
||||
return {
|
||||
component: () =>
|
||||
h(PlaybackSpeedOption, {
|
||||
label,
|
||||
active: speed === currentPlaybackSpeed.value,
|
||||
onClick: () => {
|
||||
audio.value.playbackRate = speed
|
||||
showPlaybackSpeed.value = false
|
||||
currentPlaybackSpeed.value = speed
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
let _options = [
|
||||
{
|
||||
icon: 'download',
|
||||
label: __('Download'),
|
||||
onClick: () => {
|
||||
const a = document.createElement('a')
|
||||
a.href = props.src
|
||||
a.download = props.src.split('/').pop()
|
||||
a.click()
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: () => h(PlaybackSpeedIcon, { class: 'size-4' }),
|
||||
label: __('Playback speed'),
|
||||
onClick: (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
showPlaybackSpeed.value = true
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
return showPlaybackSpeed.value ? playbackSpeedOptions : _options
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.slider {
|
||||
--trackHeight: 2px;
|
||||
--thumbRadius: 14px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: 100px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-runnable-track {
|
||||
appearance: none;
|
||||
height: var(--trackHeight);
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.slider:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
width: var(--thumbRadius);
|
||||
height: var(--thumbRadius);
|
||||
margin-top: calc((var(--trackHeight) - var(--thumbRadius)) / 2);
|
||||
background: #fff;
|
||||
border-radius: 100px;
|
||||
pointer-events: all;
|
||||
appearance: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
width: var(--thumbRadius);
|
||||
height: var(--thumbRadius);
|
||||
margin-top: calc((var(--trackHeight) - var(--thumbRadius)) / 2);
|
||||
background: #fff;
|
||||
border-radius: 100px;
|
||||
pointer-events: all;
|
||||
appearance: none;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
106
frontend/src/components/Activities/CallArea.vue
Normal file
106
frontend/src/components/Activities/CallArea.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-1 flex items-center justify-stretch gap-2 py-1 text-base">
|
||||
<div class="inline-flex items-center flex-wrap gap-1 text-gray-600">
|
||||
<Avatar
|
||||
:image="activity.caller.image"
|
||||
:label="activity.caller.label"
|
||||
size="md"
|
||||
/>
|
||||
<span class="font-medium text-gray-800 ml-1">
|
||||
{{ activity.caller.label }}
|
||||
</span>
|
||||
<span>{{
|
||||
activity.type == 'Incoming'
|
||||
? __('has reached out')
|
||||
: __('has made a call')
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="ml-auto whitespace-nowrap">
|
||||
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||
<div class="text-sm text-gray-600">
|
||||
{{ __(timeAgo(activity.creation)) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col gap-2 border border-gray-200 rounded-md bg-white px-3 py-2.5"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="inline-flex gap-2 items-center text-base font-medium">
|
||||
<div>
|
||||
{{
|
||||
activity.type == 'Incoming'
|
||||
? __('Inbound Call')
|
||||
: __('Outbound Call')
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<MultipleAvatar
|
||||
:avatars="[
|
||||
{
|
||||
image: activity.caller.image,
|
||||
label: activity.caller.label,
|
||||
name: activity.caller.label,
|
||||
},
|
||||
{
|
||||
image: activity.receiver.image,
|
||||
label: activity.receiver.label,
|
||||
name: activity.receiver.label,
|
||||
},
|
||||
]"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center flex-wrap gap-2">
|
||||
<Badge :label="dateFormat(activity.creation, 'MMM D, dddd')">
|
||||
<template #prefix>
|
||||
<CalendarIcon class="size-3" />
|
||||
</template>
|
||||
</Badge>
|
||||
<Badge v-if="activity.status == 'Completed'" :label="activity.duration">
|
||||
<template #prefix>
|
||||
<DurationIcon class="size-3" />
|
||||
</template>
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="activity.recording_url"
|
||||
:label="activity.show_recording ? __('Hide Recording') : __('Listen')"
|
||||
class="cursor-pointer"
|
||||
@click="activity.show_recording = !activity.show_recording"
|
||||
>
|
||||
<template #prefix>
|
||||
<PlayIcon class="size-3" />
|
||||
</template>
|
||||
</Badge>
|
||||
<Badge
|
||||
:label="statusLabelMap[activity.status]"
|
||||
:theme="statusColorMap[activity.status]"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="activity.show_recording && activity.recording_url"
|
||||
class="flex flex-col items-center justify-between"
|
||||
>
|
||||
<AudioPlayer :src="activity.recording_url" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import PlayIcon from '@/components/Icons/PlayIcon.vue'
|
||||
import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
|
||||
import DurationIcon from '@/components/Icons/DurationIcon.vue'
|
||||
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
||||
import AudioPlayer from '@/components/Activities/AudioPlayer.vue'
|
||||
import { statusLabelMap, statusColorMap } from '@/utils/callLog.js'
|
||||
import { dateFormat, timeAgo, dateTooltipFormat } from '@/utils'
|
||||
import { Avatar, Badge, Tooltip } from 'frappe-ui'
|
||||
|
||||
const props = defineProps({
|
||||
activity: Object,
|
||||
})
|
||||
</script>
|
||||
45
frontend/src/components/Activities/CommentArea.vue
Normal file
45
frontend/src/components/Activities/CommentArea.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div :id="activity.name">
|
||||
<div class="mb-1 flex items-center justify-stretch gap-2 py-1 text-base">
|
||||
<div class="inline-flex items-center flex-wrap gap-1 text-gray-600">
|
||||
<UserAvatar class="mr-1" :user="activity.owner" size="md" />
|
||||
<span class="font-medium text-gray-800">
|
||||
{{ activity.owner_name }}
|
||||
</span>
|
||||
<span>{{ __('added a') }}</span>
|
||||
<span class="max-w-xs truncate font-medium text-gray-800">
|
||||
{{ __('comment') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="ml-auto whitespace-nowrap">
|
||||
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||
<div class="text-sm text-gray-600">
|
||||
{{ __(timeAgo(activity.creation)) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="cursor-pointer rounded bg-gray-50 px-3 py-[7.5px] text-base leading-6 transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<div class="prose-f" v-html="activity.content" />
|
||||
<div v-if="activity.attachments.length" class="mt-2 flex flex-wrap gap-2">
|
||||
<AttachmentItem
|
||||
v-for="a in activity.attachments"
|
||||
:key="a.file_url"
|
||||
:label="a.file_name"
|
||||
:url="a.file_url"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import AttachmentItem from '@/components/AttachmentItem.vue'
|
||||
import { Tooltip } from 'frappe-ui'
|
||||
import { timeAgo, dateFormat, dateTooltipFormat } from '@/utils'
|
||||
const props = defineProps({
|
||||
activity: Object,
|
||||
})
|
||||
</script>
|
||||
144
frontend/src/components/Activities/EmailArea.vue
Normal file
144
frontend/src/components/Activities/EmailArea.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div
|
||||
class="cursor-pointer flex flex-col rounded-md shadow bg-white px-3 py-1.5 text-base transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<div class="-mb-0.5 flex items-center justify-between gap-2 truncate">
|
||||
<div class="flex items-center gap-2 truncate">
|
||||
<span>{{ activity.data.sender_full_name }}</span>
|
||||
<span class="sm:flex hidden text-sm text-gray-600">
|
||||
{{ '<' + activity.data.sender + '>' }}
|
||||
</span>
|
||||
<Badge
|
||||
v-if="activity.communication_type == 'Automated Message'"
|
||||
:label="__('Notification')"
|
||||
variant="subtle"
|
||||
theme="green"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||
<div class="text-sm text-gray-600">
|
||||
{{ __(timeAgo(activity.creation)) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div class="flex gap-0.5">
|
||||
<Tooltip :text="__('Reply')">
|
||||
<div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="text-gray-700"
|
||||
@click="reply(activity.data)"
|
||||
>
|
||||
<template #icon>
|
||||
<ReplyIcon />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip :text="__('Reply All')">
|
||||
<div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="text-gray-700"
|
||||
@click="reply(activity.data, true)"
|
||||
>
|
||||
<template #icon>
|
||||
<ReplyAllIcon />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 text-base text-gray-800">
|
||||
<div>
|
||||
<span class="mr-1 text-gray-600"> {{ __('To') }}: </span>
|
||||
<span>{{ activity.data.recipients }}</span>
|
||||
<span v-if="activity.data.cc">, </span>
|
||||
<span v-if="activity.data.cc" class="mr-1 text-gray-600">
|
||||
{{ __('CC') }}:
|
||||
</span>
|
||||
<span v-if="activity.data.cc">{{ activity.data.cc }}</span>
|
||||
<span v-if="activity.data.bcc">, </span>
|
||||
<span v-if="activity.data.bcc" class="mr-1 text-gray-600">
|
||||
{{ __('BCC') }}:
|
||||
</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" />
|
||||
<EmailContent :content="activity.data.content" />
|
||||
<div v-if="activity.data?.attachments?.length" 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>
|
||||
</template>
|
||||
<script setup>
|
||||
import ReplyIcon from '@/components/Icons/ReplyIcon.vue'
|
||||
import ReplyAllIcon from '@/components/Icons/ReplyAllIcon.vue'
|
||||
import AttachmentItem from '@/components/AttachmentItem.vue'
|
||||
import EmailContent from '@/components/Activities/EmailContent.vue'
|
||||
import { Badge, Tooltip } from 'frappe-ui'
|
||||
import { timeAgo, dateFormat, dateTooltipFormat } from '@/utils'
|
||||
|
||||
const props = defineProps({
|
||||
activity: Object,
|
||||
emailBox: Object,
|
||||
})
|
||||
|
||||
function reply(email, reply_all = false) {
|
||||
props.emailBox.show = true
|
||||
let editor = props.emailBox.editor
|
||||
let message = email.content
|
||||
let recipients = email.recipients.split(',').map((r) => r.trim())
|
||||
editor.toEmails = [email.sender]
|
||||
editor.cc = editor.bcc = false
|
||||
editor.ccEmails = []
|
||||
editor.bccEmails = []
|
||||
|
||||
if (!email.subject.startsWith('Re:')) {
|
||||
editor.subject = `Re: ${email.subject}`
|
||||
}
|
||||
|
||||
if (reply_all) {
|
||||
let cc = email.cc?.split(',').map((r) => r.trim())
|
||||
let bcc = email.bcc?.split(',').map((r) => r.trim())
|
||||
|
||||
if (cc?.length) {
|
||||
recipients = recipients.filter((r) => !cc?.includes(r))
|
||||
cc.push(...recipients)
|
||||
} else {
|
||||
cc = recipients
|
||||
}
|
||||
|
||||
editor.cc = cc ? true : false
|
||||
editor.bcc = bcc ? true : false
|
||||
|
||||
editor.ccEmails = cc
|
||||
editor.bccEmails = bcc
|
||||
}
|
||||
|
||||
let repliedMessage = `<blockquote>${message}</blockquote>`
|
||||
|
||||
editor.editor
|
||||
.chain()
|
||||
.clearContent()
|
||||
.insertContent('<p>.</p>')
|
||||
.updateAttributes('paragraph', { class: 'reply-to-content' })
|
||||
.insertContent(repliedMessage)
|
||||
.focus('all')
|
||||
.insertContentAt(0, { type: 'paragraph' })
|
||||
.focus('start')
|
||||
.run()
|
||||
}
|
||||
</script>
|
||||
@ -2,15 +2,7 @@
|
||||
<iframe
|
||||
ref="iframeRef"
|
||||
:srcdoc="htmlContent"
|
||||
class="prose-f block h-screen max-h-[500px] w-full"
|
||||
style="
|
||||
height: 40px;
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
black calc(100% - 20px),
|
||||
transparent 100%
|
||||
);
|
||||
"
|
||||
class="prose-f block h-10 max-h-[500px] w-full"
|
||||
/>
|
||||
</template>
|
||||
|
||||
73
frontend/src/components/Activities/NoteArea.vue
Normal file
73
frontend/src/components/Activities/NoteArea.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div
|
||||
class="activity group flex h-48 cursor-pointer flex-col justify-between gap-2 rounded-md bg-gray-50 px-4 py-3 hover:bg-gray-100"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="truncate text-lg font-medium">
|
||||
{{ note.title }}
|
||||
</div>
|
||||
<Dropdown
|
||||
:options="[
|
||||
{
|
||||
label: __('Delete'),
|
||||
icon: 'trash-2',
|
||||
onClick: () => deleteNote(note.name),
|
||||
},
|
||||
]"
|
||||
@click.stop
|
||||
class="h-6 w-6"
|
||||
>
|
||||
<Button
|
||||
icon="more-horizontal"
|
||||
variant="ghosted"
|
||||
class="!h-6 !w-6 hover:bg-gray-100"
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<TextEditor
|
||||
v-if="note.content"
|
||||
:content="note.content"
|
||||
:editable="false"
|
||||
editor-class="!prose-sm max-w-none !text-sm text-gray-600 focus:outline-none"
|
||||
class="flex-1 overflow-hidden"
|
||||
/>
|
||||
<div class="mt-1 flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-2 truncate">
|
||||
<UserAvatar :user="note.owner" size="xs" />
|
||||
<div
|
||||
class="truncate text-sm text-gray-800"
|
||||
:title="getUser(note.owner).full_name"
|
||||
>
|
||||
{{ getUser(note.owner).full_name }}
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip :text="dateFormat(note.modified, dateTooltipFormat)">
|
||||
<div class="truncate text-sm text-gray-700">
|
||||
{{ __(timeAgo(note.modified)) }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import { timeAgo, dateFormat, dateTooltipFormat } from '@/utils'
|
||||
import { Tooltip, Dropdown, TextEditor } from 'frappe-ui'
|
||||
import { usersStore } from '@/stores/users'
|
||||
|
||||
const props = defineProps({
|
||||
note: Object,
|
||||
})
|
||||
|
||||
const notes = defineModel()
|
||||
|
||||
const { getUser } = usersStore()
|
||||
|
||||
async function deleteNote(name) {
|
||||
await call('frappe.client.delete', {
|
||||
doctype: 'FCRM Note',
|
||||
name,
|
||||
})
|
||||
notes.reload()
|
||||
}
|
||||
</script>
|
||||
21
frontend/src/components/Activities/PlaybackSpeedOption.vue
Normal file
21
frontend/src/components/Activities/PlaybackSpeedOption.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<Button
|
||||
class="flex justify-between w-full rounded text-base"
|
||||
variant="ghost"
|
||||
:label="label"
|
||||
@click="onClick"
|
||||
>
|
||||
<template v-if="active" #suffix>
|
||||
<FeatherIcon class="size-4" name="check" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
label: String,
|
||||
active: Boolean,
|
||||
onClick: Array,
|
||||
})
|
||||
</script>
|
||||
109
frontend/src/components/Activities/TaskArea.vue
Normal file
109
frontend/src/components/Activities/TaskArea.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div v-if="tasks.length">
|
||||
<div v-for="(task, i) in tasks">
|
||||
<div
|
||||
class="activity flex cursor-pointer gap-6 rounded p-2.5 duration-300 ease-in-out hover:bg-gray-50"
|
||||
@click="modalRef.showTask(task)"
|
||||
>
|
||||
<div class="flex flex-1 flex-col gap-1.5 text-base">
|
||||
<div class="font-medium text-gray-900">
|
||||
{{ task.title }}
|
||||
</div>
|
||||
<div class="flex gap-1.5 text-gray-800">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<UserAvatar :user="task.assigned_to" size="xs" />
|
||||
{{ getUser(task.assigned_to).full_name }}
|
||||
</div>
|
||||
<div v-if="task.due_date" class="flex items-center justify-center">
|
||||
<DotIcon class="h-2.5 w-2.5 text-gray-600" :radius="2" />
|
||||
</div>
|
||||
<div v-if="task.due_date">
|
||||
<Tooltip
|
||||
:text="dateFormat(task.due_date, 'ddd, MMM D, YYYY | 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">
|
||||
<DotIcon class="h-2.5 w-2.5 text-gray-600" :radius="2" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<TaskPriorityIcon class="!h-2 !w-2" :priority="task.priority" />
|
||||
{{ task.priority }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Dropdown
|
||||
:options="taskStatusOptions(modalRef.updateTaskStatus, task)"
|
||||
@click.stop
|
||||
>
|
||||
<Tooltip :text="__('Change Status')">
|
||||
<Button variant="ghosted" class="hover:bg-gray-300">
|
||||
<TaskStatusIcon :status="task.status" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
:options="[
|
||||
{
|
||||
label: __('Delete'),
|
||||
icon: 'trash-2',
|
||||
onClick: () => {
|
||||
$dialog({
|
||||
title: __('Delete Task'),
|
||||
message: __('Are you sure you want to delete this task?'),
|
||||
actions: [
|
||||
{
|
||||
label: __('Delete'),
|
||||
theme: 'red',
|
||||
variant: 'solid',
|
||||
onClick(close) {
|
||||
modalRef.deleteTask(task.name)
|
||||
close()
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
},
|
||||
]"
|
||||
@click.stop
|
||||
>
|
||||
<Button
|
||||
icon="more-horizontal"
|
||||
variant="ghosted"
|
||||
class="hover:bg-gray-300"
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="i < tasks.length - 1"
|
||||
class="mx-2 h-px border-t border-gray-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
|
||||
import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue'
|
||||
import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue'
|
||||
import DotIcon from '@/components/Icons/DotIcon.vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import { dateFormat, taskStatusOptions } from '@/utils'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { Tooltip, Dropdown } from 'frappe-ui'
|
||||
|
||||
const props = defineProps({
|
||||
tasks: Array,
|
||||
modalRef: Object,
|
||||
})
|
||||
|
||||
const { getUser } = usersStore()
|
||||
const { $dialog } = globalStore()
|
||||
</script>
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="reply?.message"
|
||||
class="flex items-center justify-around gap-2 px-4 pt-2 sm:px-10"
|
||||
class="flex items-center justify-around gap-2 px-3 pt-2 sm:px-10"
|
||||
>
|
||||
<div
|
||||
class="mb-1 ml-13 flex-1 cursor-pointer rounded border-0 border-l-4 border-green-500 bg-gray-100 p-2 text-base text-gray-600"
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
<Button variant="ghost" icon="x" @click="reply = {}" />
|
||||
</div>
|
||||
<div class="flex items-end gap-2 px-4 py-2.5 sm:px-10" v-bind="$attrs">
|
||||
<div class="flex items-end gap-2 px-3 py-2.5 sm:px-10" v-bind="$attrs">
|
||||
<div class="flex h-8 items-center gap-2">
|
||||
<FileUploader @success="(file) => uploadFile(file)">
|
||||
<template v-slot="{ openFileSelector }">
|
||||
@ -9,7 +9,7 @@
|
||||
@click="toggleEmailBox()"
|
||||
>
|
||||
<template #prefix>
|
||||
<EmailIcon class="h-4" />
|
||||
<Email2Icon class="h-4" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
@ -87,7 +87,7 @@
|
||||
import EmailEditor from '@/components/EmailEditor.vue'
|
||||
import CommentBox from '@/components/CommentBox.vue'
|
||||
import CommentIcon from '@/components/Icons/CommentIcon.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { call, createResource } from 'frappe-ui'
|
||||
|
||||
@ -143,7 +143,7 @@
|
||||
@click="showEmailTemplateSelectorModal = true"
|
||||
>
|
||||
<template #icon>
|
||||
<EmailIcon class="h-4" />
|
||||
<Email2Icon class="h-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
@ -169,7 +169,7 @@
|
||||
<script setup>
|
||||
import IconPicker from '@/components/IconPicker.vue'
|
||||
import SmileIcon from '@/components/Icons/SmileIcon.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import AttachmentIcon from '@/components/Icons/AttachmentIcon.vue'
|
||||
import AttachmentItem from '@/components/AttachmentItem.vue'
|
||||
import MultiselectInput from '@/components/Controls/MultiselectInput.vue'
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M4.23264 0.640137C4.50878 0.640137 4.73264 0.863994 4.73264 1.14014V2.21646L5.00391 2.21638H11.0039C11.0946 2.21638 11.1824 2.21638 11.2674 2.21645V1.14014C11.2674 0.863994 11.4912 0.640137 11.7674 0.640137C12.0435 0.640137 12.2674 0.863994 12.2674 1.14014V2.21729C12.2674 2.22121 12.2673 2.22513 12.2672 2.22904C12.886 2.25159 13.2963 2.31433 13.6389 2.48886C14.1093 2.72855 14.4917 3.111 14.7314 3.5814C15.0039 4.11618 15.0039 4.81625 15.0039 6.21638V11.0164C15.0039 12.4165 15.0039 13.1166 14.7314 13.6514C14.4917 14.1218 14.1093 14.5042 13.6389 14.7439C13.1041 15.0164 12.404 15.0164 11.0039 15.0164H5.00391C3.60378 15.0164 2.90371 15.0164 2.36893 14.7439C1.89852 14.5042 1.51607 14.1218 1.27639 13.6514C1.00391 13.1166 1.00391 12.4165 1.00391 11.0164V6.21638C1.00391 4.81625 1.00391 4.11618 1.27639 3.5814C1.51607 3.111 1.89852 2.72855 2.36893 2.48886C2.71004 2.31506 3.1184 2.25212 3.73278 2.22932L3.73264 2.21729V1.14014C3.73264 0.863994 3.9565 0.640137 4.23264 0.640137ZM11.0039 3.21638H5.00391C4.28734 3.21638 3.81006 3.21716 3.44334 3.24712C3.0883 3.27613 2.92584 3.32743 2.82292 3.37987C2.54068 3.52368 2.31121 3.75315 2.1674 4.0354C2.11496 4.13832 2.06365 4.30078 2.03465 4.65582C2.0074 4.98935 2.00429 5.41434 2.00395 6.02764C2.02879 6.02384 2.05424 6.02186 2.08015 6.02186H13.9197C13.9484 6.02186 13.9765 6.02428 14.0039 6.02892C14.0035 5.41493 14.0004 4.98958 13.9732 4.65582C13.9442 4.30078 13.8929 4.13832 13.8404 4.0354C13.6966 3.75315 13.4671 3.52368 13.1849 3.37987C13.082 3.32743 12.9195 3.27613 12.5645 3.24712C12.1978 3.21716 11.7205 3.21638 11.0039 3.21638ZM14.0039 7.0148C13.9765 7.01945 13.9484 7.02186 13.9197 7.02186H2.08015C2.05423 7.02186 2.02877 7.01989 2.00391 7.01609V11.0164C2.00391 11.7329 2.00468 12.2102 2.03465 12.5769C2.06365 12.932 2.11496 13.0944 2.1674 13.1974C2.31121 13.4796 2.54068 13.7091 2.82292 13.8529C2.92584 13.9053 3.0883 13.9566 3.44334 13.9856C3.81006 14.0156 4.28734 14.0164 5.00391 14.0164H11.0039C11.7205 14.0164 12.1978 14.0156 12.5645 13.9856C12.9195 13.9566 13.082 13.9053 13.1849 13.8529C13.4671 13.7091 13.6966 13.4796 13.8404 13.1974C13.8929 13.0944 13.9442 12.932 13.9732 12.5769C14.0031 12.2102 14.0039 11.7329 14.0039 11.0164V7.0148ZM9.62 11.3961C9.62 11.0241 9.62 10.838 9.66889 10.6875C9.7678 10.3829 10.0066 10.1441 10.3111 10.0452C10.4617 9.99634 10.6477 9.99634 11.0198 9.99634H11.2531C11.6252 9.99634 11.8112 9.99634 11.9618 10.0452C12.2663 10.1441 12.5051 10.3829 12.604 10.6875C12.6529 10.838 12.6529 11.0241 12.6529 11.3961C12.6529 11.7682 12.6529 11.9542 12.604 12.1048C12.5051 12.4094 12.2663 12.6481 11.9618 12.747C11.8112 12.7959 11.6252 12.7959 11.2531 12.7959H11.0198C10.6477 12.7959 10.4617 12.7959 10.3111 12.747C10.0066 12.6481 9.7678 12.4094 9.66889 12.1048C9.62 11.9542 9.62 11.7682 9.62 11.3961Z"
|
||||
d="M6.29021 2.64272C6.68375 0.785763 9.33429 0.785757 9.72783 2.64272L10.3657 5.65238L13.3753 6.29021C15.2323 6.68375 15.2323 9.33429 13.3753 9.72783L10.3657 10.3657L9.72783 13.3753C9.33429 15.2323 6.68375 15.2323 6.29021 13.3753L5.65238 10.3657L2.64272 9.72783C0.785763 9.33429 0.785757 6.68375 2.64272 6.29021L5.65238 5.65238L6.29021 2.64272ZM8.74956 2.85004C8.58 2.04999 7.43804 2.04998 7.26848 2.85004L6.56324 6.17777L6.49584 6.49583L6.17777 6.56324L2.85004 7.26848C2.04999 7.43804 2.04998 8.58 2.85004 8.74956L6.17777 9.4548L6.49584 9.5222L6.56324 9.84027L7.26848 13.168C7.43804 13.9681 8.58 13.9681 8.74956 13.168L9.4548 9.84027L9.5222 9.5222L9.84027 9.4548L13.168 8.74956C13.9681 8.58 13.9681 7.43804 13.168 7.26848L9.84027 6.56324L9.5222 6.49583L9.4548 6.17777L8.74956 2.85004Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
28
frontend/src/components/Icons/DeclinedCallIcon.vue
Normal file
28
frontend/src/components/Icons/DeclinedCallIcon.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3.08176 7.30769C2.58005 6.18305 2.23673 4.92355 2.07547 3.60766C2.07054 3.53793 2.06535 3.47026 2.06011 3.40458C1.99269 2.56006 2.6315 2 3.47871 2H4.3983C4.85717 2 5.25715 2.31229 5.36844 2.75746L5.51307 3.33596C5.82745 4.59341 5.2987 5.91056 4.20214 6.60162L3.08176 7.30769ZM3.08176 7.30769C3.24962 7.68396 3.43521 8.04513 3.63764 8.38827C4.49814 9.85258 5.70274 11.085 7.14706 11.9786C7.42755 12.1528 7.71775 12.308 8.01716 12.4466C8.09664 12.4834 8.17677 12.5191 8.25753 12.5536M8.25753 12.5536C9.40645 13.0447 10.6848 13.3091 12.0659 13.4781L12.136 13.4867V13.4867C12.963 13.5892 13.5034 12.7424 13.5025 11.9091L13.5016 11.1801C13.5011 10.7318 13.2023 10.3386 12.7705 10.2181L12.2906 10.0842C10.9753 9.71699 9.57793 10.2845 8.89108 11.4648L8.25753 12.5536Z"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M10.8983 2.18433L13.8151 5.10114"
|
||||
stroke="currentColor"
|
||||
stroke-width="0.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle
|
||||
cx="12.25"
|
||||
cy="3.75"
|
||||
r="2.35"
|
||||
stroke="currentColor"
|
||||
stroke-width="0.8"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@ -1,24 +1,23 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14 8C14 11.3137 11.3137 14 8 14C4.68629 14 2 11.3137 2 8C2 4.68629 4.68629 2 8 2C11.3137 2 14 4.68629 14 8Z"
|
||||
stroke="currentColor"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8.00024 4.97693V8.00018L10.1666 10.1667"
|
||||
stroke="currentColor"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<g clip-path="url(#clip0_4750_5685)">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M10.4375 6C10.4375 8.45076 8.45076 10.4375 6 10.4375C3.54924 10.4375 1.5625 8.45076 1.5625 6C1.5625 3.54924 3.54924 1.5625 6 1.5625C8.45076 1.5625 10.4375 3.54924 10.4375 6ZM11.4375 6C11.4375 9.00305 9.00305 11.4375 6 11.4375C2.99695 11.4375 0.5625 9.00305 0.5625 6C0.5625 2.99695 2.99695 0.5625 6 0.5625C9.00305 0.5625 11.4375 2.99695 11.4375 6ZM9.00005 5.50004C9.27619 5.50004 9.50004 5.72391 9.50004 6.00005C9.50004 6.27619 9.27618 6.50004 9.00003 6.50004L6.25001 6.5C5.8358 6.49999 5.50003 6.16421 5.50002 5.75L5.5 3C5.5 2.72386 5.72385 2.5 6 2.5C6.27614 2.5 6.5 2.72385 6.5 3L6.50002 5.5L9.00005 5.50004Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4750_5685">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
16
frontend/src/components/Icons/Email2Icon.vue
Normal file
16
frontend/src/components/Icons/Email2Icon.vue
Normal 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="M4.00013 2C2.34328 2 1.00013 3.34315 1.00013 5V5.48845C0.999957 5.49602 0.999957 5.50357 1.00013 5.51111V11C1.00013 12.6569 2.34328 14 4.00013 14H12.0001C13.657 14 15.0001 12.6569 15.0001 11V5.51111C15.0003 5.50357 15.0003 5.49602 15.0001 5.48845V5C15.0001 3.34315 13.657 2 12.0001 2H4.00013ZM14.0001 5.16863V5C14.0001 3.89543 13.1047 3 12.0001 3H4.00013C2.89556 3 2.00013 3.89543 2.00013 5V5.16863L7.80531 7.62467C7.92985 7.67736 8.07041 7.67736 8.19495 7.62467L14.0001 5.16863ZM2.00013 6.25445V11C2.00013 12.1046 2.89556 13 4.00013 13H12.0001C13.1047 13 14.0001 12.1046 14.0001 11V6.25445L8.58459 8.54564C8.21098 8.7037 7.78928 8.7037 7.41567 8.54564L2.00013 6.25445Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@ -9,7 +9,7 @@
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M4.00013 2C2.34328 2 1.00013 3.34315 1.00013 5V5.48845C0.999957 5.49602 0.999957 5.50357 1.00013 5.51111V11C1.00013 12.6569 2.34328 14 4.00013 14H12.0001C13.657 14 15.0001 12.6569 15.0001 11V5.51111C15.0003 5.50357 15.0003 5.49602 15.0001 5.48845V5C15.0001 3.34315 13.657 2 12.0001 2H4.00013ZM14.0001 5.16863V5C14.0001 3.89543 13.1047 3 12.0001 3H4.00013C2.89556 3 2.00013 3.89543 2.00013 5V5.16863L7.80531 7.62467C7.92985 7.67736 8.07041 7.67736 8.19495 7.62467L14.0001 5.16863ZM2.00013 6.25445V11C2.00013 12.1046 2.89556 13 4.00013 13H12.0001C13.1047 13 14.0001 12.1046 14.0001 11V6.25445L8.58459 8.54564C8.21098 8.7037 7.78928 8.7037 7.41567 8.54564L2.00013 6.25445Z"
|
||||
d="M13.75 7.06416V12.1549C13.75 12.9834 13.0784 13.6549 12.25 13.6549H3.75C2.92157 13.6549 2.25 12.9834 2.25 12.1549V7.06416L6.81475 9.5221C7.55469 9.92053 8.44531 9.92053 9.18525 9.5221L13.75 7.06416ZM13.7469 5.93008C13.7137 5.41511 13.4172 4.95039 12.9583 4.70457L8.70833 2.42778C8.26586 2.19074 7.73414 2.19074 7.29167 2.42778L3.04167 4.70457C2.5828 4.95039 2.28629 5.41511 2.25311 5.93008L7.28885 8.64163C7.73282 8.88069 8.26719 8.88069 8.71115 8.64163L13.7469 5.93008ZM1.25 6.02679C1.25 5.10517 1.75706 4.25829 2.56945 3.82309L6.81945 1.5463C7.55691 1.15123 8.4431 1.15123 9.18055 1.5463L13.4306 3.82309C14.2429 4.25829 14.75 5.10517 14.75 6.02679V12.1549C14.75 13.5356 13.6307 14.6549 12.25 14.6549H3.75C2.36929 14.6549 1.25 13.5356 1.25 12.1549V6.02679Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M10.7659 5.02696V2.49055C10.7659 2.21441 10.5421 1.99055 10.2659 1.99055C9.98979 1.99055 9.76593 2.21441 9.76593 2.49055V6.22461C9.76593 6.50075 9.98979 6.72461 10.2659 6.72461L14 6.72461C14.2761 6.72461 14.5 6.50075 14.5 6.22461C14.5 5.94847 14.2761 5.72461 14 5.72461L11.4825 5.72461L14.8535 2.35355C15.0488 2.15829 15.0488 1.84171 14.8535 1.64645C14.6583 1.45118 14.3417 1.45118 14.1464 1.64645L10.7659 5.02696ZM3.95262 3.01471C3.92305 2.96757 3.85703 2.96079 3.81849 3.00094L2.78472 4.07779C2.49483 4.37975 2.42118 4.80645 2.58821 5.15824C2.84578 5.7007 3.21973 6.43293 3.69626 7.22349L5.4337 5.5008C5.46212 5.47262 5.4672 5.42851 5.44593 5.39462L3.95262 3.01471ZM4.24559 8.08705L6.13779 6.21091C6.49848 5.85329 6.56295 5.29336 6.29299 4.86312L4.79968 2.48321C4.42423 1.88484 3.58632 1.79881 3.09711 2.30841L2.06333 3.38526C1.50617 3.96563 1.32728 4.83405 1.68487 5.58716C2.28388 6.84874 3.52538 9.17888 5.26635 10.9491C7.06226 12.7753 9.57149 14.1765 10.8502 14.8261C11.5427 15.1779 12.3553 15.0724 12.9518 14.6132L14.0767 13.7472C14.6547 13.3022 14.638 12.425 14.0434 12.0023L11.6812 10.323C11.2786 10.0367 10.7335 10.0585 10.3549 10.3758L8.26185 12.1305C7.46603 11.5789 6.66354 10.9437 5.97933 10.2479C5.32711 9.58475 4.7457 8.83116 4.24559 8.08705ZM9.13279 12.7053C9.97097 13.2327 10.7473 13.6522 11.3031 13.9345C11.6307 14.1009 12.0316 14.0597 12.3418 13.8209L13.4666 12.9548C13.5122 12.9198 13.5109 12.8507 13.464 12.8174L11.1018 11.138C11.0701 11.1155 11.0271 11.1172 10.9973 11.1422L9.13279 12.7053Z"
|
||||
d="M14.8532 1.14645C15.0485 1.34171 15.0485 1.65829 14.8532 1.85355L11.4822 5.22461L14.0089 5.22461C14.2851 5.22461 14.5089 5.44847 14.5089 5.72461C14.5089 6.00075 14.2851 6.22461 14.0089 6.22461L10.2749 6.22461C9.99874 6.22461 9.77489 6.00075 9.77489 5.72461L9.77489 1.99055C9.77489 1.71441 9.99874 1.49055 10.2749 1.49055C10.551 1.49055 10.7749 1.71441 10.7749 1.99055V4.51768L14.1461 1.14645C14.3414 0.951184 14.658 0.951184 14.8532 1.14645ZM2.80797 3.6148C2.78573 3.33617 2.8774 3.1345 3.02118 2.99721C3.17153 2.85363 3.41272 2.75 3.72816 2.75H4.64775C4.87718 2.75 5.07717 2.90615 5.13282 3.12873L5.27745 3.70723C5.53943 4.7551 5.0988 5.85273 4.185 6.42862L3.56569 6.81892C3.20623 5.88631 2.95354 4.86847 2.82274 3.80936C2.81796 3.74239 2.81297 3.67751 2.80797 3.6148ZM4.71817 7.27463L3.97179 7.745C4.08077 7.96488 4.19618 8.17818 4.31773 8.38422L4.31816 8.38495C5.13747 9.77916 6.28441 10.9525 7.65958 11.8034L7.66026 11.8038C7.86826 11.933 8.08253 12.0513 8.30302 12.16L8.70837 11.4634C8.71361 11.4544 8.71888 11.4454 8.72419 11.4365C9.53099 10.0777 11.15 9.42701 12.6745 9.85257L13.1543 9.98651C13.802 10.1673 14.2502 10.7571 14.251 11.4295L14.2519 12.1585C14.2525 12.6744 14.0876 13.2069 13.7721 13.6079C13.4485 14.0194 12.9454 14.3097 12.3244 14.2329L12.3239 14.2329L12.2547 14.2244C10.8516 14.0527 9.52086 13.7807 8.31046 13.2634C8.22523 13.2269 8.14057 13.1893 8.0565 13.1503C7.73994 13.0037 7.43191 12.8391 7.13333 12.6537C5.62004 11.7174 4.35788 10.4262 3.45622 8.89196C3.24384 8.53192 3.04973 8.15399 2.87459 7.7614C2.35125 6.58828 1.99551 5.28024 1.82863 3.91848L1.82707 3.90575L1.82617 3.89296C1.82136 3.82509 1.8163 3.75896 1.81114 3.69437C1.76597 3.12847 1.95999 2.62785 2.33056 2.27398C2.69456 1.9264 3.19638 1.75 3.72816 1.75H4.64775C5.33604 1.75 5.93602 2.21844 6.10296 2.88619L6.24759 3.46468C6.61436 4.93171 5.99749 6.46839 4.71817 7.27463ZM9.5209 12.0553L9.23286 12.5503C9.69175 12.7132 10.1733 12.845 10.6771 12.9534C11.2179 13.0698 11.7844 13.1594 12.3761 13.2318L12.3764 13.2318L12.4464 13.2404L12.3853 13.7371L12.4469 13.2405C12.6527 13.266 12.8331 13.1843 12.9862 12.9897C13.1474 12.7847 13.2523 12.477 13.2519 12.1597L13.251 11.4307C13.2508 11.2065 13.1014 11.01 12.8855 10.9497L12.4056 10.8157C11.3095 10.5098 10.1451 10.9827 9.57269 11.9663L9.5209 12.0553Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
16
frontend/src/components/Icons/MissedCallIcon.vue
Normal file
16
frontend/src/components/Icons/MissedCallIcon.vue
Normal 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="M3.02118 2.99721C2.8774 3.1345 2.78573 3.33617 2.80797 3.6148C2.81297 3.67751 2.81796 3.74239 2.82274 3.80936C2.95354 4.86847 3.20623 5.88631 3.56569 6.81892L4.185 6.42862C5.0988 5.85273 5.53943 4.7551 5.27745 3.70723L5.13282 3.12873C5.07717 2.90615 4.87718 2.75 4.64775 2.75H3.72816C3.41272 2.75 3.17153 2.85363 3.02118 2.99721ZM3.97179 7.745L4.71817 7.27463C5.99749 6.46839 6.61436 4.93171 6.24759 3.46468L6.10296 2.88619C5.93602 2.21844 5.33604 1.75 4.64775 1.75H3.72816C3.19638 1.75 2.69456 1.9264 2.33056 2.27398C1.95999 2.62785 1.76597 3.12847 1.81114 3.69437C1.8163 3.75896 1.82136 3.82509 1.82617 3.89296L1.82707 3.90575L1.82863 3.91848C1.99551 5.28024 2.35125 6.58828 2.87459 7.7614C3.04973 8.15399 3.24384 8.53192 3.45622 8.89196C4.35788 10.4262 5.62004 11.7174 7.13333 12.6537C7.43191 12.8391 7.73994 13.0037 8.0565 13.1503C8.14057 13.1893 8.22523 13.2269 8.31046 13.2634C9.52086 13.7807 10.8516 14.0527 12.2547 14.2244L12.3239 14.2329L12.3244 14.2329C12.9454 14.3097 13.4485 14.0194 13.7721 13.6079C14.0876 13.2069 14.2525 12.6744 14.2519 12.1585L14.251 11.4295C14.2502 10.7571 13.802 10.1673 13.1543 9.98651L12.6745 9.85257C11.15 9.42701 9.53099 10.0777 8.72419 11.4365L8.70837 11.4634L8.30302 12.16C8.08253 12.0513 7.86826 11.933 7.66026 11.8038L7.65958 11.8034C6.28441 10.9525 5.13747 9.77916 4.31816 8.38495L4.31773 8.38422C4.19618 8.17818 4.08077 7.96488 3.97179 7.745ZM9.23286 12.5503L9.5209 12.0553L9.57269 11.9663C10.1451 10.9827 11.3095 10.5098 12.4056 10.8157L12.8855 10.9497C13.1014 11.01 13.2508 11.2065 13.251 11.4307L13.2519 12.1597C13.2523 12.477 13.1474 12.7847 12.9862 12.9897C12.8331 13.1843 12.6527 13.266 12.4469 13.2405L12.3853 13.7371L12.4464 13.2404L12.3764 13.2318L12.3761 13.2318C11.7844 13.1594 11.2179 13.0698 10.6771 12.9534C10.1733 12.845 9.69175 12.7132 9.23286 12.5503ZM10.1715 1.93846C10.3668 1.7432 10.6834 1.7432 10.8786 1.93846L12.1161 3.1759L13.3535 1.93846C13.5488 1.7432 13.8654 1.7432 14.0606 1.93846C14.2559 2.13373 14.2559 2.45031 14.0606 2.64557L12.8232 3.88301L14.0606 5.12044C14.2559 5.31571 14.2559 5.63229 14.0606 5.82755C13.8654 6.02281 13.5488 6.02281 13.3535 5.82755L12.1161 4.59011L10.8786 5.82755C10.6834 6.02281 10.3668 6.02281 10.1715 5.82755C9.97627 5.63229 9.97627 5.31571 10.1715 5.12044L11.409 3.88301L10.1715 2.64557C9.97627 2.45031 9.97627 2.13373 10.1715 1.93846Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
38
frontend/src/components/Icons/MuteIcon.vue
Normal file
38
frontend/src/components/Icons/MuteIcon.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.05142 5.27273H2.6926C2.3798 5.27273 2.07981 5.38766 1.85863 5.59225C1.63744 5.79683 1.51318 6.07431 1.51318 6.36364V9.63636C1.51318 9.92569 1.63744 10.2032 1.85863 10.4078C2.07981 10.6123 2.3798 10.7273 2.6926 10.7273H5.05142L9.76908 14V2L5.05142 5.27273Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11.5 5.5C11.8143 5.81281 12.066 6.1975 12.2384 6.62854C12.4108 7.05958 12.5 7.52708 12.5 8C12.5 8.47292 12.4108 8.94042 12.2384 9.37146C12.066 9.8025 11.8143 10.1872 11.5 10.5"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12.5 3C13.1286 3.62561 13.632 4.39501 13.9768 5.25708C14.3217 6.11915 14.5 7.05416 14.5 8C14.5 8.94584 14.3217 9.88085 13.9768 10.7429C13.632 11.605 13.1286 12.3744 12.5 13"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.25 3.07596L13.7286 12.2609"
|
||||
stroke="white"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M2.97913 2.0192L14.0209 13.9808"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@ -9,7 +9,7 @@
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M10.7656 1.5C10.4894 1.5 10.2656 1.72386 10.2656 2C10.2656 2.27614 10.4894 2.5 10.7656 2.5H13.2927L9.92161 5.87106C9.72634 6.06632 9.72634 6.3829 9.92161 6.57816C10.1169 6.77342 10.4334 6.77342 10.6287 6.57816L13.9997 3.20722V5.73406C13.9997 6.0102 14.2235 6.23406 14.4997 6.23406C14.7758 6.23406 14.9997 6.0102 14.9997 5.73406V2.01073C15.0025 1.87922 14.9537 1.74681 14.8533 1.64645C14.7935 1.58659 14.7222 1.54509 14.6466 1.52193C14.6001 1.50767 14.5508 1.5 14.4997 1.5H10.7656ZM3.95262 3.04013C3.92305 2.99299 3.85703 2.98621 3.81849 3.02636L2.78472 4.10321C2.49483 4.40518 2.42118 4.83187 2.58821 5.18366C2.84578 5.72612 3.21973 6.45835 3.69626 7.24891L5.4337 5.52622C5.46212 5.49804 5.4672 5.45393 5.44593 5.42004L3.95262 3.04013ZM4.24559 8.11247L6.13779 6.23633C6.49848 5.87871 6.56295 5.31878 6.29299 4.88854L4.79968 2.50863C4.42423 1.91026 3.58632 1.82423 3.09711 2.33383L2.06333 3.41068C1.50617 3.99105 1.32728 4.85947 1.68487 5.61258C2.28388 6.87416 3.52538 9.2043 5.26635 10.9746C7.06226 12.8007 9.57149 14.2019 10.8502 14.8515C11.5427 15.2033 12.3553 15.0979 12.9518 14.6386L14.0767 13.7726C14.6547 13.3276 14.638 12.4504 14.0434 12.0278L11.6812 10.3484C11.2786 10.0622 10.7335 10.0839 10.3549 10.4013L8.26185 12.1559C7.46603 11.6043 6.66354 10.9691 5.97933 10.2734C5.32711 9.61018 4.7457 8.85659 4.24559 8.11247ZM9.13279 12.7307C9.97097 13.2581 10.7473 13.6776 11.3031 13.9599C11.6307 14.1264 12.0316 14.0851 12.3418 13.8463L13.4666 12.9802C13.5122 12.9452 13.5109 12.8761 13.464 12.8428L11.1018 11.1634C11.0701 11.1409 11.0271 11.1426 10.9973 11.1676L9.13279 12.7307Z"
|
||||
d="M10.765 1C10.4889 1 10.265 1.22386 10.265 1.5C10.265 1.77614 10.4889 2 10.765 2H13.2921L9.92106 5.37106C9.72579 5.56632 9.72579 5.8829 9.92106 6.07816C10.1163 6.27342 10.4329 6.27342 10.6282 6.07816L13.9992 2.70714L13.9991 5.23405C13.9991 5.51019 14.2229 5.73405 14.4991 5.73406C14.7752 5.73407 14.9991 5.51022 14.9991 5.23408L14.9992 1.50002C14.9992 1.3674 14.9465 1.24022 14.8528 1.14645C14.759 1.05268 14.6318 1 14.4992 1H10.765ZM2.80797 3.61483C2.78573 3.3362 2.8774 3.13453 3.02118 2.99724C3.17153 2.85366 3.41272 2.75003 3.72816 2.75003H4.64775C4.87718 2.75003 5.07717 2.90618 5.13282 3.12876L5.27745 3.70726C5.53943 4.75513 5.0988 5.85276 4.185 6.42865L3.56569 6.81894C3.20623 5.88634 2.95354 4.8685 2.82274 3.80939C2.81796 3.74242 2.81297 3.67754 2.80797 3.61483ZM4.71817 7.27466L3.97179 7.74503C4.08077 7.96491 4.19618 8.17821 4.31773 8.38425L4.31816 8.38498C5.13747 9.77919 6.28441 10.9526 7.65958 11.8034L7.66026 11.8039C7.86826 11.933 8.08253 12.0514 8.30302 12.16L8.70837 11.4634C8.71361 11.4544 8.71888 11.4454 8.72419 11.4365C9.53099 10.0777 11.15 9.42704 12.6745 9.8526L13.1543 9.98654C13.802 10.1673 14.2502 10.7571 14.251 11.4295L14.2519 12.1585C14.2525 12.6744 14.0876 13.2069 13.7721 13.608C13.4485 14.0194 12.9454 14.3098 12.3244 14.233L12.3239 14.2329L12.2547 14.2244C10.8516 14.0528 9.52086 13.7807 8.31046 13.2634C8.22523 13.227 8.14057 13.1893 8.0565 13.1504C7.73994 13.0038 7.43191 12.8391 7.13333 12.6538C5.62004 11.7174 4.35788 10.4262 3.45622 8.89199C3.24384 8.53194 3.04973 8.15402 2.87459 7.76142C2.35125 6.58831 1.99551 5.28027 1.82863 3.91851L1.82707 3.90578L1.82617 3.89299C1.82136 3.82512 1.8163 3.75899 1.81114 3.6944C1.76597 3.1285 1.95999 2.62788 2.33056 2.27401C2.69456 1.92643 3.19638 1.75003 3.72816 1.75003H4.64775C5.33604 1.75003 5.93602 2.21847 6.10296 2.88621L6.24759 3.46471C6.61436 4.93174 5.99749 6.46842 4.71817 7.27466ZM9.5209 12.0553L9.23286 12.5503C9.69175 12.7132 10.1733 12.845 10.6771 12.9535C11.2179 13.0699 11.7844 13.1594 12.3761 13.2318L12.3764 13.2318L12.4464 13.2404L12.3853 13.7372L12.4469 13.2405C12.6527 13.266 12.8331 13.1843 12.9862 12.9897C13.1474 12.7847 13.2523 12.4771 13.2519 12.1597L13.251 11.4307C13.2508 11.2066 13.1014 11.01 12.8855 10.9497L12.4056 10.8158C11.3095 10.5098 10.1451 10.9827 9.57269 11.9663L9.5209 12.0553Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
16
frontend/src/components/Icons/PauseIcon.vue
Normal file
16
frontend/src/components/Icons/PauseIcon.vue
Normal 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="M6 4.5V11.5C6 12.3284 5.32843 13 4.5 13C3.67157 13 3 12.3284 3 11.5V4.5C3 3.67157 3.67157 3 4.5 3C5.32843 3 6 3.67157 6 4.5ZM2 4.5C2 3.11929 3.11929 2 4.5 2C5.88071 2 7 3.11929 7 4.5V11.5C7 12.8807 5.88071 14 4.5 14C3.11929 14 2 12.8807 2 11.5V4.5ZM13 4.5V11.5C13 12.3284 12.3284 13 11.5 13C10.6716 13 10 12.3284 10 11.5V4.5C10 3.67157 10.6716 3 11.5 3C12.3284 3 13 3.67157 13 4.5ZM9 4.5C9 3.11929 10.1193 2 11.5 2C12.8807 2 14 3.11929 14 4.5V11.5C14 12.8807 12.8807 14 11.5 14C10.1193 14 9 12.8807 9 11.5V4.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@ -9,7 +9,7 @@
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3.95261 2.54013C3.92303 2.49299 3.85702 2.48621 3.81848 2.52636L2.78471 3.60321C2.49482 3.90517 2.42116 4.33187 2.58819 4.68366C2.84576 5.22612 3.21972 5.95835 3.69625 6.74891L5.43369 5.02622C5.4621 4.99804 5.46718 4.95393 5.44592 4.92004L3.95261 2.54013ZM4.24557 7.61247L6.13777 5.73633C6.49846 5.37871 6.56294 4.81878 6.29297 4.38854L4.79967 2.00863C4.42421 1.41026 3.5863 1.32423 3.09709 1.83383L2.06332 2.91068C1.50616 3.49105 1.32727 4.35947 1.68485 5.11258C2.28387 6.37416 3.52537 8.7043 5.26633 10.4746C7.06225 12.3007 9.57147 13.7019 10.8502 14.3515C11.5427 14.7033 12.3553 14.5979 12.9518 14.1386L14.0767 13.2726C14.6547 12.8276 14.638 11.9504 14.0434 11.5278L11.6812 9.8484C11.2786 9.56215 10.7334 9.58388 10.3549 9.90126L8.26183 11.6559C7.46601 11.1043 6.66352 10.4691 5.97931 9.77337C5.32709 9.11017 4.74568 8.35658 4.24557 7.61247ZM9.13278 12.2307C9.97096 12.7581 10.7473 13.1776 11.3031 13.4599C11.6307 13.6264 12.0315 13.5851 12.3417 13.3463L13.4666 12.4802C13.5122 12.4452 13.5109 12.3761 13.464 12.3428L11.1018 10.6634C11.0701 10.6409 11.0271 10.6426 10.9973 10.6676L9.13278 12.2307Z"
|
||||
d="M2.67754 2.70675C2.53628 2.84008 2.44787 3.03363 2.46988 3.29885C2.47828 3.40004 2.4869 3.50717 2.49497 3.61981C2.63494 4.75003 2.9076 5.83579 3.29638 6.82863L4.1395 6.29907C5.02533 5.74269 5.45257 4.67951 5.19805 3.66489L4.98918 2.83225C4.93526 2.61729 4.74204 2.46656 4.52043 2.46656H3.36772C3.06265 2.46656 2.82575 2.56686 2.67754 2.70675ZM3.69508 7.71962L4.6536 7.11757C5.89375 6.33864 6.49189 4.85019 6.13556 3.42971L5.9267 2.59708C5.76493 1.9522 5.18528 1.5 4.52043 1.5H3.36772C2.85467 1.5 2.368 1.66981 2.01409 2.00386C1.65323 2.34447 1.46097 2.82869 1.50663 3.3788C1.51521 3.48212 1.52381 3.58915 1.53174 3.70087L1.53262 3.71327L1.53414 3.72561C1.70903 5.14795 2.08181 6.51379 2.62992 7.73832C2.8107 8.1422 3.76654 10.0804 5.03876 11.2498C6.27205 12.3833 6.85696 12.7556 8.29758 13.4488L8.31072 13.4551L8.32421 13.4606C9.77742 14.0548 11.0901 14.3742 12.5272 14.4965C13.6361 14.5909 14.4998 13.677 14.4998 12.6231V11.5096C14.4998 10.8539 14.0598 10.2799 13.4267 10.1095L12.6539 9.90153C11.1864 9.50659 9.63455 10.1384 8.86371 11.4495C8.67966 11.7625 8.49178 12.0826 8.3163 12.3822C7.24188 11.8473 6.72935 11.4908 5.69284 10.5382C4.79599 9.71382 4.04577 8.4042 3.69508 7.71962ZM9.21019 12.7685C10.3726 13.198 11.4466 13.4344 12.6092 13.5334C13.096 13.5749 13.5332 13.1706 13.5332 12.6231V11.5096C13.5332 11.291 13.3866 11.0997 13.1755 11.0429L12.4027 10.8349C11.3541 10.5527 10.2467 11.0044 9.69692 11.9394C9.53381 12.2168 9.3678 12.4995 9.21019 12.7685Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
<template>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19C14.9706 19 19 14.9706 19 10C19 7.61305 18.0518 5.32387 16.364 3.63604C14.6761 1.94821 12.3869 1 10 1Z"
|
||||
fill="#DEEEFC"
|
||||
/>
|
||||
<path
|
||||
d="M13.2581 10.5593L9.02502 13.3556C8.7895 13.5169 8.48212 13.5452 8.21897 13.4297C7.95581 13.3143 7.77698 13.0728 7.75 12.7964V7.20365C7.77698 6.92722 7.95581 6.68568 8.21897 6.57025C8.48212 6.45482 8.7895 6.48309 9.02502 6.64438L13.2581 9.44073C13.4602 9.56041 13.5833 9.77207 13.5833 10C13.5833 10.2279 13.4602 10.4396 13.2581 10.5593Z"
|
||||
fill="#2D95F0"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3.76527 2.76476L3.76515 2.76469C3.76518 2.76471 3.76516 2.7647 3.76508 2.76466C3.76417 2.76417 3.75528 2.75942 3.73828 2.7571V9.24215C3.75528 9.23982 3.76418 9.23508 3.76508 9.23459C3.76516 9.23455 3.76518 9.23454 3.76515 9.23456L3.76527 9.23448L8.75611 5.99962L3.76527 2.76476ZM9.5471 6.67863L4.30917 10.0736C4.01166 10.2665 3.61363 10.2975 3.28101 10.1538C2.94839 10.01 2.73828 9.71611 2.73828 9.39463V2.60462C2.73828 2.28313 2.94839 1.98924 3.28101 1.84547C3.61363 1.7017 4.01166 1.73272 4.30917 1.92561L9.5471 5.32062C9.79432 5.48091 9.93982 5.73247 9.93982 5.99962C9.93982 6.26677 9.79432 6.51833 9.5471 6.67863Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
28
frontend/src/components/Icons/PlaybackSpeedIcon.vue
Normal file
28
frontend/src/components/Icons/PlaybackSpeedIcon.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
cx="8"
|
||||
cy="8"
|
||||
r="6.5"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-dasharray="1.9 1.9"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M7.5 14.9824C7.66515 14.9941 7.83188 15 8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C7.83188 1 7.66515 1.00593 7.5 1.01758V2.02054C7.66487 2.00694 7.83162 2 8 2C11.3137 2 14 4.68629 14 8C14 11.3137 11.3137 14 8 14C7.83162 14 7.66487 13.9931 7.5 13.9795V14.9824Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M6.55133 5.06237L10.9174 8.09973L6.54669 10.7899L6.55133 5.06237Z"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
16
frontend/src/components/Icons/SelectIcon.vue
Normal file
16
frontend/src/components/Icons/SelectIcon.vue
Normal 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="M5.85355 9.64645C5.65829 9.45118 5.34171 9.45118 5.14645 9.64645C4.95118 9.84171 4.95118 10.1583 5.14645 10.3536L7.64645 12.8536C7.84171 13.0488 8.15829 13.0488 8.35355 12.8536L10.8536 10.3536C11.0488 10.1583 11.0488 9.84171 10.8536 9.64645C10.6583 9.45118 10.3417 9.45118 10.1464 9.64645L8 11.7929L5.85355 9.64645ZM5.85355 6.35355C5.65829 6.54882 5.34171 6.54882 5.14645 6.35355C4.95118 6.15829 4.95118 5.84171 5.14645 5.64645L7.64645 3.14645C7.84171 2.95118 8.15829 2.95118 8.35355 3.14645L10.8536 5.64645C11.0488 5.84171 11.0488 6.15829 10.8536 6.35355C10.6583 6.54882 10.3417 6.54882 10.1464 6.35355L8 4.20711L5.85355 6.35355Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@ -6,18 +6,11 @@
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_3692_15849)">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14.25 8C14.25 11.4518 11.4518 14.25 8 14.25C4.54822 14.25 1.75 11.4518 1.75 8C1.75 4.54822 4.54822 1.75 8 1.75C11.4518 1.75 14.25 4.54822 14.25 8ZM15.25 8C15.25 12.0041 12.0041 15.25 8 15.25C3.99594 15.25 0.75 12.0041 0.75 8C0.75 3.99594 3.99594 0.75 8 0.75C12.0041 0.75 15.25 3.99594 15.25 8ZM11.2909 5.98482C11.4666 5.77175 11.4363 5.45663 11.2232 5.28096C11.0101 5.1053 10.695 5.13561 10.5193 5.34868L7.07001 9.53239L5.72845 7.79857C5.55946 7.58018 5.24543 7.54012 5.02703 7.70911C4.80863 7.8781 4.76858 8.19214 4.93756 8.41053L6.66217 10.6394C6.7552 10.7596 6.89788 10.831 7.04988 10.8334C7.20188 10.8357 7.3467 10.7688 7.4434 10.6515L11.2909 5.98482Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3692_15849">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M10.3396 2.75011C9.20746 2.24565 7.94257 2.12068 6.73358 2.39383C5.52459 2.66699 4.43629 3.32363 3.63099 4.26584C2.82568 5.20804 2.34652 6.38532 2.26497 7.6221C2.18342 8.85887 2.50384 10.0889 3.17845 11.1287C3.85307 12.1685 4.84572 12.9623 6.00837 13.3919C7.17102 13.8214 8.44138 13.8636 9.62998 13.5122C10.8186 13.1607 11.8617 12.4345 12.6039 11.4418C13.3459 10.4491 13.7473 9.24321 13.748 8.00385C13.748 8.00376 13.748 8.00366 13.748 8.00357L13.748 8.00385M13.748 8.00357L13.748 7.42885C13.748 7.15148 13.9729 6.92662 14.2503 6.92662C14.5277 6.92662 14.7525 7.15148 14.7525 7.42885V8.00385V8.00414C14.7517 9.46021 14.2802 10.877 13.4084 12.0432C12.5365 13.2094 11.3111 14.0626 9.91478 14.4754C8.51846 14.8883 7.02611 14.8387 5.66027 14.3341C4.29444 13.8295 3.12831 12.8969 2.3358 11.6754C1.5433 10.4539 1.16688 9.00892 1.26268 7.55601C1.35849 6.1031 1.92138 4.72008 2.86742 3.61322C3.81346 2.50635 5.09195 1.73495 6.51221 1.41406C7.93248 1.09317 9.41843 1.23999 10.7484 1.8326C11.0018 1.9455 11.1157 2.2424 11.0028 2.49577C10.8899 2.74913 10.593 2.863 10.3396 2.75011M14.6052 2.64497C14.8015 2.84101 14.8016 3.159 14.6056 3.35523L8.35559 9.61148C8.26142 9.70575 8.13365 9.75873 8.0004 9.75876C7.86716 9.75879 7.73936 9.70588 7.64515 9.61166L5.77015 7.73666C5.57401 7.54053 5.57401 7.22253 5.77015 7.0264C5.96628 6.83026 6.28428 6.83026 6.48041 7.0264L8.0001 8.54609L13.895 2.64532C14.091 2.44909 14.409 2.44893 14.6052 2.64497Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
28
frontend/src/components/Icons/VolumnHighIcon.vue
Normal file
28
frontend/src/components/Icons/VolumnHighIcon.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.05142 5.27273H2.6926C2.3798 5.27273 2.07981 5.38766 1.85863 5.59225C1.63744 5.79683 1.51318 6.07431 1.51318 6.36364V9.63636C1.51318 9.92569 1.63744 10.2032 1.85863 10.4078C2.07981 10.6123 2.3798 10.7273 2.6926 10.7273H5.05142L9.76908 14V2L5.05142 5.27273Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11.5 5.5C11.8143 5.81281 12.066 6.1975 12.2384 6.62854C12.4108 7.05958 12.5 7.52708 12.5 8C12.5 8.47292 12.4108 8.94042 12.2384 9.37146C12.066 9.8025 11.8143 10.1872 11.5 10.5"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12.5 3C13.1286 3.62561 13.632 4.39501 13.9768 5.25708C14.3217 6.11915 14.5 7.05416 14.5 8C14.5 8.94584 14.3217 9.88085 13.9768 10.7429C13.632 11.605 13.1286 12.3744 12.5 13"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
22
frontend/src/components/Icons/VolumnLowIcon.vue
Normal file
22
frontend/src/components/Icons/VolumnLowIcon.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.05142 5.27273H2.6926C2.3798 5.27273 2.07981 5.38766 1.85863 5.59225C1.63744 5.79683 1.51318 6.07431 1.51318 6.36364V9.63636C1.51318 9.92569 1.63744 10.2032 1.85863 10.4078C2.07981 10.6123 2.3798 10.7273 2.6926 10.7273H5.05142L9.76908 14V2L5.05142 5.27273Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11.5 5.5C11.8143 5.81281 12.066 6.1975 12.2384 6.62854C12.4108 7.05958 12.5 7.52708 12.5 8C12.5 8.47292 12.4108 8.94042 12.2384 9.37146C12.066 9.8025 11.8143 10.1872 11.5 10.5"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@ -97,7 +97,7 @@
|
||||
|
||||
<script setup>
|
||||
import Section from '@/components/Section.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import PinIcon from '@/components/Icons/PinIcon.vue'
|
||||
import UserDropdown from '@/components/UserDropdown.vue'
|
||||
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
||||
@ -160,7 +160,7 @@ const links = [
|
||||
},
|
||||
{
|
||||
label: 'Email Templates',
|
||||
icon: EmailIcon,
|
||||
icon: Email2Icon,
|
||||
to: 'Email Templates',
|
||||
},
|
||||
]
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
</Button>
|
||||
</ListHeaderItem>
|
||||
</ListHeader>
|
||||
<ListRows :rows="rows" v-slot="{ idx, column, item }">
|
||||
<ListRows :rows="rows" v-slot="{ idx, column, item, row }">
|
||||
<div v-if="column.key === '_assign'" class="flex items-center">
|
||||
<MultipleAvatar
|
||||
:avatars="item"
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
</Button>
|
||||
</ListHeaderItem>
|
||||
</ListHeader>
|
||||
<ListRows :rows="rows" v-slot="{ idx, column, item }">
|
||||
<ListRows :rows="rows" v-slot="{ idx, column, item, row }">
|
||||
<div v-if="column.key === '_assign'" class="flex items-center">
|
||||
<MultipleAvatar
|
||||
:avatars="item"
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
v-slot="{ idx, column, item }"
|
||||
:row="row"
|
||||
>
|
||||
<slot v-bind="{ idx, column, item }" />
|
||||
<slot v-bind="{ idx, column, item, row }" />
|
||||
</ListRow>
|
||||
</ListGroupRows>
|
||||
</div>
|
||||
@ -34,7 +34,7 @@
|
||||
v-slot="{ idx, column, item }"
|
||||
:row="row"
|
||||
>
|
||||
<slot v-bind="{ idx, column, item }" />
|
||||
<slot v-bind="{ idx, column, item, row }" />
|
||||
</ListRow>
|
||||
</ListRows>
|
||||
</template>
|
||||
|
||||
@ -88,7 +88,7 @@ import {
|
||||
DialogOverlay,
|
||||
} from '@headlessui/vue'
|
||||
import Section from '@/components/Section.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import PinIcon from '@/components/Icons/PinIcon.vue'
|
||||
import UserDropdown from '@/components/UserDropdown.vue'
|
||||
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
||||
@ -145,7 +145,7 @@ const links = [
|
||||
},
|
||||
{
|
||||
label: 'Email Templates',
|
||||
icon: EmailIcon,
|
||||
icon: Email2Icon,
|
||||
to: 'Email Templates',
|
||||
},
|
||||
]
|
||||
|
||||
@ -84,7 +84,7 @@
|
||||
import Fields from '@/components/Fields.vue'
|
||||
import ContactIcon from '@/components/Icons/ContactIcon.vue'
|
||||
import GenderIcon from '@/components/Icons/GenderIcon.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue'
|
||||
import AddressIcon from '@/components/Icons/AddressIcon.vue'
|
||||
@ -208,7 +208,7 @@ const detailFields = computed(() => {
|
||||
value: _contact.value.gender,
|
||||
},
|
||||
{
|
||||
icon: EmailIcon,
|
||||
icon: Email2Icon,
|
||||
name: 'email_id',
|
||||
value: _contact.value.email_id,
|
||||
},
|
||||
|
||||
@ -216,7 +216,7 @@ function render() {
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => render())
|
||||
onMounted(() => show.value && render())
|
||||
|
||||
watch(show, (value) => {
|
||||
if (!value) return
|
||||
|
||||
@ -64,7 +64,7 @@
|
||||
v-if="contact.data.email_id"
|
||||
class="flex items-center gap-1.5"
|
||||
>
|
||||
<EmailIcon class="h-4 w-4" />
|
||||
<Email2Icon class="h-4 w-4" />
|
||||
<span class="">{{ contact.data.email_id }}</span>
|
||||
</div>
|
||||
<span
|
||||
@ -227,7 +227,7 @@ import {
|
||||
} from 'frappe-ui'
|
||||
import Dropdown from '@/components/frappe-ui/Dropdown.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
import CameraIcon from '@/components/Icons/CameraIcon.vue'
|
||||
|
||||
@ -77,7 +77,7 @@
|
||||
</Tooltip>
|
||||
<Tooltip :text="__('Send an email')">
|
||||
<Button class="h-7 w-7">
|
||||
<EmailIcon
|
||||
<Email2Icon
|
||||
class="h-4 w-4"
|
||||
@click="
|
||||
deal.data.email
|
||||
@ -240,7 +240,7 @@
|
||||
class="flex flex-col gap-1.5 text-base text-gray-800"
|
||||
>
|
||||
<div class="flex items-center gap-3 pb-1.5 pl-1 pt-4">
|
||||
<EmailIcon class="h-4 w-4" />
|
||||
<Email2Icon class="h-4 w-4" />
|
||||
{{ contact.email }}
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-1 py-1.5">
|
||||
@ -306,6 +306,7 @@ import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
|
||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import CommentIcon from '@/components/Icons/CommentIcon.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import TaskIcon from '@/components/Icons/TaskIcon.vue'
|
||||
@ -316,7 +317,7 @@ import LinkIcon from '@/components/Icons/LinkIcon.vue'
|
||||
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
|
||||
import SuccessIcon from '@/components/Icons/SuccessIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import Activities from '@/components/Activities.vue'
|
||||
import Activities from '@/components/Activities/Activities.vue'
|
||||
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
|
||||
import AssignmentModal from '@/components/Modals/AssignmentModal.vue'
|
||||
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
<div
|
||||
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
|
||||
>
|
||||
<EmailIcon class="h-10 w-10" />
|
||||
<Email2Icon class="h-10 w-10" />
|
||||
<span>{{ __('No {0} Found', [__('Email Templates')]) }}</span>
|
||||
<Button :label="__('Create')" @click="showEmailTemplateModal = true">
|
||||
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
|
||||
@ -69,7 +69,7 @@
|
||||
|
||||
<script setup>
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import ViewControls from '@/components/ViewControls.vue'
|
||||
import EmailTemplatesListView from '@/components/ListViews/EmailTemplatesListView.vue'
|
||||
|
||||
@ -128,7 +128,7 @@
|
||||
</Tooltip>
|
||||
<Tooltip :text="__('Send an email')">
|
||||
<Button class="h-7 w-7">
|
||||
<EmailIcon
|
||||
<Email2Icon
|
||||
class="h-4 w-4"
|
||||
@click="
|
||||
lead.data.email
|
||||
@ -273,6 +273,7 @@ import Resizer from '@/components/Resizer.vue'
|
||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import CommentIcon from '@/components/Icons/CommentIcon.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import TaskIcon from '@/components/Icons/TaskIcon.vue'
|
||||
@ -284,7 +285,7 @@ import LinkIcon from '@/components/Icons/LinkIcon.vue'
|
||||
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue'
|
||||
import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import Activities from '@/components/Activities.vue'
|
||||
import Activities from '@/components/Activities/Activities.vue'
|
||||
import AssignmentModal from '@/components/Modals/AssignmentModal.vue'
|
||||
import SidePanelModal from '@/components/Settings/SidePanelModal.vue'
|
||||
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
||||
|
||||
@ -180,7 +180,7 @@
|
||||
class="flex flex-col gap-1.5 text-base text-gray-800"
|
||||
>
|
||||
<div class="flex items-center gap-3 pb-1.5 pl-1 pt-4">
|
||||
<EmailIcon class="h-4 w-4" />
|
||||
<Email2Icon class="h-4 w-4" />
|
||||
{{ contact.email }}
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-1 py-1.5">
|
||||
@ -249,6 +249,7 @@ import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
|
||||
import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
|
||||
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import CommentIcon from '@/components/Icons/CommentIcon.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import TaskIcon from '@/components/Icons/TaskIcon.vue'
|
||||
@ -258,7 +259,7 @@ import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
|
||||
import SuccessIcon from '@/components/Icons/SuccessIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import Activities from '@/components/Activities.vue'
|
||||
import Activities from '@/components/Activities/Activities.vue'
|
||||
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
|
||||
import AssignmentModal from '@/components/Modals/AssignmentModal.vue'
|
||||
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
||||
|
||||
@ -182,7 +182,7 @@ import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue'
|
||||
import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import Activities from '@/components/Activities.vue'
|
||||
import Activities from '@/components/Activities/Activities.vue'
|
||||
import AssignmentModal from '@/components/Modals/AssignmentModal.vue'
|
||||
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
|
||||
@ -177,7 +177,7 @@
|
||||
<div
|
||||
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
|
||||
>
|
||||
<EmailIcon class="h-10 w-10" />
|
||||
<Email2Icon class="h-10 w-10" />
|
||||
<span>{{ __('No {0} Found', [__('Tasks')]) }}</span>
|
||||
<Button :label="__('Create')" @click="showTaskModal = true">
|
||||
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
|
||||
@ -197,7 +197,7 @@ import CustomActions from '@/components/CustomActions.vue'
|
||||
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
|
||||
import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue'
|
||||
import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import ViewControls from '@/components/ViewControls.vue'
|
||||
import TasksListView from '@/components/ListViews/TasksListView.vue'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user