Merge branch 'develop' into groupby-view
This commit is contained in:
commit
853c4b5add
@ -89,7 +89,7 @@ def get_filterable_fields(doctype: str):
|
|||||||
"options": "User",
|
"options": "User",
|
||||||
},
|
},
|
||||||
{"fieldname": "_user_tags", "fieldtype": "Data", "label": "Tags"},
|
{"fieldname": "_user_tags", "fieldtype": "Data", "label": "Tags"},
|
||||||
{"fieldname": "_liked_by", "fieldtype": "Data", "label": "Liked By"},
|
{"fieldname": "_liked_by", "fieldtype": "Data", "label": "Like"},
|
||||||
{"fieldname": "_comments", "fieldtype": "Text", "label": "Comments"},
|
{"fieldname": "_comments", "fieldtype": "Text", "label": "Comments"},
|
||||||
{"fieldname": "_assign", "fieldtype": "Text", "label": "Assigned To"},
|
{"fieldname": "_assign", "fieldtype": "Text", "label": "Assigned To"},
|
||||||
{"fieldname": "creation", "fieldtype": "Datetime", "label": "Created On"},
|
{"fieldname": "creation", "fieldtype": "Datetime", "label": "Created On"},
|
||||||
@ -314,7 +314,7 @@ def get_list_data(
|
|||||||
},
|
},
|
||||||
{"label": "Assigned To", "type": "Text", "value": "_assign"},
|
{"label": "Assigned To", "type": "Text", "value": "_assign"},
|
||||||
{"label": "Owner", "type": "Link", "value": "owner", "options": "User"},
|
{"label": "Owner", "type": "Link", "value": "owner", "options": "User"},
|
||||||
{"label": "Liked By", "type": "Data", "value": "_liked_by"},
|
{"label": "Like", "type": "Data", "value": "_liked_by"},
|
||||||
]
|
]
|
||||||
|
|
||||||
for field in std_fields:
|
for field in std_fields:
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^4.2.3",
|
"@vitejs/plugin-vue": "^4.2.3",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"postcss": "^8.4.5",
|
"postcss": "^8.4.5",
|
||||||
"vite": "^4.4.9",
|
"vite": "^4.4.9",
|
||||||
|
|||||||
@ -102,11 +102,11 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="title == 'Notes'"
|
v-if="title == 'Notes'"
|
||||||
class="activity grid grid-cols-1 gap-4 px-4 pb-3 sm:px-10 sm:pb-5 lg:grid-cols-2 xl:grid-cols-3"
|
class="grid grid-cols-1 gap-4 px-4 pb-3 sm:px-10 sm:pb-5 lg:grid-cols-2 xl:grid-cols-3"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="note in activities"
|
v-for="note in activities"
|
||||||
class="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"
|
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"
|
||||||
@click="showNote(note)"
|
@click="showNote(note)"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
@ -156,10 +156,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="title == 'Comments'" class="activity pb-5">
|
<div v-else-if="title == 'Comments'" class="pb-5">
|
||||||
<div v-for="(comment, i) in activities">
|
<div v-for="(comment, i) in activities">
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-[30px_minmax(auto,_1fr)] sm:gap-4 gap-2 px-4 sm:px-10"
|
class="activity grid grid-cols-[30px_minmax(auto,_1fr)] gap-2 px-4 sm:gap-4 sm:px-10"
|
||||||
>
|
>
|
||||||
<div
|
<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="relative flex justify-center after:absolute after:left-[50%] after:top-0 after:-z-10 after:border-l after:border-gray-200"
|
||||||
@ -214,13 +214,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-else-if="title == 'Tasks'" class="px-4 pb-3 sm:px-10 sm:pb-5">
|
||||||
v-else-if="title == 'Tasks'"
|
|
||||||
class="activity px-4 pb-3 sm:px-10 sm:pb-5"
|
|
||||||
>
|
|
||||||
<div v-for="(task, i) in activities">
|
<div v-for="(task, i) in activities">
|
||||||
<div
|
<div
|
||||||
class="flex cursor-pointer gap-6 rounded p-2.5 duration-300 ease-in-out hover:bg-gray-50"
|
class="activity flex cursor-pointer gap-6 rounded p-2.5 duration-300 ease-in-out hover:bg-gray-50"
|
||||||
@click="showTask(task)"
|
@click="showTask(task)"
|
||||||
>
|
>
|
||||||
<div class="flex flex-1 flex-col gap-1.5 text-base">
|
<div class="flex flex-1 flex-col gap-1.5 text-base">
|
||||||
@ -313,7 +310,7 @@
|
|||||||
<div v-else-if="title == 'Calls'" class="activity">
|
<div v-else-if="title == 'Calls'" class="activity">
|
||||||
<div v-for="(call, i) in activities">
|
<div v-for="(call, i) in activities">
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-4 sm:px-10"
|
class="activity grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-4 sm:px-10"
|
||||||
>
|
>
|
||||||
<div
|
<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="relative flex justify-center after:absolute after:left-[50%] after:top-0 after:-z-10 after:border-l after:border-gray-200"
|
||||||
@ -416,283 +413,341 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else v-for="(activity, i) in activities" class="activity">
|
<div
|
||||||
|
v-else
|
||||||
|
v-for="(activity, i) in activities"
|
||||||
|
class="activity px-4 sm:px-10"
|
||||||
|
:class="
|
||||||
|
title == 'Activity'
|
||||||
|
? 'grid grid-cols-[30px_minmax(auto,_1fr)] gap-2 sm:gap-4'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="px-4 sm:px-10"
|
v-if="title == 'Activity'"
|
||||||
:class="
|
class="relative flex justify-center before:absolute before:left-[50%] before:top-0 before:-z-10 before:border-l before:border-gray-200"
|
||||||
title == 'Activity'
|
:class="[
|
||||||
? 'grid grid-cols-[30px_minmax(auto,_1fr)] sm:gap-4 gap-2'
|
i != activities.length - 1 ? 'before:h-full' : 'before:h-4',
|
||||||
: ''
|
activity.other_versions
|
||||||
"
|
? 'after:translate-y-[calc(-50% - 4px)] after:absolute after:bottom-9 after:left-[50%] after:top-0 after:-z-10 after:w-8 after:rounded-bl-xl after:border-b after:border-l after:border-gray-200'
|
||||||
|
: '',
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="title == 'Activity'"
|
class="z-10 flex h-7 w-7 items-center justify-center rounded bg-gray-100"
|
||||||
class="relative flex justify-center before:absolute before:left-[50%] before:top-0 before:-z-10 before:border-l before:border-gray-200"
|
:class="{
|
||||||
:class="[
|
'mt-3': [
|
||||||
i != activities.length - 1 ? 'before:h-full' : 'before:h-4',
|
'communication',
|
||||||
activity.other_versions
|
'incoming_call',
|
||||||
? 'after:translate-y-[calc(-50% - 4px)] after:absolute after:bottom-9 after:left-[50%] after:top-0 after:-z-10 after:w-8 after:rounded-bl-xl after:border-b after:border-l after:border-gray-200'
|
'outgoing_call',
|
||||||
: '',
|
].includes(activity.activity_type),
|
||||||
]"
|
'bg-white': ['added', 'removed', 'changed'].includes(
|
||||||
|
activity.activity_type
|
||||||
|
),
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<component
|
||||||
class="z-10 flex h-7 w-7 items-center justify-center rounded bg-gray-100"
|
:is="activity.icon"
|
||||||
:class="{
|
:class="
|
||||||
'mt-3': [
|
['added', 'removed', 'changed'].includes(activity.activity_type)
|
||||||
'communication',
|
? 'text-gray-500'
|
||||||
'incoming_call',
|
: 'text-gray-800'
|
||||||
'outgoing_call',
|
"
|
||||||
].includes(activity.activity_type),
|
/>
|
||||||
'bg-white': ['added', 'removed', 'changed'].includes(
|
|
||||||
activity.activity_type
|
|
||||||
),
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<component
|
|
||||||
: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-6">
|
|
||||||
<div
|
|
||||||
class="cursor-pointer rounded-md bg-gray-50 p-3 text-base leading-6 transition-all duration-300 ease-in-out"
|
|
||||||
>
|
|
||||||
<div class="mb-1 flex items-center justify-between gap-2">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<UserAvatar :user="activity.data.sender" size="md" />
|
|
||||||
<span>{{ activity.data.sender_full_name }}</span>
|
|
||||||
<span>·</span>
|
|
||||||
<Tooltip
|
|
||||||
:text="dateFormat(activity.creation, dateTooltipFormat)"
|
|
||||||
>
|
|
||||||
<div class="text-sm text-gray-600">
|
|
||||||
{{ __(timeAgo(activity.creation)) }}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
<Badge
|
|
||||||
v-if="activity.communication_type == 'Automated Message'"
|
|
||||||
:label="__('Notification')"
|
|
||||||
variant="subtle"
|
|
||||||
theme="green"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-0.5">
|
|
||||||
<Tooltip :text="__('Reply')">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
class="text-gray-700"
|
|
||||||
@click="reply(activity.data)"
|
|
||||||
>
|
|
||||||
<ReplyIcon class="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip :text="__('Reply All')">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
class="text-gray-700"
|
|
||||||
@click="reply(activity.data, true)"
|
|
||||||
>
|
|
||||||
<ReplyAllIcon class="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-sm leading-5 text-gray-600">
|
|
||||||
{{ activity.data.subject }}
|
|
||||||
</div>
|
|
||||||
<div class="mb-3 text-sm leading-5 text-gray-600">
|
|
||||||
<span class="mr-1 text-2xs font-bold text-gray-500">
|
|
||||||
{{ __('TO') }}:
|
|
||||||
</span>
|
|
||||||
<span>{{ activity.data.recipients }}</span>
|
|
||||||
<span v-if="activity.data.cc">, </span>
|
|
||||||
<span
|
|
||||||
v-if="activity.data.cc"
|
|
||||||
class="mr-1 text-2xs font-bold text-gray-500"
|
|
||||||
>
|
|
||||||
{{ __('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-2xs font-bold text-gray-500"
|
|
||||||
>
|
|
||||||
{{ __('BCC') }}:
|
|
||||||
</span>
|
|
||||||
<span v-if="activity.data.bcc">{{ activity.data.bcc }}</span>
|
|
||||||
</div>
|
|
||||||
<FadedScrollableDiv
|
|
||||||
:maskHeight="30"
|
|
||||||
class="email-content prose-f max-h-[500px] overflow-y-auto"
|
|
||||||
v-html="activity.data.content"
|
|
||||||
/>
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
<AttachmentItem
|
|
||||||
v-for="a in activity.data.attachments"
|
|
||||||
:key="a.file_url"
|
|
||||||
:label="a.file_name"
|
|
||||||
:url="a.file_url"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="activity.activity_type == 'communication'" class="pb-6">
|
||||||
<div
|
<div
|
||||||
class="mb-4"
|
class="cursor-pointer rounded-md bg-gray-50 p-3 text-base leading-6 transition-all duration-300 ease-in-out"
|
||||||
:id="activity.name"
|
|
||||||
v-else-if="activity.activity_type == 'comment'"
|
|
||||||
>
|
>
|
||||||
<div
|
<div class="mb-1 flex items-center justify-between gap-2">
|
||||||
class="mb-0.5 flex items-start justify-stretch gap-2 py-1.5 text-base"
|
<div class="flex items-center gap-2">
|
||||||
>
|
<UserAvatar :user="activity.data.sender" size="md" />
|
||||||
<div class="inline-flex flex-wrap gap-1 text-gray-600">
|
<span>{{ activity.data.sender_full_name }}</span>
|
||||||
<span class="font-medium text-gray-800">
|
<span>·</span>
|
||||||
{{ 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)">
|
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||||
<div class="text-sm text-gray-600">
|
<div class="text-sm text-gray-600">
|
||||||
{{ __(timeAgo(activity.creation)) }}
|
{{ __(timeAgo(activity.creation)) }}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
<Badge
|
||||||
</div>
|
v-if="activity.communication_type == 'Automated Message'"
|
||||||
<div
|
:label="__('Notification')"
|
||||||
class="cursor-pointer rounded bg-gray-50 px-4 py-3 text-base leading-6 transition-all duration-300 ease-in-out"
|
variant="subtle"
|
||||||
>
|
theme="green"
|
||||||
<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 class="flex gap-0.5">
|
||||||
|
<Tooltip :text="__('Reply')">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
class="text-gray-700"
|
||||||
|
@click="reply(activity.data)"
|
||||||
|
>
|
||||||
|
<ReplyIcon class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip :text="__('Reply All')">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
class="text-gray-700"
|
||||||
|
@click="reply(activity.data, true)"
|
||||||
|
>
|
||||||
|
<ReplyAllIcon class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm leading-5 text-gray-600">
|
||||||
|
{{ activity.data.subject }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 text-sm leading-5 text-gray-600">
|
||||||
|
<span class="mr-1 text-2xs font-bold text-gray-500">
|
||||||
|
{{ __('TO') }}:
|
||||||
|
</span>
|
||||||
|
<span>{{ activity.data.recipients }}</span>
|
||||||
|
<span v-if="activity.data.cc">, </span>
|
||||||
|
<span
|
||||||
|
v-if="activity.data.cc"
|
||||||
|
class="mr-1 text-2xs font-bold text-gray-500"
|
||||||
|
>
|
||||||
|
{{ __('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-2xs font-bold text-gray-500"
|
||||||
|
>
|
||||||
|
{{ __('BCC') }}:
|
||||||
|
</span>
|
||||||
|
<span v-if="activity.data.bcc">{{ activity.data.bcc }}</span>
|
||||||
|
</div>
|
||||||
|
<EmailContent :content="activity.data.content" />
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<AttachmentItem
|
||||||
|
v-for="a in activity.data.attachments"
|
||||||
|
:key="a.file_url"
|
||||||
|
:label="a.file_name"
|
||||||
|
:url="a.file_url"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mb-4"
|
||||||
|
:id="activity.name"
|
||||||
|
v-else-if="activity.activity_type == 'comment'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mb-0.5 flex items-start justify-stretch gap-2 py-1.5 text-base"
|
||||||
|
>
|
||||||
|
<div class="inline-flex flex-wrap gap-1 text-gray-600">
|
||||||
|
<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>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="
|
class="cursor-pointer rounded bg-gray-50 px-4 py-3 text-base leading-6 transition-all duration-300 ease-in-out"
|
||||||
activity.activity_type == 'incoming_call' ||
|
|
||||||
activity.activity_type == 'outgoing_call'
|
|
||||||
"
|
|
||||||
class="mb-3 flex flex-col gap-3 rounded-md bg-gray-50 p-4 sm:max-w-[70%]"
|
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="prose-f" v-html="activity.content" />
|
||||||
<div>
|
<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>
|
||||||
|
<div
|
||||||
|
v-else-if="
|
||||||
|
activity.activity_type == 'incoming_call' ||
|
||||||
|
activity.activity_type == 'outgoing_call'
|
||||||
|
"
|
||||||
|
class="mb-3 flex flex-col gap-3 rounded-md bg-gray-50 p-4 sm:max-w-[70%]"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
{{
|
||||||
|
activity.type == 'Incoming'
|
||||||
|
? __('Inbound Call')
|
||||||
|
: __('Outbound Call')
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
{{ __(timeAgo(activity.creation)) }}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<DurationIcon class="h-4 w-4 text-gray-600" />
|
||||||
|
<div class="text-sm text-gray-600">{{ __('Duration') }}</div>
|
||||||
|
<div class="text-sm">
|
||||||
|
{{ activity.duration }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="activity.recording_url"
|
||||||
|
class="flex cursor-pointer select-none items-center gap-1"
|
||||||
|
@click="activity.show_recording = !activity.show_recording"
|
||||||
|
>
|
||||||
|
<PlayIcon class="h-4 w-4 text-gray-600" />
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
{{
|
{{
|
||||||
activity.type == 'Incoming'
|
activity.show_recording
|
||||||
? __('Inbound Call')
|
? __('Hide Recording')
|
||||||
: __('Outbound Call')
|
: __('Listen to Call')
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
|
||||||
<div class="text-sm text-gray-600">
|
|
||||||
{{ __(timeAgo(activity.creation)) }}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div
|
||||||
<DurationIcon class="h-4 w-4 text-gray-600" />
|
v-if="activity.show_recording && activity.recording_url"
|
||||||
<div class="text-sm text-gray-600">{{ __('Duration') }}</div>
|
class="flex items-center justify-between rounded border"
|
||||||
<div class="text-sm">
|
>
|
||||||
{{ activity.duration }}
|
<audio
|
||||||
</div>
|
class="audio-control"
|
||||||
</div>
|
controls
|
||||||
<div
|
:src="activity.recording_url"
|
||||||
v-if="activity.recording_url"
|
></audio>
|
||||||
class="flex cursor-pointer select-none items-center gap-1"
|
</div>
|
||||||
@click="activity.show_recording = !activity.show_recording"
|
<div
|
||||||
>
|
class="flex items-center justify-between sm:justify-start sm:gap-1"
|
||||||
<PlayIcon class="h-4 w-4 text-gray-600" />
|
>
|
||||||
<div class="text-sm text-gray-600">
|
<div class="flex items-center gap-1">
|
||||||
{{
|
<Avatar
|
||||||
activity.show_recording
|
:image="activity.caller.image"
|
||||||
? __('Hide Recording')
|
:label="activity.caller.label"
|
||||||
: __('Listen to Call')
|
class="sm:h-8 sm:w-8"
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="activity.show_recording && activity.recording_url"
|
|
||||||
class="flex items-center justify-between rounded border"
|
|
||||||
>
|
|
||||||
<audio
|
|
||||||
class="audio-control"
|
|
||||||
controls
|
|
||||||
:src="activity.recording_url"
|
|
||||||
></audio>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-between sm:justify-start sm:gap-1"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-1">
|
|
||||||
<Avatar
|
|
||||||
:image="activity.caller.image"
|
|
||||||
:label="activity.caller.label"
|
|
||||||
class="sm:h-8 sm:w-8"
|
|
||||||
/>
|
|
||||||
<div class="ml-1 flex flex-col gap-1">
|
|
||||||
<div class="text-xs font-medium sm:text-base">
|
|
||||||
{{ __(activity.caller.label) }}
|
|
||||||
</div>
|
|
||||||
<div class="text-2xs text-gray-600 sm:text-xs">
|
|
||||||
{{ activity.from }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<FeatherIcon
|
|
||||||
name="arrow-right"
|
|
||||||
class="size-4 text-gray-600 sm:mx-2 sm:size-5"
|
|
||||||
/>
|
/>
|
||||||
<div class="flex items-center gap-1">
|
<div class="ml-1 flex flex-col gap-1">
|
||||||
<Avatar
|
<div class="text-xs font-medium sm:text-base">
|
||||||
:image="activity.receiver.image"
|
{{ __(activity.caller.label) }}
|
||||||
:label="activity.receiver.label"
|
</div>
|
||||||
class="sm:h-8 sm:w-8"
|
<div class="text-2xs text-gray-600 sm:text-xs">
|
||||||
/>
|
{{ activity.from }}
|
||||||
<div class="ml-1 flex flex-col gap-1">
|
</div>
|
||||||
<div class="text-xs font-medium sm:text-base">
|
</div>
|
||||||
{{ __(activity.receiver.label) }}
|
</div>
|
||||||
</div>
|
<FeatherIcon
|
||||||
<div class="text-2xs text-gray-600 sm:text-xs">
|
name="arrow-right"
|
||||||
{{ activity.to }}
|
class="size-4 text-gray-600 sm:mx-2 sm:size-5"
|
||||||
</div>
|
/>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Avatar
|
||||||
|
:image="activity.receiver.image"
|
||||||
|
:label="activity.receiver.label"
|
||||||
|
class="sm:h-8 sm:w-8"
|
||||||
|
/>
|
||||||
|
<div class="ml-1 flex flex-col gap-1">
|
||||||
|
<div class="text-xs font-medium sm:text-base">
|
||||||
|
{{ __(activity.receiver.label) }}
|
||||||
|
</div>
|
||||||
|
<div class="text-2xs text-gray-600 sm:text-xs">
|
||||||
|
{{ activity.to }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mb-4 flex flex-col gap-5 py-1.5">
|
</div>
|
||||||
<div class="flex items-start justify-stretch gap-2 text-base">
|
<div v-else class="mb-4 flex flex-col gap-5 py-1.5">
|
||||||
<div class="inline-flex flex-wrap gap-1 text-gray-600">
|
<div class="flex items-start justify-stretch gap-2 text-base">
|
||||||
<span class="font-medium text-gray-800">
|
<div class="inline-flex flex-wrap gap-1 text-gray-600">
|
||||||
{{ activity.owner_name }}
|
<span class="font-medium text-gray-800">
|
||||||
</span>
|
{{ activity.owner_name }}
|
||||||
<span v-if="activity.type">{{ __(activity.type) }}</span>
|
</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"
|
||||||
|
v-for="activity in activity.other_versions"
|
||||||
|
class="flex items-start justify-stretch gap-2 text-base"
|
||||||
|
>
|
||||||
|
<div class="flex items-start gap-1 text-gray-600">
|
||||||
|
<div class="flex flex-1 items-center gap-1">
|
||||||
<span
|
<span
|
||||||
v-if="activity.data.field_label"
|
v-if="activity.data.field_label"
|
||||||
class="max-w-xs truncate font-medium text-gray-800"
|
class="max-w-xs truncate text-gray-600"
|
||||||
>
|
>
|
||||||
{{ __(activity.data.field_label) }}
|
{{ __(activity.data.field_label) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="activity.value">{{ __(activity.value) }}</span>
|
<FeatherIcon
|
||||||
|
name="arrow-right"
|
||||||
|
class="mx-1 h-4 w-4 text-gray-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap items-center gap-1">
|
||||||
|
<span v-if="activity.type">{{
|
||||||
|
startCase(__(activity.type))
|
||||||
|
}}</span>
|
||||||
<span
|
<span
|
||||||
v-if="activity.data.old_value"
|
v-if="activity.data.old_value"
|
||||||
class="max-w-xs font-medium text-gray-800"
|
class="max-w-xs font-medium text-gray-800"
|
||||||
@ -725,97 +780,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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
|
|
||||||
v-if="activity.other_versions && activity.show_others"
|
<div class="ml-auto whitespace-nowrap">
|
||||||
v-for="activity in activity.other_versions"
|
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||||
class="flex items-start justify-stretch gap-2 text-base"
|
<div class="text-sm text-gray-600">
|
||||||
|
{{ __(timeAgo(activity.creation)) }}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="activity.other_versions">
|
||||||
|
<Button
|
||||||
|
:label="
|
||||||
|
activity.show_others
|
||||||
|
? __('Hide all Changes')
|
||||||
|
: __('Show all Changes')
|
||||||
|
"
|
||||||
|
variant="outline"
|
||||||
|
@click="activity.show_others = !activity.show_others"
|
||||||
>
|
>
|
||||||
<div class="flex items-start gap-1 text-gray-600">
|
<template #suffix>
|
||||||
<div class="flex flex-1 items-center gap-1">
|
<FeatherIcon
|
||||||
<span
|
:name="activity.show_others ? 'chevron-up' : 'chevron-down'"
|
||||||
v-if="activity.data.field_label"
|
class="h-4 text-gray-600"
|
||||||
class="max-w-xs truncate text-gray-600"
|
/>
|
||||||
>
|
</template>
|
||||||
{{ __(activity.data.field_label) }}
|
</Button>
|
||||||
</span>
|
|
||||||
<FeatherIcon
|
|
||||||
name="arrow-right"
|
|
||||||
class="mx-1 h-4 w-4 text-gray-600"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap items-center gap-1">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<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">
|
|
||||||
<Button
|
|
||||||
:label="
|
|
||||||
activity.show_others
|
|
||||||
? __('Hide all Changes')
|
|
||||||
: __('Show all Changes')
|
|
||||||
"
|
|
||||||
variant="outline"
|
|
||||||
@click="activity.show_others = !activity.show_others"
|
|
||||||
>
|
|
||||||
<template #suffix>
|
|
||||||
<FeatherIcon
|
|
||||||
:name="activity.show_others ? 'chevron-up' : 'chevron-down'"
|
|
||||||
class="h-4 text-gray-600"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -891,6 +882,7 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import EmailContent from '@/components/EmailContent.vue'
|
||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
|
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
|
||||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||||
@ -1404,79 +1396,4 @@ nextTick(() => {
|
|||||||
.audio-control::-webkit-media-controls-panel {
|
.audio-control::-webkit-media-controls-panel {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* email content */
|
|
||||||
|
|
||||||
.email-content {
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
:deep(.email-content
|
|
||||||
:is(:where(table):not(:where([class~='not-prose'], [class~='not-prose']
|
|
||||||
*)))) {
|
|
||||||
table-layout: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.email-content
|
|
||||||
:where(table):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
|
|
||||||
width: unset;
|
|
||||||
table-layout: auto;
|
|
||||||
text-align: unset;
|
|
||||||
margin-top: unset;
|
|
||||||
margin-bottom: unset;
|
|
||||||
font-size: unset;
|
|
||||||
line-height: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* tr */
|
|
||||||
|
|
||||||
:deep(.email-content
|
|
||||||
:where(tbody tr):not(:where([class~='not-prose'], [class~='not-prose']
|
|
||||||
*))) {
|
|
||||||
border-bottom-width: 0;
|
|
||||||
border-bottom-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* td */
|
|
||||||
|
|
||||||
:deep(.email-content
|
|
||||||
:is(:where(td):not(:where([class~='not-prose'], [class~='not-prose'] *)))) {
|
|
||||||
position: unset;
|
|
||||||
border-width: 0;
|
|
||||||
border-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.email-content
|
|
||||||
:where(tbody td):not(:where([class~='not-prose'], [class~='not-prose']
|
|
||||||
*))) {
|
|
||||||
vertical-align: revert;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* image */
|
|
||||||
:deep(.email-content
|
|
||||||
:is(:where(img):not(:where([class~='not-prose'], [class~='not-prose']
|
|
||||||
*)))) {
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.email-content
|
|
||||||
:where(img):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* before & after */
|
|
||||||
|
|
||||||
:deep(.email-content
|
|
||||||
:where(blockquote
|
|
||||||
p:first-of-type):not(:where([class~='not-prose'], [class~='not-prose']
|
|
||||||
*))::before) {
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.email-content
|
|
||||||
:where(blockquote
|
|
||||||
p:last-of-type):not(:where([class~='not-prose'], [class~='not-prose']
|
|
||||||
*))::after) {
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
127
frontend/src/components/EmailContent.vue
Normal file
127
frontend/src/components/EmailContent.vue
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<template>
|
||||||
|
<iframe
|
||||||
|
ref="iframeRef"
|
||||||
|
:srcdoc="htmlContent"
|
||||||
|
class="prose-f block h-screen max-h-[500px] w-full"
|
||||||
|
style="
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
black calc(100% - 30px),
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const files = import.meta.globEager('/src/index.css')
|
||||||
|
const css = files['/src/index.css'].default
|
||||||
|
|
||||||
|
const iframeRef = ref(null)
|
||||||
|
|
||||||
|
const htmlContent = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
${css}
|
||||||
|
|
||||||
|
.email-content {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.email-content
|
||||||
|
:is(:where(table):not(:where([class~='not-prose'], [class~='not-prose']
|
||||||
|
*))) {
|
||||||
|
table-layout: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-content
|
||||||
|
:where(table):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
|
||||||
|
width: unset;
|
||||||
|
table-layout: auto;
|
||||||
|
text-align: unset;
|
||||||
|
margin-top: unset;
|
||||||
|
margin-bottom: unset;
|
||||||
|
font-size: unset;
|
||||||
|
line-height: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tr */
|
||||||
|
|
||||||
|
.email-content
|
||||||
|
:where(tbody tr):not(:where([class~='not-prose'], [class~='not-prose']
|
||||||
|
*)) {
|
||||||
|
border-bottom-width: 0;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* td */
|
||||||
|
|
||||||
|
.email-content
|
||||||
|
:is(:where(td):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
|
||||||
|
position: unset;
|
||||||
|
border-width: 0;
|
||||||
|
border-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-content
|
||||||
|
:where(tbody td):not(:where([class~='not-prose'], [class~='not-prose']
|
||||||
|
*)) {
|
||||||
|
vertical-align: revert;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* image */
|
||||||
|
.email-content
|
||||||
|
:is(:where(img):not(:where([class~='not-prose'], [class~='not-prose']
|
||||||
|
*))) {
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-content
|
||||||
|
:where(img):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* before & after */
|
||||||
|
|
||||||
|
.email-content
|
||||||
|
:where(blockquote
|
||||||
|
p:first-of-type):not(:where([class~='not-prose'], [class~='not-prose']
|
||||||
|
*))::before {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-content
|
||||||
|
:where(blockquote
|
||||||
|
p:last-of-type):not(:where([class~='not-prose'], [class~='not-prose']
|
||||||
|
*))::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div ref="emailContentRef" class="email-content prose-f">${props.content}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
watch(iframeRef, (iframe) => {
|
||||||
|
if (iframe) {
|
||||||
|
iframe.onload = () => {
|
||||||
|
const emailContent =
|
||||||
|
iframe.contentWindow.document.querySelector('.email-content')
|
||||||
|
iframe.style.height = emailContent.offsetHeight + 25 + 'px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@ -1,16 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<component
|
||||||
|
:is="props.as || 'div'"
|
||||||
ref="scrollableDiv"
|
ref="scrollableDiv"
|
||||||
:style="`maskImage: ${maskStyle}`"
|
:style="`maskImage: ${maskStyle}`"
|
||||||
@scroll="updateMaskStyle"
|
@scroll="updateMaskStyle"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
as: {
|
||||||
|
type: String,
|
||||||
|
default: 'div',
|
||||||
|
},
|
||||||
maskLength: {
|
maskLength: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 30,
|
default: 30,
|
||||||
|
|||||||
@ -15,11 +15,13 @@
|
|||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
<Tooltip v-if="filters?.size" :text="__('Clear all Filter')">
|
<Tooltip v-if="filters?.size" :text="__('Clear all Filter')">
|
||||||
<Button
|
<span>
|
||||||
class="rounded-l-none border-l"
|
<Button
|
||||||
icon="x"
|
class="rounded-l-none border-l"
|
||||||
@click.stop="clearfilter(false)"
|
icon="x"
|
||||||
/>
|
@click.stop="clearfilter(false)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template #body="{ close }">
|
<template #body="{ close }">
|
||||||
|
|||||||
@ -84,7 +84,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template
|
<template
|
||||||
v-if="callLog.type.label == 'Incoming' && !callLog.reference_docname"
|
v-if="
|
||||||
|
callLog.doc?.type.label == 'Incoming' && !callLog.doc?.reference_docname
|
||||||
|
"
|
||||||
#actions
|
#actions
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@ -116,11 +118,12 @@ import {
|
|||||||
createDocumentResource,
|
createDocumentResource,
|
||||||
call,
|
call,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
|
import { getCallLogDetail } from '@/utils/callLog'
|
||||||
import { ref, computed, h, watch } from 'vue'
|
import { ref, computed, h, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
callLog: {
|
name: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
@ -130,60 +133,66 @@ const show = defineModel()
|
|||||||
const showNoteModal = ref(false)
|
const showNoteModal = ref(false)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const callNoteDoc = ref(null)
|
const callNoteDoc = ref(null)
|
||||||
|
const callLog = ref({})
|
||||||
|
|
||||||
const detailFields = computed(() => {
|
const detailFields = computed(() => {
|
||||||
|
if (!callLog.value.doc) return []
|
||||||
let details = [
|
let details = [
|
||||||
{
|
{
|
||||||
icon: h(FeatherIcon, {
|
icon: h(FeatherIcon, {
|
||||||
name: props.callLog.type.icon,
|
name: callLog.value.doc.type.icon,
|
||||||
class: 'h-3.5 w-3.5',
|
class: 'h-3.5 w-3.5',
|
||||||
}),
|
}),
|
||||||
name: 'type',
|
name: 'type',
|
||||||
value: props.callLog.type.label + ' Call',
|
value: callLog.value.doc.type.label + ' Call',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: ContactsIcon,
|
icon: ContactsIcon,
|
||||||
name: 'receiver',
|
name: 'receiver',
|
||||||
value: {
|
value: {
|
||||||
receiver: props.callLog.receiver,
|
receiver: callLog.value.doc.receiver,
|
||||||
caller: props.callLog.caller,
|
caller: callLog.value.doc.caller,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon:
|
icon:
|
||||||
props.callLog.reference_doctype == 'CRM Lead' ? LeadsIcon : Dealsicon,
|
callLog.value.doc.reference_doctype == 'CRM Lead'
|
||||||
|
? LeadsIcon
|
||||||
|
: Dealsicon,
|
||||||
name: 'reference_doctype',
|
name: 'reference_doctype',
|
||||||
value: props.callLog.reference_doctype == 'CRM Lead' ? 'Lead' : 'Deal',
|
value:
|
||||||
|
callLog.value.doc.reference_doctype == 'CRM Lead' ? 'Lead' : 'Deal',
|
||||||
link: () => {
|
link: () => {
|
||||||
if (props.callLog.reference_doctype == 'CRM Lead') {
|
if (callLog.value.doc.reference_doctype == 'CRM Lead') {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'Lead',
|
name: 'Lead',
|
||||||
params: { leadId: props.callLog.reference_docname },
|
params: { leadId: callLog.value.doc.reference_docname },
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'Deal',
|
name: 'Deal',
|
||||||
params: { dealId: props.callLog.reference_docname },
|
params: { dealId: callLog.value.doc.reference_docname },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
condition: () => callLog.value.doc.reference_docname,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: CalendarIcon,
|
icon: CalendarIcon,
|
||||||
name: 'creation',
|
name: 'creation',
|
||||||
value: props.callLog.creation.label,
|
value: callLog.value.doc.creation.label,
|
||||||
tooltip: props.callLog.creation.label,
|
tooltip: callLog.value.doc.creation.label,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: DurationIcon,
|
icon: DurationIcon,
|
||||||
name: 'duration',
|
name: 'duration',
|
||||||
value: props.callLog.duration.label,
|
value: callLog.value.doc.duration.label,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: CheckCircleIcon,
|
icon: CheckCircleIcon,
|
||||||
name: 'status',
|
name: 'status',
|
||||||
value: props.callLog.status.label,
|
value: callLog.value.doc.status.label,
|
||||||
color: props.callLog.status.color,
|
color: callLog.value.doc.status.color,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: h(FeatherIcon, {
|
icon: h(FeatherIcon, {
|
||||||
@ -191,7 +200,7 @@ const detailFields = computed(() => {
|
|||||||
class: 'h-4 w-4 mt-2',
|
class: 'h-4 w-4 mt-2',
|
||||||
}),
|
}),
|
||||||
name: 'recording_url',
|
name: 'recording_url',
|
||||||
value: props.callLog.recording_url,
|
value: callLog.value.doc.recording_url,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: NoteIcon,
|
icon: NoteIcon,
|
||||||
@ -200,12 +209,14 @@ const detailFields = computed(() => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return details.filter((detail) => detail.value)
|
return details
|
||||||
|
.filter((detail) => detail.value)
|
||||||
|
.filter((detail) => (detail.condition ? detail.condition() : true))
|
||||||
})
|
})
|
||||||
|
|
||||||
function createLead() {
|
function createLead() {
|
||||||
call('crm.fcrm.doctype.crm_call_log.crm_call_log.create_lead_from_call_log', {
|
call('crm.fcrm.doctype.crm_call_log.crm_call_log.create_lead_from_call_log', {
|
||||||
call_log: props.callLog,
|
call_log: callLog.value.doc,
|
||||||
}).then((d) => {
|
}).then((d) => {
|
||||||
if (d) {
|
if (d) {
|
||||||
router.push({ name: 'Lead', params: { leadId: d } })
|
router.push({ name: 'Lead', params: { leadId: d } })
|
||||||
@ -215,12 +226,45 @@ function createLead() {
|
|||||||
|
|
||||||
watch(show, (val) => {
|
watch(show, (val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
callNoteDoc.value = createDocumentResource({
|
callLog.value = createDocumentResource({
|
||||||
doctype: 'FCRM Note',
|
doctype: 'CRM Call Log',
|
||||||
name: props.callLog.note,
|
name: props.name,
|
||||||
fields: ['title', 'content'],
|
fields: [
|
||||||
cache: ['note', props.callLog.note],
|
'name',
|
||||||
|
'caller',
|
||||||
|
'receiver',
|
||||||
|
'duration',
|
||||||
|
'type',
|
||||||
|
'status',
|
||||||
|
'from',
|
||||||
|
'to',
|
||||||
|
'note',
|
||||||
|
'recording_url',
|
||||||
|
'reference_doctype',
|
||||||
|
'reference_docname',
|
||||||
|
'creation',
|
||||||
|
],
|
||||||
|
cache: ['call_log', props.name],
|
||||||
auto: true,
|
auto: true,
|
||||||
|
transform: (doc) => {
|
||||||
|
for (const key in doc) {
|
||||||
|
doc[key] = getCallLogDetail(key, doc)
|
||||||
|
}
|
||||||
|
return doc
|
||||||
|
},
|
||||||
|
onSuccess: (doc) => {
|
||||||
|
if (!doc.note) {
|
||||||
|
callNoteDoc.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callNoteDoc.value = createDocumentResource({
|
||||||
|
doctype: 'FCRM Note',
|
||||||
|
name: doc.note,
|
||||||
|
fields: ['title', 'content'],
|
||||||
|
cache: ['note', doc.note],
|
||||||
|
auto: true,
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
64
frontend/src/components/QuickFilterField.vue
Normal file
64
frontend/src/components/QuickFilterField.vue
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<FormControl
|
||||||
|
v-if="filter.type == 'Check'"
|
||||||
|
:label="filter.label"
|
||||||
|
type="checkbox"
|
||||||
|
v-model="filter.value"
|
||||||
|
@change.stop="updateFilter(filter, $event.target.checked)"
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
|
v-else-if="filter.type === 'Select'"
|
||||||
|
class="form-control cursor-pointer [&_select]:cursor-pointer"
|
||||||
|
type="select"
|
||||||
|
v-model="filter.value"
|
||||||
|
:options="filter.options"
|
||||||
|
:placeholder="filter.label"
|
||||||
|
@change.stop="updateFilter(filter, $event.target.value)"
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
v-else-if="filter.type === 'Link'"
|
||||||
|
:value="filter.value"
|
||||||
|
:doctype="filter.options"
|
||||||
|
:placeholder="filter.label"
|
||||||
|
@change="(data) => updateFilter(filter, data)"
|
||||||
|
/>
|
||||||
|
<component
|
||||||
|
v-else-if="['Date', 'Datetime'].includes(filter.type)"
|
||||||
|
class="border-none"
|
||||||
|
:is="filter.type === 'Date' ? DatePicker : DatetimePicker"
|
||||||
|
:value="filter.value"
|
||||||
|
@change="(v) => updateFilter(filter, v)"
|
||||||
|
:placeholder="filter.label"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
v-else
|
||||||
|
v-model="filter.value"
|
||||||
|
type="text"
|
||||||
|
:placeholder="filter.label"
|
||||||
|
@input.stop="debouncedFn(filter, $event.target.value)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import DatePicker from '@/components/Controls/DatePicker.vue'
|
||||||
|
import DatetimePicker from '@/components/Controls/DatetimePicker.vue'
|
||||||
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
import { TextInput, FormControl } from 'frappe-ui'
|
||||||
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
filter: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['applyQuickFilter'])
|
||||||
|
|
||||||
|
const debouncedFn = useDebounceFn((f, value) => {
|
||||||
|
emit('applyQuickFilter', f, value)
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
function updateFilter(f, value) {
|
||||||
|
emit('applyQuickFilter', f, value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -120,44 +120,9 @@
|
|||||||
:key="filter.name"
|
:key="filter.name"
|
||||||
class="m-1 min-w-36"
|
class="m-1 min-w-36"
|
||||||
>
|
>
|
||||||
<FormControl
|
<QuickFilterField
|
||||||
v-if="filter.type == 'Check'"
|
:filter="filter"
|
||||||
:label="filter.label"
|
@applyQuickFilter="(f, v) => applyQuickFilter(f, v)"
|
||||||
type="checkbox"
|
|
||||||
v-model="filter.value"
|
|
||||||
@change.stop="applyQuickFilter(filter, $event.target.checked)"
|
|
||||||
/>
|
|
||||||
<FormControl
|
|
||||||
v-else-if="filter.type === 'Select'"
|
|
||||||
class="form-control cursor-pointer [&_select]:cursor-pointer"
|
|
||||||
type="select"
|
|
||||||
v-model="filter.value"
|
|
||||||
:options="filter.options"
|
|
||||||
:placeholder="filter.label"
|
|
||||||
@change.stop="applyQuickFilter(filter, $event.target.value)"
|
|
||||||
/>
|
|
||||||
<Link
|
|
||||||
v-else-if="filter.type === 'Link'"
|
|
||||||
:value="filter.value"
|
|
||||||
:doctype="filter.options"
|
|
||||||
:placeholder="filter.label"
|
|
||||||
@change="(data) => applyQuickFilter(filter, data)"
|
|
||||||
/>
|
|
||||||
<component
|
|
||||||
v-else-if="['Date', 'Datetime'].includes(filter.type)"
|
|
||||||
class="border-none"
|
|
||||||
:is="filter.type === 'Date' ? DatePicker : DatetimePicker"
|
|
||||||
:value="filter.value"
|
|
||||||
@change="(v) => applyQuickFilter(filter, v)"
|
|
||||||
:placeholder="filter.label"
|
|
||||||
/>
|
|
||||||
<FormControl
|
|
||||||
v-else
|
|
||||||
:value="filter.value"
|
|
||||||
type="text"
|
|
||||||
:placeholder="filter.label"
|
|
||||||
:debounce="500"
|
|
||||||
@change.stop="applyQuickFilter(filter, $event.target.value)"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FadedScrollableDiv>
|
</FadedScrollableDiv>
|
||||||
@ -281,10 +246,8 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import DatePicker from '@/components/Controls/DatePicker.vue'
|
|
||||||
import DatetimePicker from '@/components/Controls/DatetimePicker.vue'
|
|
||||||
import Link from '@/components/Controls/Link.vue'
|
|
||||||
import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
|
import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
|
||||||
|
import QuickFilterField from '@/components/QuickFilterField.vue'
|
||||||
import RefreshIcon from '@/components/Icons/RefreshIcon.vue'
|
import RefreshIcon from '@/components/Icons/RefreshIcon.vue'
|
||||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||||
import DuplicateIcon from '@/components/Icons/DuplicateIcon.vue'
|
import DuplicateIcon from '@/components/Icons/DuplicateIcon.vue'
|
||||||
|
|||||||
@ -53,7 +53,7 @@
|
|||||||
<CallLogModal
|
<CallLogModal
|
||||||
v-model="showCallLogModal"
|
v-model="showCallLogModal"
|
||||||
v-model:reloadCallLogs="callLogs"
|
v-model:reloadCallLogs="callLogs"
|
||||||
:callLog="callLog"
|
:name="selectedCallLog"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -64,20 +64,10 @@ import LayoutHeader from '@/components/LayoutHeader.vue'
|
|||||||
import ViewControls from '@/components/ViewControls.vue'
|
import ViewControls from '@/components/ViewControls.vue'
|
||||||
import CallLogsListView from '@/components/ListViews/CallLogsListView.vue'
|
import CallLogsListView from '@/components/ListViews/CallLogsListView.vue'
|
||||||
import CallLogModal from '@/components/Modals/CallLogModal.vue'
|
import CallLogModal from '@/components/Modals/CallLogModal.vue'
|
||||||
import {
|
import { getCallLogDetail } from '@/utils/callLog'
|
||||||
secondsToDuration,
|
|
||||||
dateFormat,
|
|
||||||
dateTooltipFormat,
|
|
||||||
timeAgo,
|
|
||||||
} from '@/utils'
|
|
||||||
import { usersStore } from '@/stores/users'
|
|
||||||
import { contactsStore } from '@/stores/contacts'
|
|
||||||
import { Breadcrumbs } from 'frappe-ui'
|
import { Breadcrumbs } from 'frappe-ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const { getUser } = usersStore()
|
|
||||||
const { getContact, getLeadContact } = contactsStore()
|
|
||||||
|
|
||||||
const breadcrumbs = [{ label: __('Call Logs'), route: { name: 'Call Logs' } }]
|
const breadcrumbs = [{ label: __('Call Logs'), route: { name: 'Call Logs' } }]
|
||||||
|
|
||||||
const callLogsListView = ref(null)
|
const callLogsListView = ref(null)
|
||||||
@ -94,119 +84,17 @@ const rows = computed(() => {
|
|||||||
return callLogs.value?.data.data.map((callLog) => {
|
return callLogs.value?.data.data.map((callLog) => {
|
||||||
let _rows = {}
|
let _rows = {}
|
||||||
callLogs.value?.data.rows.forEach((row) => {
|
callLogs.value?.data.rows.forEach((row) => {
|
||||||
_rows[row] = callLog[row]
|
_rows[row] = getCallLogDetail(row, callLog)
|
||||||
|
|
||||||
let incoming = callLog.type === 'Incoming'
|
|
||||||
|
|
||||||
if (row === 'caller') {
|
|
||||||
_rows[row] = {
|
|
||||||
label: incoming
|
|
||||||
? getContact(callLog.from)?.full_name ||
|
|
||||||
getLeadContact(callLog.from)?.full_name ||
|
|
||||||
'Unknown'
|
|
||||||
: getUser(callLog.caller).full_name,
|
|
||||||
image: incoming
|
|
||||||
? getContact(callLog.from)?.image ||
|
|
||||||
getLeadContact(callLog.from)?.image
|
|
||||||
: getUser(callLog.caller).user_image,
|
|
||||||
}
|
|
||||||
} else if (row === 'receiver') {
|
|
||||||
_rows[row] = {
|
|
||||||
label: incoming
|
|
||||||
? getUser(callLog.receiver).full_name
|
|
||||||
: getContact(callLog.to)?.full_name ||
|
|
||||||
getLeadContact(callLog.to)?.full_name ||
|
|
||||||
'Unknown',
|
|
||||||
image: incoming
|
|
||||||
? getUser(callLog.receiver).user_image
|
|
||||||
: getContact(callLog.to)?.image ||
|
|
||||||
getLeadContact(callLog.to)?.image,
|
|
||||||
}
|
|
||||||
} else if (row === 'duration') {
|
|
||||||
_rows[row] = {
|
|
||||||
label: secondsToDuration(callLog.duration),
|
|
||||||
icon: 'clock',
|
|
||||||
}
|
|
||||||
} else if (row === 'type') {
|
|
||||||
_rows[row] = {
|
|
||||||
label: callLog.type,
|
|
||||||
icon: incoming ? 'phone-incoming' : 'phone-outgoing',
|
|
||||||
}
|
|
||||||
} else if (row === 'status') {
|
|
||||||
_rows[row] = {
|
|
||||||
label: statusLabelMap[callLog.status],
|
|
||||||
color: statusColorMap[callLog.status],
|
|
||||||
}
|
|
||||||
} else if (['modified', 'creation'].includes(row)) {
|
|
||||||
_rows[row] = {
|
|
||||||
label: dateFormat(callLog[row], dateTooltipFormat),
|
|
||||||
timeAgo: __(timeAgo(callLog[row])),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
return _rows
|
return _rows
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const showCallLogModal = ref(false)
|
const showCallLogModal = ref(false)
|
||||||
|
const selectedCallLog = ref(null)
|
||||||
const callLog = ref({
|
|
||||||
name: '',
|
|
||||||
caller: '',
|
|
||||||
receiver: '',
|
|
||||||
duration: '',
|
|
||||||
type: '',
|
|
||||||
status: '',
|
|
||||||
from: '',
|
|
||||||
to: '',
|
|
||||||
note: '',
|
|
||||||
recording_url: '',
|
|
||||||
reference_doctype: '',
|
|
||||||
reference_docname: '',
|
|
||||||
creation: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
function showCallLog(name) {
|
function showCallLog(name) {
|
||||||
let d = rows.value?.find((row) => row.name === name)
|
selectedCallLog.value = name
|
||||||
callLog.value = {
|
|
||||||
name: d.name,
|
|
||||||
caller: d.caller,
|
|
||||||
receiver: d.receiver,
|
|
||||||
duration: d.duration,
|
|
||||||
type: d.type,
|
|
||||||
status: d.status,
|
|
||||||
from: d.from,
|
|
||||||
to: d.to,
|
|
||||||
note: d.note,
|
|
||||||
recording_url: d.recording_url,
|
|
||||||
reference_doctype: d.reference_doctype,
|
|
||||||
reference_docname: d.reference_docname,
|
|
||||||
creation: d.creation,
|
|
||||||
}
|
|
||||||
showCallLogModal.value = true
|
showCallLogModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusLabelMap = {
|
|
||||||
Completed: 'Completed',
|
|
||||||
Initiated: 'Initiated',
|
|
||||||
Busy: 'Declined',
|
|
||||||
Failed: 'Failed',
|
|
||||||
Queued: 'Queued',
|
|
||||||
Cancelled: 'Cancelled',
|
|
||||||
Ringing: 'Ringing',
|
|
||||||
'No Answer': 'Missed Call',
|
|
||||||
'In Progress': 'In Progress',
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusColorMap = {
|
|
||||||
Completed: 'green',
|
|
||||||
Busy: 'orange',
|
|
||||||
Failed: 'red',
|
|
||||||
Initiated: 'gray',
|
|
||||||
Queued: 'gray',
|
|
||||||
Cancelled: 'gray',
|
|
||||||
Ringing: 'gray',
|
|
||||||
'No Answer': 'red',
|
|
||||||
'In Progress': 'blue',
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
84
frontend/src/utils/callLog.js
Normal file
84
frontend/src/utils/callLog.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import {
|
||||||
|
secondsToDuration,
|
||||||
|
dateFormat,
|
||||||
|
dateTooltipFormat,
|
||||||
|
timeAgo,
|
||||||
|
} from '@/utils'
|
||||||
|
import { usersStore } from '@/stores/users'
|
||||||
|
import { contactsStore } from '@/stores/contacts'
|
||||||
|
|
||||||
|
const { getUser } = usersStore()
|
||||||
|
const { getContact, getLeadContact } = contactsStore()
|
||||||
|
|
||||||
|
export function getCallLogDetail(row, log) {
|
||||||
|
let incoming = log.type === 'Incoming'
|
||||||
|
|
||||||
|
if (row === 'caller') {
|
||||||
|
return {
|
||||||
|
label: incoming
|
||||||
|
? getContact(log.from)?.full_name ||
|
||||||
|
getLeadContact(log.from)?.full_name ||
|
||||||
|
'Unknown'
|
||||||
|
: getUser(log.caller).full_name,
|
||||||
|
image: incoming
|
||||||
|
? getContact(log.from)?.image || getLeadContact(log.from)?.image
|
||||||
|
: getUser(log.caller).user_image,
|
||||||
|
}
|
||||||
|
} else if (row === 'receiver') {
|
||||||
|
return {
|
||||||
|
label: incoming
|
||||||
|
? getUser(log.receiver).full_name
|
||||||
|
: getContact(log.to)?.full_name ||
|
||||||
|
getLeadContact(log.to)?.full_name ||
|
||||||
|
'Unknown',
|
||||||
|
image: incoming
|
||||||
|
? getUser(log.receiver).user_image
|
||||||
|
: getContact(log.to)?.image || getLeadContact(log.to)?.image,
|
||||||
|
}
|
||||||
|
} else if (row === 'duration') {
|
||||||
|
return {
|
||||||
|
label: secondsToDuration(log.duration),
|
||||||
|
icon: 'clock',
|
||||||
|
}
|
||||||
|
} else if (row === 'type') {
|
||||||
|
return {
|
||||||
|
label: log.type,
|
||||||
|
icon: incoming ? 'phone-incoming' : 'phone-outgoing',
|
||||||
|
}
|
||||||
|
} else if (row === 'status') {
|
||||||
|
return {
|
||||||
|
label: statusLabelMap[log.status],
|
||||||
|
color: statusColorMap[log.status],
|
||||||
|
}
|
||||||
|
} else if (['modified', 'creation'].includes(row)) {
|
||||||
|
return {
|
||||||
|
label: dateFormat(log[row], dateTooltipFormat),
|
||||||
|
timeAgo: __(timeAgo(log[row])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return log[row]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const statusLabelMap = {
|
||||||
|
Completed: 'Completed',
|
||||||
|
Initiated: 'Initiated',
|
||||||
|
Busy: 'Declined',
|
||||||
|
Failed: 'Failed',
|
||||||
|
Queued: 'Queued',
|
||||||
|
Cancelled: 'Cancelled',
|
||||||
|
Ringing: 'Ringing',
|
||||||
|
'No Answer': 'Missed Call',
|
||||||
|
'In Progress': 'In Progress',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const statusColorMap = {
|
||||||
|
Completed: 'green',
|
||||||
|
Busy: 'orange',
|
||||||
|
Failed: 'red',
|
||||||
|
Initiated: 'gray',
|
||||||
|
Queued: 'gray',
|
||||||
|
Cancelled: 'gray',
|
||||||
|
Ringing: 'gray',
|
||||||
|
'No Answer': 'red',
|
||||||
|
'In Progress': 'blue',
|
||||||
|
}
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { Dialog, ErrorMessage } from 'frappe-ui'
|
|
||||||
import { h, reactive, ref } from 'vue'
|
|
||||||
|
|
||||||
let dialogs = ref([])
|
|
||||||
|
|
||||||
export let Dialogs = {
|
|
||||||
name: 'Dialogs',
|
|
||||||
render() {
|
|
||||||
return dialogs.value.map((dialog) => {
|
|
||||||
return h(
|
|
||||||
Dialog,
|
|
||||||
{
|
|
||||||
options: dialog,
|
|
||||||
modelValue: dialog.show,
|
|
||||||
'onUpdate:modelValue': (val) => (dialog.show = val),
|
|
||||||
},
|
|
||||||
() => [
|
|
||||||
h('p', { class: 'text-p-base text-gray-700' }, dialog.message),
|
|
||||||
h(ErrorMessage, { class: 'mt-2', message: dialog.error }),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDialog(options) {
|
|
||||||
let dialog = reactive(options)
|
|
||||||
dialog.key = `dialog-${Math.random().toString(36).slice(2, 9)}`
|
|
||||||
dialogs.value.push(dialog)
|
|
||||||
dialog.show = true
|
|
||||||
}
|
|
||||||
41
frontend/src/utils/dialogs.jsx
Normal file
41
frontend/src/utils/dialogs.jsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Dialog, ErrorMessage } from 'frappe-ui'
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
|
||||||
|
let dialogs = ref([])
|
||||||
|
|
||||||
|
export let Dialogs = {
|
||||||
|
name: 'Dialogs',
|
||||||
|
render() {
|
||||||
|
return dialogs.value.map((dialog) => (
|
||||||
|
<Dialog
|
||||||
|
options={dialog}
|
||||||
|
modelValue={dialog.show}
|
||||||
|
onUpdate:modelValue={(val) => (dialog.show = val)}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
'body-content': () => {
|
||||||
|
return [
|
||||||
|
dialog.message && (
|
||||||
|
<p class="text-p-base text-gray-700">{dialog.message}</p>
|
||||||
|
),
|
||||||
|
dialog.html && (
|
||||||
|
<div v-html={dialog.html} />
|
||||||
|
),
|
||||||
|
<ErrorMessage class="mt-2" message={dialog.error} />,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
</Dialog>
|
||||||
|
))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDialog(dialogOptions) {
|
||||||
|
let dialog = reactive(dialogOptions)
|
||||||
|
dialog.key = 'dialog-' + dialogs.value.length
|
||||||
|
dialog.show = false
|
||||||
|
setTimeout(() => {
|
||||||
|
dialog.show = true
|
||||||
|
}, 0)
|
||||||
|
dialogs.value.push(dialog)
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import frappeui from 'frappe-ui/vite'
|
import frappeui from 'frappe-ui/vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
@ -13,6 +14,7 @@ export default defineConfig({
|
|||||||
propsDestructure: true,
|
propsDestructure: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
vueJsx(),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
registerType: 'autoUpdate',
|
registerType: 'autoUpdate',
|
||||||
devOptions: {
|
devOptions: {
|
||||||
|
|||||||
154
yarn.lock
154
yarn.lock
@ -29,7 +29,7 @@
|
|||||||
jsonpointer "^5.0.0"
|
jsonpointer "^5.0.0"
|
||||||
leven "^3.1.0"
|
leven "^3.1.0"
|
||||||
|
|
||||||
"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.24.6":
|
"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.6":
|
||||||
version "7.24.6"
|
version "7.24.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.6.tgz#ab88da19344445c3d8889af2216606d3329f3ef2"
|
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.6.tgz#ab88da19344445c3d8889af2216606d3329f3ef2"
|
||||||
integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==
|
integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==
|
||||||
@ -42,7 +42,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.6.tgz#b3600217688cabb26e25f8e467019e66d71b7ae2"
|
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.6.tgz#b3600217688cabb26e25f8e467019e66d71b7ae2"
|
||||||
integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==
|
integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==
|
||||||
|
|
||||||
"@babel/core@^7.11.1":
|
"@babel/core@^7.11.1", "@babel/core@^7.23.3":
|
||||||
version "7.24.6"
|
version "7.24.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.6.tgz#8650e0e4b03589ebe886c4e4a60398db0a7ec787"
|
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.6.tgz#8650e0e4b03589ebe886c4e4a60398db0a7ec787"
|
||||||
integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==
|
integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==
|
||||||
@ -167,6 +167,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.24.6"
|
"@babel/types" "^7.24.6"
|
||||||
|
|
||||||
|
"@babel/helper-module-imports@~7.22.15":
|
||||||
|
version "7.22.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0"
|
||||||
|
integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==
|
||||||
|
dependencies:
|
||||||
|
"@babel/types" "^7.22.15"
|
||||||
|
|
||||||
"@babel/helper-module-transforms@^7.24.6":
|
"@babel/helper-module-transforms@^7.24.6":
|
||||||
version "7.24.6"
|
version "7.24.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz#22346ed9df44ce84dee850d7433c5b73fab1fe4e"
|
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz#22346ed9df44ce84dee850d7433c5b73fab1fe4e"
|
||||||
@ -276,7 +283,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88"
|
||||||
integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==
|
integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==
|
||||||
|
|
||||||
"@babel/parser@^7.24.6":
|
"@babel/parser@^7.24.4", "@babel/parser@^7.24.6":
|
||||||
version "7.24.6"
|
version "7.24.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.6.tgz#5e030f440c3c6c78d195528c3b688b101a365328"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.6.tgz#5e030f440c3c6c78d195528c3b688b101a365328"
|
||||||
integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==
|
integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==
|
||||||
@ -381,6 +388,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.8.0"
|
"@babel/helper-plugin-utils" "^7.8.0"
|
||||||
|
|
||||||
|
"@babel/plugin-syntax-jsx@^7.23.3":
|
||||||
|
version "7.24.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.6.tgz#bcca2964150437f88f65e3679e3d68762287b9c8"
|
||||||
|
integrity sha512-lWfvAIFNWMlCsU0DRUun2GpFwZdGTukLaHJqRh1JRb80NdAP5Sb1HDHB5X9P9OtgZHQl089UzQkpYlBq2VTPRw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-plugin-utils" "^7.24.6"
|
||||||
|
|
||||||
"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
|
"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
|
||||||
version "7.10.4"
|
version "7.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
|
||||||
@ -437,6 +451,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.14.5"
|
"@babel/helper-plugin-utils" "^7.14.5"
|
||||||
|
|
||||||
|
"@babel/plugin-syntax-typescript@^7.24.6":
|
||||||
|
version "7.24.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.6.tgz#769daf2982d60308bc83d8936eaecb7582463c87"
|
||||||
|
integrity sha512-TzCtxGgVTEJWWwcYwQhCIQ6WaKlo80/B+Onsk4RRCcYqpYGFcG9etPW94VToGte5AAcxRrhjPUFvUS3Y2qKi4A==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-plugin-utils" "^7.24.6"
|
||||||
|
|
||||||
"@babel/plugin-syntax-unicode-sets-regex@^7.18.6":
|
"@babel/plugin-syntax-unicode-sets-regex@^7.18.6":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357"
|
||||||
@ -801,6 +822,16 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.24.6"
|
"@babel/helper-plugin-utils" "^7.24.6"
|
||||||
|
|
||||||
|
"@babel/plugin-transform-typescript@^7.23.3":
|
||||||
|
version "7.24.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.6.tgz#339c6127a783c32e28a5b591e6c666f899b57db0"
|
||||||
|
integrity sha512-H0i+hDLmaYYSt6KU9cZE0gb3Cbssa/oxWis7PX4ofQzbvsfix9Lbh8SRk7LCPDlLWJHUiFeHU0qRRpF/4Zv7mQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-annotate-as-pure" "^7.24.6"
|
||||||
|
"@babel/helper-create-class-features-plugin" "^7.24.6"
|
||||||
|
"@babel/helper-plugin-utils" "^7.24.6"
|
||||||
|
"@babel/plugin-syntax-typescript" "^7.24.6"
|
||||||
|
|
||||||
"@babel/plugin-transform-unicode-escapes@^7.24.6":
|
"@babel/plugin-transform-unicode-escapes@^7.24.6":
|
||||||
version "7.24.6"
|
version "7.24.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.6.tgz#c8ddca8fd5bacece837a4e27bd3b7ed64580d1a8"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.6.tgz#c8ddca8fd5bacece837a4e27bd3b7ed64580d1a8"
|
||||||
@ -940,7 +971,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.14.0"
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
"@babel/template@^7.24.6":
|
"@babel/template@^7.23.9", "@babel/template@^7.24.6":
|
||||||
version "7.24.6"
|
version "7.24.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.6.tgz#048c347b2787a6072b24c723664c8d02b67a44f9"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.6.tgz#048c347b2787a6072b24c723664c8d02b67a44f9"
|
||||||
integrity sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==
|
integrity sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==
|
||||||
@ -949,7 +980,7 @@
|
|||||||
"@babel/parser" "^7.24.6"
|
"@babel/parser" "^7.24.6"
|
||||||
"@babel/types" "^7.24.6"
|
"@babel/types" "^7.24.6"
|
||||||
|
|
||||||
"@babel/traverse@^7.24.6":
|
"@babel/traverse@^7.23.9", "@babel/traverse@^7.24.6":
|
||||||
version "7.24.6"
|
version "7.24.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.6.tgz#0941ec50cdeaeacad0911eb67ae227a4f8424edc"
|
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.6.tgz#0941ec50cdeaeacad0911eb67ae227a4f8424edc"
|
||||||
integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==
|
integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==
|
||||||
@ -965,7 +996,7 @@
|
|||||||
debug "^4.3.1"
|
debug "^4.3.1"
|
||||||
globals "^11.1.0"
|
globals "^11.1.0"
|
||||||
|
|
||||||
"@babel/types@^7.24.6", "@babel/types@^7.4.4":
|
"@babel/types@^7.22.15", "@babel/types@^7.23.9", "@babel/types@^7.24.6", "@babel/types@^7.4.4":
|
||||||
version "7.24.6"
|
version "7.24.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.6.tgz#ba4e1f59870c10dc2fa95a274ac4feec23b21912"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.6.tgz#ba4e1f59870c10dc2fa95a274ac4feec23b21912"
|
||||||
integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==
|
integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==
|
||||||
@ -1975,11 +2006,53 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597"
|
resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597"
|
||||||
integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==
|
integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==
|
||||||
|
|
||||||
|
"@vitejs/plugin-vue-jsx@^3.0.1":
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-3.1.0.tgz#9953fd9456539e1f0f253bf0fcd1289e66c67cd1"
|
||||||
|
integrity sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/core" "^7.23.3"
|
||||||
|
"@babel/plugin-transform-typescript" "^7.23.3"
|
||||||
|
"@vue/babel-plugin-jsx" "^1.1.5"
|
||||||
|
|
||||||
"@vitejs/plugin-vue@^4.0.0", "@vitejs/plugin-vue@^4.2.3":
|
"@vitejs/plugin-vue@^4.0.0", "@vitejs/plugin-vue@^4.2.3":
|
||||||
version "4.6.2"
|
version "4.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz#057d2ded94c4e71b94e9814f92dcd9306317aa46"
|
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz#057d2ded94c4e71b94e9814f92dcd9306317aa46"
|
||||||
integrity sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==
|
integrity sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==
|
||||||
|
|
||||||
|
"@vue/babel-helper-vue-transform-on@1.2.2":
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.2.tgz#7f1f817a4f00ad531651a8d1d22e22d9e42807ef"
|
||||||
|
integrity sha512-nOttamHUR3YzdEqdM/XXDyCSdxMA9VizUKoroLX6yTyRtggzQMHXcmwh8a7ZErcJttIBIc9s68a1B8GZ+Dmvsw==
|
||||||
|
|
||||||
|
"@vue/babel-plugin-jsx@^1.1.5":
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.2.tgz#eb426fb4660aa510bb8d188ff0ec140405a97d8a"
|
||||||
|
integrity sha512-nYTkZUVTu4nhP199UoORePsql0l+wj7v/oyQjtThUVhJl1U+6qHuoVhIvR3bf7eVKjbCK+Cs2AWd7mi9Mpz9rA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-module-imports" "~7.22.15"
|
||||||
|
"@babel/helper-plugin-utils" "^7.22.5"
|
||||||
|
"@babel/plugin-syntax-jsx" "^7.23.3"
|
||||||
|
"@babel/template" "^7.23.9"
|
||||||
|
"@babel/traverse" "^7.23.9"
|
||||||
|
"@babel/types" "^7.23.9"
|
||||||
|
"@vue/babel-helper-vue-transform-on" "1.2.2"
|
||||||
|
"@vue/babel-plugin-resolve-type" "1.2.2"
|
||||||
|
camelcase "^6.3.0"
|
||||||
|
html-tags "^3.3.1"
|
||||||
|
svg-tags "^1.0.0"
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type@1.2.2":
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.2.tgz#66844898561da6449e0f4a261b0c875118e0707b"
|
||||||
|
integrity sha512-EntyroPwNg5IPVdUJupqs0CFzuf6lUrVvCspmv2J1FITLeGnUCuoGNNk78dgCusxEiYj6RMkTJflGSxk5aIC4A==
|
||||||
|
dependencies:
|
||||||
|
"@babel/code-frame" "^7.23.5"
|
||||||
|
"@babel/helper-module-imports" "~7.22.15"
|
||||||
|
"@babel/helper-plugin-utils" "^7.22.5"
|
||||||
|
"@babel/parser" "^7.23.9"
|
||||||
|
"@vue/compiler-sfc" "^3.4.15"
|
||||||
|
|
||||||
"@vue/compiler-core@3.4.21":
|
"@vue/compiler-core@3.4.21":
|
||||||
version "3.4.21"
|
version "3.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.21.tgz#868b7085378fc24e58c9aed14c8d62110a62be1a"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.21.tgz#868b7085378fc24e58c9aed14c8d62110a62be1a"
|
||||||
@ -1991,6 +2064,17 @@
|
|||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
|
"@vue/compiler-core@3.4.27":
|
||||||
|
version "3.4.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.27.tgz#e69060f4b61429fe57976aa5872cfa21389e4d91"
|
||||||
|
integrity sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/parser" "^7.24.4"
|
||||||
|
"@vue/shared" "3.4.27"
|
||||||
|
entities "^4.5.0"
|
||||||
|
estree-walker "^2.0.2"
|
||||||
|
source-map-js "^1.2.0"
|
||||||
|
|
||||||
"@vue/compiler-dom@3.4.21":
|
"@vue/compiler-dom@3.4.21":
|
||||||
version "3.4.21"
|
version "3.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz#0077c355e2008207283a5a87d510330d22546803"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz#0077c355e2008207283a5a87d510330d22546803"
|
||||||
@ -1999,6 +2083,14 @@
|
|||||||
"@vue/compiler-core" "3.4.21"
|
"@vue/compiler-core" "3.4.21"
|
||||||
"@vue/shared" "3.4.21"
|
"@vue/shared" "3.4.21"
|
||||||
|
|
||||||
|
"@vue/compiler-dom@3.4.27":
|
||||||
|
version "3.4.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz#d51d35f40d00ce235d7afc6ad8b09dfd92b1cc1c"
|
||||||
|
integrity sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==
|
||||||
|
dependencies:
|
||||||
|
"@vue/compiler-core" "3.4.27"
|
||||||
|
"@vue/shared" "3.4.27"
|
||||||
|
|
||||||
"@vue/compiler-sfc@3.4.21":
|
"@vue/compiler-sfc@3.4.21":
|
||||||
version "3.4.21"
|
version "3.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz#4af920dc31ab99e1ff5d152b5fe0ad12181145b2"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz#4af920dc31ab99e1ff5d152b5fe0ad12181145b2"
|
||||||
@ -2014,6 +2106,21 @@
|
|||||||
postcss "^8.4.35"
|
postcss "^8.4.35"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
|
"@vue/compiler-sfc@^3.4.15":
|
||||||
|
version "3.4.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz#399cac1b75c6737bf5440dc9cf3c385bb2959701"
|
||||||
|
integrity sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/parser" "^7.24.4"
|
||||||
|
"@vue/compiler-core" "3.4.27"
|
||||||
|
"@vue/compiler-dom" "3.4.27"
|
||||||
|
"@vue/compiler-ssr" "3.4.27"
|
||||||
|
"@vue/shared" "3.4.27"
|
||||||
|
estree-walker "^2.0.2"
|
||||||
|
magic-string "^0.30.10"
|
||||||
|
postcss "^8.4.38"
|
||||||
|
source-map-js "^1.2.0"
|
||||||
|
|
||||||
"@vue/compiler-ssr@3.4.21":
|
"@vue/compiler-ssr@3.4.21":
|
||||||
version "3.4.21"
|
version "3.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz#b84ae64fb9c265df21fc67f7624587673d324fef"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz#b84ae64fb9c265df21fc67f7624587673d324fef"
|
||||||
@ -2022,6 +2129,14 @@
|
|||||||
"@vue/compiler-dom" "3.4.21"
|
"@vue/compiler-dom" "3.4.21"
|
||||||
"@vue/shared" "3.4.21"
|
"@vue/shared" "3.4.21"
|
||||||
|
|
||||||
|
"@vue/compiler-ssr@3.4.27":
|
||||||
|
version "3.4.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz#2a8ecfef1cf448b09be633901a9c020360472e3d"
|
||||||
|
integrity sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==
|
||||||
|
dependencies:
|
||||||
|
"@vue/compiler-dom" "3.4.27"
|
||||||
|
"@vue/shared" "3.4.27"
|
||||||
|
|
||||||
"@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.5.1":
|
"@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.5.1":
|
||||||
version "6.6.1"
|
version "6.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.1.tgz#7c14346383751d9f6ad4bea0963245b30220ef83"
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.1.tgz#7c14346383751d9f6ad4bea0963245b30220ef83"
|
||||||
@ -2064,6 +2179,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.21.tgz#de526a9059d0a599f0b429af7037cd0c3ed7d5a1"
|
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.21.tgz#de526a9059d0a599f0b429af7037cd0c3ed7d5a1"
|
||||||
integrity sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==
|
integrity sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==
|
||||||
|
|
||||||
|
"@vue/shared@3.4.27":
|
||||||
|
version "3.4.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.27.tgz#f05e3cd107d157354bb4ae7a7b5fc9cf73c63b50"
|
||||||
|
integrity sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==
|
||||||
|
|
||||||
"@vueuse/core@10.9.0", "@vueuse/core@^10.3.0", "@vueuse/core@^10.4.1", "@vueuse/core@^10.5.0":
|
"@vueuse/core@10.9.0", "@vueuse/core@^10.3.0", "@vueuse/core@^10.4.1", "@vueuse/core@^10.5.0":
|
||||||
version "10.9.0"
|
version "10.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.9.0.tgz#7d779a95cf0189de176fee63cee4ba44b3c85d64"
|
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.9.0.tgz#7d779a95cf0189de176fee63cee4ba44b3c85d64"
|
||||||
@ -2372,6 +2492,11 @@ camelcase-css@^2.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
|
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
|
||||||
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
|
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
|
||||||
|
|
||||||
|
camelcase@^6.3.0:
|
||||||
|
version "6.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
|
||||||
|
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599:
|
caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599:
|
||||||
version "1.0.30001607"
|
version "1.0.30001607"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001607.tgz#b91e8e033f6bca4e13d3d45388d87fa88931d9a5"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001607.tgz#b91e8e033f6bca4e13d3d45388d87fa88931d9a5"
|
||||||
@ -3425,6 +3550,11 @@ html-encoding-sniffer@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
whatwg-encoding "^2.0.0"
|
whatwg-encoding "^2.0.0"
|
||||||
|
|
||||||
|
html-tags@^3.3.1:
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
|
||||||
|
integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
|
||||||
|
|
||||||
http-proxy-agent@^5.0.0:
|
http-proxy-agent@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
|
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
|
||||||
@ -3970,6 +4100,13 @@ magic-string@^0.25.0, magic-string@^0.25.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
sourcemap-codec "^1.4.8"
|
sourcemap-codec "^1.4.8"
|
||||||
|
|
||||||
|
magic-string@^0.30.10:
|
||||||
|
version "0.30.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e"
|
||||||
|
integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/sourcemap-codec" "^1.4.15"
|
||||||
|
|
||||||
magic-string@^0.30.7:
|
magic-string@^0.30.7:
|
||||||
version "0.30.9"
|
version "0.30.9"
|
||||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.9.tgz#8927ae21bfdd856310e07a1bc8dd5e73cb6c251d"
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.9.tgz#8927ae21bfdd856310e07a1bc8dd5e73cb6c251d"
|
||||||
@ -5251,6 +5388,11 @@ supports-preserve-symlinks-flag@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||||
|
|
||||||
|
svg-tags@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
|
||||||
|
integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==
|
||||||
|
|
||||||
symbol-tree@^3.2.4:
|
symbol-tree@^3.2.4:
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user