feat: List UI and basic select source type UI
This commit is contained in:
parent
7d2ccc2e58
commit
5e5929ef40
@ -36,8 +36,8 @@ class LeadSyncSource(Document):
|
||||
|
||||
def before_save(self):
|
||||
if self.type == "Facebook" and self.access_token:
|
||||
fetch_and_store_pages_from_facebook(self.access_token)
|
||||
pass
|
||||
# fetch_and_store_pages_from_facebook(self.access_token)
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def sync_leads(self):
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
@click="handleSelect(s)"
|
||||
>
|
||||
<EmailProviderIcon
|
||||
:service-name="s.name"
|
||||
:label="s.name"
|
||||
:logo="s.icon"
|
||||
:selected="selectedService?.name === s?.name"
|
||||
/>
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<div class="w-fit">
|
||||
<EmailProviderIcon
|
||||
:logo="emailIcon[accountData.service]"
|
||||
:service-name="accountData.service"
|
||||
:label="accountData.service"
|
||||
/>
|
||||
</div>
|
||||
<!-- banner for setting up email account -->
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
>
|
||||
<img :src="logo" class="w-4 h-4" />
|
||||
</div>
|
||||
<p v-if="serviceName" class="text-xs text-center text-ink-gray-6 mt-2">
|
||||
{{ serviceName }}
|
||||
<p v-if="label" class="text-xs text-center text-ink-gray-6 mt-2">
|
||||
{{ label }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
@ -16,7 +16,7 @@ defineProps({
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
serviceName: {
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
<template>
|
||||
<NewLeadSyncSource
|
||||
v-if="step === 'new-source'"
|
||||
:templateData="source"
|
||||
@updateStep="updateStep"
|
||||
/>
|
||||
<LeadSyncSources
|
||||
v-else-if="step === 'source-list'"
|
||||
@updateStep="updateStep"
|
||||
/>
|
||||
<EditLeadSyncSource
|
||||
v-else-if="step === 'edit-source'"
|
||||
:templateData="source"
|
||||
@updateStep="updateStep"
|
||||
/>
|
||||
<div class="flex-1 p-6">
|
||||
<NewLeadSyncSource
|
||||
v-if="step === 'new-source'"
|
||||
:templateData="source"
|
||||
@updateStep="updateStep"
|
||||
/>
|
||||
<LeadSyncSources
|
||||
v-else-if="step === 'source-list'"
|
||||
@updateStep="updateStep"
|
||||
/>
|
||||
<EditLeadSyncSource
|
||||
v-else-if="step === 'edit-source'"
|
||||
:templateData="source"
|
||||
@updateStep="updateStep"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
@ -1,8 +1,245 @@
|
||||
<template>
|
||||
<pre>{{ JSON.stringify(sources, null, 2) }}</pre>
|
||||
<!-- <pre>{{ JSON.stringify(sources, null, 2) }}</pre> -->
|
||||
|
||||
<div class="flex h-full flex-col gap-6 text-ink-gray-8">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between px-2 pt-2">
|
||||
<div class="flex flex-col gap-1 w-9/12">
|
||||
<h2 class="flex gap-2 text-xl font-semibold leading-none h-5">
|
||||
{{ __('Lead Sources') }}
|
||||
</h2>
|
||||
<p class="text-p-base text-ink-gray-6">
|
||||
{{
|
||||
__(
|
||||
'Add, edit, and manage sources for automatic lead syncing to your CRM',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex item-center space-x-2 w-3/12 justify-end">
|
||||
<Button
|
||||
:label="__('New')"
|
||||
icon-left="plus"
|
||||
variant="solid"
|
||||
@click="emit('updateStep', 'new-source')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- loading state -->
|
||||
<div
|
||||
v-if="sources.loading"
|
||||
class="flex mt-28 justify-between w-full h-full"
|
||||
>
|
||||
<Button
|
||||
:loading="sources.loading"
|
||||
variant="ghost"
|
||||
class="w-full"
|
||||
size="2xl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div
|
||||
v-if="!sources.loading && !sources.data?.length"
|
||||
class="flex justify-between w-full h-full"
|
||||
>
|
||||
<div
|
||||
class="text-ink-gray-4 border border-dashed rounded w-full flex items-center justify-center"
|
||||
>
|
||||
{{ __('No email sources found') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lead source list -->
|
||||
<div
|
||||
class="flex flex-col overflow-hidden"
|
||||
v-if="!sources.loading && sources.data?.length"
|
||||
>
|
||||
<div
|
||||
v-if="sources.data?.length > 10"
|
||||
class="flex items-center justify-between mb-4 px-2 pt-0.5"
|
||||
>
|
||||
<TextInput
|
||||
ref="searchRef"
|
||||
v-model="search"
|
||||
:placeholder="__('Search template')"
|
||||
class="w-1/3"
|
||||
:debounce="300"
|
||||
>
|
||||
<template #prefix>
|
||||
<FeatherIcon name="search" class="h-4 w-4 text-ink-gray-6" />
|
||||
</template>
|
||||
</TextInput>
|
||||
<FormControl
|
||||
type="select"
|
||||
v-model="currentDoctype"
|
||||
:options="[
|
||||
{ label: __('All'), value: 'All' },
|
||||
{ label: __('Lead'), value: 'CRM Lead' },
|
||||
{ label: __('Deal'), value: 'CRM Deal' },
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center py-2 px-4 text-sm text-ink-gray-5">
|
||||
<div class="w-4/6">{{ __('Source') }}</div>
|
||||
<div class="w-2/6">{{ __('Enabled') }}</div>
|
||||
</div>
|
||||
<div class="h-px border-t mx-4 border-outline-gray-modals" />
|
||||
<ul class="overflow-y-auto px-2">
|
||||
<template v-for="(source, i) in sourcesList" :key="source.name">
|
||||
<li
|
||||
class="flex items-center justify-between p-3 cursor-pointer hover:bg-surface-menu-bar rounded"
|
||||
@click="() => emit('updateStep', 'edit-source', { ...source })"
|
||||
>
|
||||
<div class="flex flex-col w-4/6 pr-5">
|
||||
<div class="text-p-base font-medium text-ink-gray-7 truncate">
|
||||
{{ source.type }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between w-2/6">
|
||||
<Switch
|
||||
size="sm"
|
||||
v-model="source.enabled"
|
||||
@update:model-value="toggleLeadSyncSourceEnabled(source)"
|
||||
@click.stop
|
||||
/>
|
||||
<Dropdown
|
||||
class=""
|
||||
:options="getDropdownOptions(source)"
|
||||
placement="right"
|
||||
:button="{
|
||||
icon: 'more-horizontal',
|
||||
variant: 'ghost',
|
||||
onblur: (e) => {
|
||||
e.stopPropagation()
|
||||
confirmDelete = false
|
||||
},
|
||||
}"
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<div
|
||||
v-if="sourcesList.length !== i + 1"
|
||||
class="h-px border-t mx-2 border-outline-gray-modals"
|
||||
/>
|
||||
</template>
|
||||
<!-- Load More Button -->
|
||||
<div
|
||||
v-if="!sources.loading && sources.hasNextPage"
|
||||
class="flex justify-center"
|
||||
>
|
||||
|
||||
<Button
|
||||
class="mt-3.5 p-2"
|
||||
@click="() => sources.next()"
|
||||
:loading="sources.loading"
|
||||
:label="__('Load More')"
|
||||
icon-left="refresh-cw"
|
||||
/>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { inject } from 'vue';
|
||||
import {
|
||||
TextInput,
|
||||
FormControl,
|
||||
Switch,
|
||||
Dropdown,
|
||||
FeatherIcon,
|
||||
toast,
|
||||
} from "frappe-ui";
|
||||
import { ref, computed, inject } from "vue";
|
||||
|
||||
const sources = inject('sources')
|
||||
const emit = defineEmits(["updateStep"]);
|
||||
|
||||
const sources = inject("sources");
|
||||
|
||||
const search = ref("");
|
||||
// const currentDoctype = ref('All')
|
||||
const confirmDelete = ref(false);
|
||||
|
||||
const sourcesList = computed(() => {
|
||||
let list = sources.data || [];
|
||||
if (search.value) {
|
||||
list = list.filter(
|
||||
(source) =>
|
||||
source.name.toLowerCase().includes(search.value.toLowerCase()) ||
|
||||
source.subject.toLowerCase().includes(search.value.toLowerCase()),
|
||||
);
|
||||
}
|
||||
// if (currentDoctype.value !== 'All') {
|
||||
// list = list.filter(
|
||||
// (template) => source.reference_doctype === currentDoctype.value,
|
||||
// )
|
||||
// }
|
||||
return list;
|
||||
});
|
||||
|
||||
function toggleLeadSyncSourceEnabled(source) {
|
||||
sources.setValue.submit(
|
||||
{
|
||||
name: source.name,
|
||||
enabled: source.enabled ? 1 : 0,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(
|
||||
source.enabled
|
||||
? __('Source enabled successfully')
|
||||
: __('Source disabled successfully'),
|
||||
)
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.messages[0] || __('Failed to update source'))
|
||||
// Revert the change if there was an error
|
||||
source.enabled = !source.enabled
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
function deleteLeadSource(source) {
|
||||
confirmDelete.value = false;
|
||||
sources.delete.submit(source.name, {
|
||||
onSuccess: () => {
|
||||
toast.success(__("Lead Sync Source deleted successfully"));
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.messages[0] || __("Failed to delete Lead Sync Source"));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getDropdownOptions(source) {
|
||||
let options = [
|
||||
{
|
||||
label: __("Duplicate"),
|
||||
icon: "copy",
|
||||
onClick: () => emit("updateStep", "new-source", { ...source }),
|
||||
},
|
||||
{
|
||||
label: __("Delete"),
|
||||
icon: "trash-2",
|
||||
onClick: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
confirmDelete.value = true;
|
||||
},
|
||||
condition: () => !confirmDelete.value,
|
||||
},
|
||||
{
|
||||
label: __("Confirm Delete"),
|
||||
icon: "trash-2",
|
||||
theme: "red",
|
||||
onClick: () => deleteLeadSource(source),
|
||||
condition: () => confirmDelete.value,
|
||||
},
|
||||
];
|
||||
|
||||
return options;
|
||||
}
|
||||
</script>
|
||||
@ -1 +1,52 @@
|
||||
<template></template>
|
||||
<template>
|
||||
<div class="flex flex-col h-full gap-4">
|
||||
<div role="heading" aria-level="1" class="flex flex-col gap-1">
|
||||
<h2 class="text-xl font-semibold text-ink-gray-8">
|
||||
{{ __('Setup Lead Syncing Source') }}
|
||||
</h2>
|
||||
<p class="text-sm text-ink-gray-5">
|
||||
{{ __('Choose the type of source you want to configure.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- supported sources -->
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div
|
||||
v-for="s in supportedSourceTypes"
|
||||
:key="s.name"
|
||||
class="flex flex-col items-center gap-1 mt-4 w-[70px]"
|
||||
@click="handleSelect(s)"
|
||||
>
|
||||
<EmailProviderIcon
|
||||
:label="s.name"
|
||||
:logo="s.icon"
|
||||
:selected="selectedSourceType?.name === s?.name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { createResource, toast } from 'frappe-ui'
|
||||
import CircleAlert from '~icons/lucide/circle-alert'
|
||||
// import {
|
||||
// customProviderFields,
|
||||
// popularProviderFields,
|
||||
// services,
|
||||
// validateInputs,
|
||||
// incomingOutgoingFields,
|
||||
// } from './emailConfig'
|
||||
import { supportedSourceTypes } from './leadSyncSourceConfig'
|
||||
import EmailProviderIcon from '../EmailProviderIcon.vue'
|
||||
|
||||
|
||||
const selectedSourceType = ref(null)
|
||||
|
||||
function handleSelect(sourceType) {
|
||||
selectedSourceType.value = sourceType
|
||||
// state.service = service.name
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,11 @@
|
||||
import LogoFacebook from '@/images/facebook.png'
|
||||
|
||||
|
||||
export const supportedSourceTypes = [
|
||||
{
|
||||
name: 'Facebook',
|
||||
icon: LogoFacebook,
|
||||
link: 'https://support.google.com/accounts/answer/185833',
|
||||
custom: false,
|
||||
}
|
||||
]
|
||||
BIN
frontend/src/images/facebook.png
Normal file
BIN
frontend/src/images/facebook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Loading…
x
Reference in New Issue
Block a user