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",
|
||||
},
|
||||
{"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": "_assign", "fieldtype": "Text", "label": "Assigned To"},
|
||||
{"fieldname": "creation", "fieldtype": "Datetime", "label": "Created On"},
|
||||
@ -314,7 +314,7 @@ def get_list_data(
|
||||
},
|
||||
{"label": "Assigned To", "type": "Text", "value": "_assign"},
|
||||
{"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:
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.5",
|
||||
"vite": "^4.4.9",
|
||||
|
||||
@ -102,11 +102,11 @@
|
||||
>
|
||||
<div
|
||||
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
|
||||
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)"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
@ -156,10 +156,10 @@
|
||||
</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
|
||||
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
|
||||
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
|
||||
v-else-if="title == 'Tasks'"
|
||||
class="activity px-4 pb-3 sm:px-10 sm:pb-5"
|
||||
>
|
||||
<div v-else-if="title == 'Tasks'" class="px-4 pb-3 sm:px-10 sm:pb-5">
|
||||
<div v-for="(task, i) in activities">
|
||||
<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)"
|
||||
>
|
||||
<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-for="(call, i) in activities">
|
||||
<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
|
||||
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 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
|
||||
class="px-4 sm:px-10"
|
||||
:class="
|
||||
title == 'Activity'
|
||||
? 'grid grid-cols-[30px_minmax(auto,_1fr)] sm:gap-4 gap-2'
|
||||
: ''
|
||||
"
|
||||
v-if="title == 'Activity'"
|
||||
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',
|
||||
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
|
||||
v-if="title == 'Activity'"
|
||||
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',
|
||||
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'
|
||||
: '',
|
||||
]"
|
||||
class="z-10 flex h-7 w-7 items-center justify-center rounded bg-gray-100"
|
||||
:class="{
|
||||
'mt-3': [
|
||||
'communication',
|
||||
'incoming_call',
|
||||
'outgoing_call',
|
||||
].includes(activity.activity_type),
|
||||
'bg-white': ['added', 'removed', 'changed'].includes(
|
||||
activity.activity_type
|
||||
),
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="z-10 flex h-7 w-7 items-center justify-center rounded bg-gray-100"
|
||||
:class="{
|
||||
'mt-3': [
|
||||
'communication',
|
||||
'incoming_call',
|
||||
'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>
|
||||
<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="mb-4"
|
||||
:id="activity.name"
|
||||
v-else-if="activity.activity_type == 'comment'"
|
||||
class="cursor-pointer rounded-md bg-gray-50 p-3 text-base leading-6 transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="cursor-pointer rounded bg-gray-50 px-4 py-3 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"
|
||||
<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>
|
||||
<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
|
||||
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%]"
|
||||
class="cursor-pointer rounded bg-gray-50 px-4 py-3 text-base leading-6 transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<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>
|
||||
<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'
|
||||
? __('Inbound Call')
|
||||
: __('Outbound Call')
|
||||
activity.show_recording
|
||||
? __('Hide Recording')
|
||||
: __('Listen to 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.show_recording
|
||||
? __('Hide Recording')
|
||||
: __('Listen to Call')
|
||||
}}
|
||||
</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>
|
||||
<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="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 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">
|
||||
<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 v-else class="mb-4 flex flex-col gap-5 py-1.5">
|
||||
<div class="flex items-start justify-stretch gap-2 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 v-if="activity.type">{{ __(activity.type) }}</span>
|
||||
</div>
|
||||
<div v-else class="mb-4 flex flex-col gap-5 py-1.5">
|
||||
<div class="flex items-start justify-stretch gap-2 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 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
|
||||
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) }}
|
||||
</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
|
||||
v-if="activity.data.old_value"
|
||||
class="max-w-xs font-medium text-gray-800"
|
||||
@ -725,97 +780,33 @@
|
||||
</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="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"
|
||||
>
|
||||
<div class="flex items-start gap-1 text-gray-600">
|
||||
<div class="flex flex-1 items-center gap-1">
|
||||
<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"
|
||||
/>
|
||||
</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>
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
:name="activity.show_others ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4 text-gray-600"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -891,6 +882,7 @@
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import EmailContent from '@/components/EmailContent.vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
@ -1404,79 +1396,4 @@ nextTick(() => {
|
||||
.audio-control::-webkit-media-controls-panel {
|
||||
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>
|
||||
|
||||
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>
|
||||
<div
|
||||
<component
|
||||
:is="props.as || 'div'"
|
||||
ref="scrollableDiv"
|
||||
:style="`maskImage: ${maskStyle}`"
|
||||
@scroll="updateMaskStyle"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
as: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
maskLength: {
|
||||
type: Number,
|
||||
default: 30,
|
||||
|
||||
@ -15,11 +15,13 @@
|
||||
</template>
|
||||
</Button>
|
||||
<Tooltip v-if="filters?.size" :text="__('Clear all Filter')">
|
||||
<Button
|
||||
class="rounded-l-none border-l"
|
||||
icon="x"
|
||||
@click.stop="clearfilter(false)"
|
||||
/>
|
||||
<span>
|
||||
<Button
|
||||
class="rounded-l-none border-l"
|
||||
icon="x"
|
||||
@click.stop="clearfilter(false)"
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</template>
|
||||
<template #body="{ close }">
|
||||
|
||||
@ -84,7 +84,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<template
|
||||
v-if="callLog.type.label == 'Incoming' && !callLog.reference_docname"
|
||||
v-if="
|
||||
callLog.doc?.type.label == 'Incoming' && !callLog.doc?.reference_docname
|
||||
"
|
||||
#actions
|
||||
>
|
||||
<Button
|
||||
@ -116,11 +118,12 @@ import {
|
||||
createDocumentResource,
|
||||
call,
|
||||
} from 'frappe-ui'
|
||||
import { getCallLogDetail } from '@/utils/callLog'
|
||||
import { ref, computed, h, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
callLog: {
|
||||
name: {
|
||||
type: Object,
|
||||
default: {},
|
||||
},
|
||||
@ -130,60 +133,66 @@ const show = defineModel()
|
||||
const showNoteModal = ref(false)
|
||||
const router = useRouter()
|
||||
const callNoteDoc = ref(null)
|
||||
const callLog = ref({})
|
||||
|
||||
const detailFields = computed(() => {
|
||||
if (!callLog.value.doc) return []
|
||||
let details = [
|
||||
{
|
||||
icon: h(FeatherIcon, {
|
||||
name: props.callLog.type.icon,
|
||||
name: callLog.value.doc.type.icon,
|
||||
class: 'h-3.5 w-3.5',
|
||||
}),
|
||||
name: 'type',
|
||||
value: props.callLog.type.label + ' Call',
|
||||
value: callLog.value.doc.type.label + ' Call',
|
||||
},
|
||||
{
|
||||
icon: ContactsIcon,
|
||||
name: 'receiver',
|
||||
value: {
|
||||
receiver: props.callLog.receiver,
|
||||
caller: props.callLog.caller,
|
||||
receiver: callLog.value.doc.receiver,
|
||||
caller: callLog.value.doc.caller,
|
||||
},
|
||||
},
|
||||
{
|
||||
icon:
|
||||
props.callLog.reference_doctype == 'CRM Lead' ? LeadsIcon : Dealsicon,
|
||||
callLog.value.doc.reference_doctype == 'CRM Lead'
|
||||
? LeadsIcon
|
||||
: Dealsicon,
|
||||
name: 'reference_doctype',
|
||||
value: props.callLog.reference_doctype == 'CRM Lead' ? 'Lead' : 'Deal',
|
||||
value:
|
||||
callLog.value.doc.reference_doctype == 'CRM Lead' ? 'Lead' : 'Deal',
|
||||
link: () => {
|
||||
if (props.callLog.reference_doctype == 'CRM Lead') {
|
||||
if (callLog.value.doc.reference_doctype == 'CRM Lead') {
|
||||
router.push({
|
||||
name: 'Lead',
|
||||
params: { leadId: props.callLog.reference_docname },
|
||||
params: { leadId: callLog.value.doc.reference_docname },
|
||||
})
|
||||
} else {
|
||||
router.push({
|
||||
name: 'Deal',
|
||||
params: { dealId: props.callLog.reference_docname },
|
||||
params: { dealId: callLog.value.doc.reference_docname },
|
||||
})
|
||||
}
|
||||
},
|
||||
condition: () => callLog.value.doc.reference_docname,
|
||||
},
|
||||
{
|
||||
icon: CalendarIcon,
|
||||
name: 'creation',
|
||||
value: props.callLog.creation.label,
|
||||
tooltip: props.callLog.creation.label,
|
||||
value: callLog.value.doc.creation.label,
|
||||
tooltip: callLog.value.doc.creation.label,
|
||||
},
|
||||
{
|
||||
icon: DurationIcon,
|
||||
name: 'duration',
|
||||
value: props.callLog.duration.label,
|
||||
value: callLog.value.doc.duration.label,
|
||||
},
|
||||
{
|
||||
icon: CheckCircleIcon,
|
||||
name: 'status',
|
||||
value: props.callLog.status.label,
|
||||
color: props.callLog.status.color,
|
||||
value: callLog.value.doc.status.label,
|
||||
color: callLog.value.doc.status.color,
|
||||
},
|
||||
{
|
||||
icon: h(FeatherIcon, {
|
||||
@ -191,7 +200,7 @@ const detailFields = computed(() => {
|
||||
class: 'h-4 w-4 mt-2',
|
||||
}),
|
||||
name: 'recording_url',
|
||||
value: props.callLog.recording_url,
|
||||
value: callLog.value.doc.recording_url,
|
||||
},
|
||||
{
|
||||
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() {
|
||||
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) => {
|
||||
if (d) {
|
||||
router.push({ name: 'Lead', params: { leadId: d } })
|
||||
@ -215,12 +226,45 @@ function createLead() {
|
||||
|
||||
watch(show, (val) => {
|
||||
if (val) {
|
||||
callNoteDoc.value = createDocumentResource({
|
||||
doctype: 'FCRM Note',
|
||||
name: props.callLog.note,
|
||||
fields: ['title', 'content'],
|
||||
cache: ['note', props.callLog.note],
|
||||
callLog.value = createDocumentResource({
|
||||
doctype: 'CRM Call Log',
|
||||
name: props.name,
|
||||
fields: [
|
||||
'name',
|
||||
'caller',
|
||||
'receiver',
|
||||
'duration',
|
||||
'type',
|
||||
'status',
|
||||
'from',
|
||||
'to',
|
||||
'note',
|
||||
'recording_url',
|
||||
'reference_doctype',
|
||||
'reference_docname',
|
||||
'creation',
|
||||
],
|
||||
cache: ['call_log', props.name],
|
||||
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"
|
||||
class="m-1 min-w-36"
|
||||
>
|
||||
<FormControl
|
||||
v-if="filter.type == 'Check'"
|
||||
:label="filter.label"
|
||||
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)"
|
||||
<QuickFilterField
|
||||
:filter="filter"
|
||||
@applyQuickFilter="(f, v) => applyQuickFilter(f, v)"
|
||||
/>
|
||||
</div>
|
||||
</FadedScrollableDiv>
|
||||
@ -281,10 +246,8 @@
|
||||
</Dialog>
|
||||
</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 DetailsIcon from '@/components/Icons/DetailsIcon.vue'
|
||||
import QuickFilterField from '@/components/QuickFilterField.vue'
|
||||
import RefreshIcon from '@/components/Icons/RefreshIcon.vue'
|
||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
import DuplicateIcon from '@/components/Icons/DuplicateIcon.vue'
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
<CallLogModal
|
||||
v-model="showCallLogModal"
|
||||
v-model:reloadCallLogs="callLogs"
|
||||
:callLog="callLog"
|
||||
:name="selectedCallLog"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@ -64,20 +64,10 @@ import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import ViewControls from '@/components/ViewControls.vue'
|
||||
import CallLogsListView from '@/components/ListViews/CallLogsListView.vue'
|
||||
import CallLogModal from '@/components/Modals/CallLogModal.vue'
|
||||
import {
|
||||
secondsToDuration,
|
||||
dateFormat,
|
||||
dateTooltipFormat,
|
||||
timeAgo,
|
||||
} from '@/utils'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { contactsStore } from '@/stores/contacts'
|
||||
import { getCallLogDetail } from '@/utils/callLog'
|
||||
import { Breadcrumbs } from 'frappe-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const { getUser } = usersStore()
|
||||
const { getContact, getLeadContact } = contactsStore()
|
||||
|
||||
const breadcrumbs = [{ label: __('Call Logs'), route: { name: 'Call Logs' } }]
|
||||
|
||||
const callLogsListView = ref(null)
|
||||
@ -94,119 +84,17 @@ const rows = computed(() => {
|
||||
return callLogs.value?.data.data.map((callLog) => {
|
||||
let _rows = {}
|
||||
callLogs.value?.data.rows.forEach((row) => {
|
||||
_rows[row] = callLog[row]
|
||||
|
||||
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])),
|
||||
}
|
||||
}
|
||||
_rows[row] = getCallLogDetail(row, callLog)
|
||||
})
|
||||
return _rows
|
||||
})
|
||||
})
|
||||
|
||||
const showCallLogModal = ref(false)
|
||||
|
||||
const callLog = ref({
|
||||
name: '',
|
||||
caller: '',
|
||||
receiver: '',
|
||||
duration: '',
|
||||
type: '',
|
||||
status: '',
|
||||
from: '',
|
||||
to: '',
|
||||
note: '',
|
||||
recording_url: '',
|
||||
reference_doctype: '',
|
||||
reference_docname: '',
|
||||
creation: '',
|
||||
})
|
||||
const selectedCallLog = ref(null)
|
||||
|
||||
function showCallLog(name) {
|
||||
let d = rows.value?.find((row) => row.name === 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,
|
||||
}
|
||||
selectedCallLog.value = name
|
||||
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>
|
||||
|
||||
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 vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import path from 'path'
|
||||
import frappeui from 'frappe-ui/vite'
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
@ -13,6 +14,7 @@ export default defineConfig({
|
||||
propsDestructure: true,
|
||||
},
|
||||
}),
|
||||
vueJsx(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
devOptions: {
|
||||
|
||||
154
yarn.lock
154
yarn.lock
@ -29,7 +29,7 @@
|
||||
jsonpointer "^5.0.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"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.6.tgz#ab88da19344445c3d8889af2216606d3329f3ef2"
|
||||
integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==
|
||||
@ -42,7 +42,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.6.tgz#b3600217688cabb26e25f8e467019e66d71b7ae2"
|
||||
integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==
|
||||
|
||||
"@babel/core@^7.11.1":
|
||||
"@babel/core@^7.11.1", "@babel/core@^7.23.3":
|
||||
version "7.24.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.6.tgz#8650e0e4b03589ebe886c4e4a60398db0a7ec787"
|
||||
integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==
|
||||
@ -167,6 +167,13 @@
|
||||
dependencies:
|
||||
"@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":
|
||||
version "7.24.6"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.6.tgz#5e030f440c3c6c78d195528c3b688b101a365328"
|
||||
integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==
|
||||
@ -381,6 +388,13 @@
|
||||
dependencies:
|
||||
"@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":
|
||||
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"
|
||||
@ -437,6 +451,13 @@
|
||||
dependencies:
|
||||
"@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":
|
||||
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"
|
||||
@ -801,6 +822,16 @@
|
||||
dependencies:
|
||||
"@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":
|
||||
version "7.24.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.6.tgz#c8ddca8fd5bacece837a4e27bd3b7ed64580d1a8"
|
||||
@ -940,7 +971,7 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/template@^7.24.6":
|
||||
"@babel/template@^7.23.9", "@babel/template@^7.24.6":
|
||||
version "7.24.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.6.tgz#048c347b2787a6072b24c723664c8d02b67a44f9"
|
||||
integrity sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==
|
||||
@ -949,7 +980,7 @@
|
||||
"@babel/parser" "^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"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.6.tgz#0941ec50cdeaeacad0911eb67ae227a4f8424edc"
|
||||
integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==
|
||||
@ -965,7 +996,7 @@
|
||||
debug "^4.3.1"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.6.tgz#ba4e1f59870c10dc2fa95a274ac4feec23b21912"
|
||||
integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==
|
||||
@ -1975,11 +2006,53 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597"
|
||||
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":
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz#057d2ded94c4e71b94e9814f92dcd9306317aa46"
|
||||
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":
|
||||
version "3.4.21"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.21.tgz#868b7085378fc24e58c9aed14c8d62110a62be1a"
|
||||
@ -1991,6 +2064,17 @@
|
||||
estree-walker "^2.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":
|
||||
version "3.4.21"
|
||||
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/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":
|
||||
version "3.4.21"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz#4af920dc31ab99e1ff5d152b5fe0ad12181145b2"
|
||||
@ -2014,6 +2106,21 @@
|
||||
postcss "^8.4.35"
|
||||
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":
|
||||
version "3.4.21"
|
||||
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/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":
|
||||
version "6.6.1"
|
||||
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"
|
||||
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":
|
||||
version "10.9.0"
|
||||
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"
|
||||
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:
|
||||
version "1.0.30001607"
|
||||
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:
|
||||
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:
|
||||
version "5.0.0"
|
||||
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:
|
||||
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:
|
||||
version "0.30.9"
|
||||
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"
|
||||
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:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user