changes
This commit is contained in:
@@ -183,7 +183,7 @@
|
|||||||
></Modal>
|
></Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { useNuxtApp } from "nuxt/app";
|
import { useNuxtApp } from "nuxt/app";
|
||||||
import { ref, computed, onMounted, watch, markRaw } from "vue";
|
import { ref, computed, onMounted, watch, markRaw } from "vue";
|
||||||
import EmailForm1 from "./forms/EmailForm1.vue";
|
import EmailForm1 from "./forms/EmailForm1.vue";
|
||||||
@@ -193,43 +193,12 @@ import MappingConfigurator from "~/components/marketing/email/MappingConfigurato
|
|||||||
import JobConfigurator from "~/components/marketing/email/JobConfigurator.vue";
|
import JobConfigurator from "~/components/marketing/email/JobConfigurator.vue";
|
||||||
|
|
||||||
const nuxtApp = useNuxtApp();
|
const nuxtApp = useNuxtApp();
|
||||||
const $snackbar = nuxtApp.$snackbar as (message?: string) => void;
|
const { $getdata, $snackbar, $deleteapi, $getEditRights } = useNuxtApp();
|
||||||
const $getdata = nuxtApp.$getdata as (name: string) => Promise<DataTemplate[]>;
|
const showmodal = ref();
|
||||||
const $deleteapi = nuxtApp.$deleteapi as (name: string, id: any) => Promise<DataTemplate[]>;
|
|
||||||
const $getEditRights = nuxtApp.$getEditRights as () => boolean;
|
|
||||||
const showmodal = ref<any>();
|
|
||||||
const activeTab = ref("content");
|
const activeTab = ref("content");
|
||||||
const defaultTemplate = "defaultTemplate";
|
const defaultTemplate = "defaultTemplate";
|
||||||
|
|
||||||
// Types
|
const formData = ref({
|
||||||
interface FormContent {
|
|
||||||
receiver: string;
|
|
||||||
subject: string;
|
|
||||||
content: string; // This is the email body
|
|
||||||
imageUrl: string | null;
|
|
||||||
linkUrl: string[];
|
|
||||||
textLinkUrl: string[];
|
|
||||||
keyword: (string | { keyword: string; value: string })[];
|
|
||||||
html: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FormData {
|
|
||||||
name?: string;
|
|
||||||
id?: number;
|
|
||||||
template: string;
|
|
||||||
content: FormContent;
|
|
||||||
mappings: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DataTemplate {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
content: Record<string, any> | string;
|
|
||||||
mappings?: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reactive state
|
|
||||||
const formData = ref<FormData>({
|
|
||||||
name: "",
|
name: "",
|
||||||
id: undefined,
|
id: undefined,
|
||||||
template: defaultTemplate,
|
template: defaultTemplate,
|
||||||
@@ -246,14 +215,14 @@ const formData = ref<FormData>({
|
|||||||
mappings: [],
|
mappings: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const dataTemplate = ref<DataTemplate[] | null>(null);
|
const dataTemplate = ref(null);
|
||||||
const dataTemplateSelected = ref<DataTemplate | null>(null);
|
const dataTemplateSelected = ref(null);
|
||||||
const originalLoadedTemplate = ref<DataTemplate | null>(null); // For diffing on save
|
const originalLoadedTemplate = ref(null); // For diffing on save
|
||||||
const editMode = ref(false);
|
const editMode = ref(false);
|
||||||
const formKey = ref(0);
|
const formKey = ref(0);
|
||||||
const selectedValue = ref(defaultTemplate);
|
const selectedValue = ref(defaultTemplate);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const selectedTemplateId = ref<number | null>(null);
|
const selectedTemplateId = ref(null);
|
||||||
const showDeleteDialog = ref(false);
|
const showDeleteDialog = ref(false);
|
||||||
|
|
||||||
// Computed properties
|
// Computed properties
|
||||||
@@ -287,7 +256,7 @@ const currentTemplateComponent = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const handleChangeData = (data: Partial<FormData>) => {
|
const handleChangeData = (data) => {
|
||||||
formData.value.name = data.name || formData.value.name;
|
formData.value.name = data.name || formData.value.name;
|
||||||
formData.value.id = data.id || formData.value.id;
|
formData.value.id = data.id || formData.value.id;
|
||||||
if (data.content) {
|
if (data.content) {
|
||||||
@@ -295,7 +264,7 @@ const handleChangeData = (data: Partial<FormData>) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateMappings = (newMappings: any[]) => {
|
const updateMappings = (newMappings) => {
|
||||||
formData.value.mappings = newMappings;
|
formData.value.mappings = newMappings;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -347,8 +316,8 @@ const handleTemplateChange = async () => {
|
|||||||
let imageUrl = null;
|
let imageUrl = null;
|
||||||
let linkUrl = [""];
|
let linkUrl = [""];
|
||||||
let textLinkUrl = [""];
|
let textLinkUrl = [""];
|
||||||
let keyword: any[] = [{ keyword: "", value: "" }];
|
let keyword = [{ keyword: "", value: "" }];
|
||||||
let mappings: any[] = [];
|
let mappings = [];
|
||||||
|
|
||||||
if (typeof tplContent === "string") {
|
if (typeof tplContent === "string") {
|
||||||
emailBody = tplContent;
|
emailBody = tplContent;
|
||||||
@@ -419,12 +388,8 @@ const handleOpenModal = () => {
|
|||||||
|
|
||||||
if (editMode.value && originalLoadedTemplate.value) {
|
if (editMode.value && originalLoadedTemplate.value) {
|
||||||
// EDIT MODE: Calculate a patch of changed data
|
// EDIT MODE: Calculate a patch of changed data
|
||||||
const patchPayload: {
|
const patchPayload = {
|
||||||
id: number;
|
id: formData.value.id,
|
||||||
name?: string;
|
|
||||||
content?: Record<string, any>;
|
|
||||||
} = {
|
|
||||||
id: formData.value.id!,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. Check if name has changed
|
// 1. Check if name has changed
|
||||||
|
|||||||
@@ -234,31 +234,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { imageUrl } from "~/components/marketing/email/Email.utils";
|
import { imageUrl } from "~/components/marketing/email/Email.utils";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
interface KeywordItem {
|
const props = defineProps({
|
||||||
keyword: string;
|
content: Object,
|
||||||
value: string;
|
previewMode: Boolean,
|
||||||
}
|
|
||||||
|
|
||||||
interface Template1Content {
|
|
||||||
subject?: string;
|
|
||||||
message?: string;
|
|
||||||
imageUrl?: string | null;
|
|
||||||
linkUrl?: string[];
|
|
||||||
textLinkUrl?: string[];
|
|
||||||
keyword?: KeywordItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
content: Template1Content;
|
|
||||||
previewMode?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
previewMode: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasValidLinks = computed(() => {
|
const hasValidLinks = computed(() => {
|
||||||
@@ -269,7 +251,7 @@ const hasValidImage = computed(() => {
|
|||||||
return props.content.imageUrl && props.content.imageUrl.trim() !== "";
|
return props.content.imageUrl && props.content.imageUrl.trim() !== "";
|
||||||
});
|
});
|
||||||
|
|
||||||
const replaceKeywords = (content: string): string => {
|
const replaceKeywords = (contentstring) => {
|
||||||
if (!content) return "";
|
if (!content) return "";
|
||||||
|
|
||||||
let replacedContent = content;
|
let replacedContent = content;
|
||||||
@@ -293,7 +275,7 @@ const validLinks = computed(() => {
|
|||||||
return props.content.linkUrl.filter((link) => link && link.trim() !== "");
|
return props.content.linkUrl.filter((link) => link && link.trim() !== "");
|
||||||
});
|
});
|
||||||
|
|
||||||
const getLinkText = (index: number): string => {
|
const getLinkText = (index) => {
|
||||||
return props.content.textLinkUrl && props.content.textLinkUrl[index] && props.content.textLinkUrl[index].trim() !== ""
|
return props.content.textLinkUrl && props.content.textLinkUrl[index] && props.content.textLinkUrl[index].trim() !== ""
|
||||||
? props.content.textLinkUrl[index]
|
? props.content.textLinkUrl[index]
|
||||||
: `Link ${index + 1}`;
|
: `Link ${index + 1}`;
|
||||||
@@ -311,7 +293,7 @@ const styles = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content-padding, .company-padding {
|
.content-padding, .company-padding {
|
||||||
padding: 0px 25px 20px;
|
padding: 0px 25px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-content p {
|
.message-content p {
|
||||||
@@ -407,16 +389,16 @@ const styles = `
|
|||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.greeting-padding{
|
.greeting-padding{
|
||||||
padding: 20px 0 0 0 !important;
|
padding: 20px 0 0 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-padding {
|
.content-padding {
|
||||||
padding: 20px 0px !important;
|
padding: 20px 0px !important;
|
||||||
}
|
}
|
||||||
.company-padding {
|
.company-padding {
|
||||||
padding: 0px 0px 20px 0px !important;
|
padding: 0px 0px 20px 0px !important;
|
||||||
}
|
}
|
||||||
.company-info {
|
.company-info {
|
||||||
padding: 20px 10px !important;
|
padding: 20px 10px !important;
|
||||||
|
|||||||
@@ -248,8 +248,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import axios from "axios";
|
|
||||||
import { apiUrl } from "~/components/marketing/email/Email.utils";
|
|
||||||
import type dayjs from "dayjs";
|
import type dayjs from "dayjs";
|
||||||
|
|
||||||
interface EmailSent {
|
interface EmailSent {
|
||||||
@@ -263,6 +261,7 @@ interface EmailSent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nuxtApp = useNuxtApp();
|
const nuxtApp = useNuxtApp();
|
||||||
|
const $getdata = nuxtApp.$getdata as any;
|
||||||
const $snackbar = nuxtApp.$snackbar as (message?: string) => void;
|
const $snackbar = nuxtApp.$snackbar as (message?: string) => void;
|
||||||
const $dayjs = nuxtApp.$dayjs as (date?: string | Date) => ReturnType<typeof dayjs>;
|
const $dayjs = nuxtApp.$dayjs as (date?: string | Date) => ReturnType<typeof dayjs>;
|
||||||
|
|
||||||
@@ -311,10 +310,7 @@ const stripHtml = (html: string, maxLength: number = 1000): string => {
|
|||||||
const getDataEmailsSent = async () => {
|
const getDataEmailsSent = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${apiUrl}/Email_Sent/?sort=-id`);
|
emailsSent.value = await $getdata("Email_Sent", { params: { sort: "-id" } });
|
||||||
if (response.status === 200) {
|
|
||||||
emailsSent.value = response.data.rows || response.data || [];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching sent emails:", error);
|
console.error("Error fetching sent emails:", error);
|
||||||
$snackbar("Failed to load sent emails");
|
$snackbar("Failed to load sent emails");
|
||||||
|
|||||||
@@ -198,15 +198,17 @@ const handleSave = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateNew = async () => {
|
const handleCreateNew = async () => {
|
||||||
const postTemplateUrl = `${apiUrl}/Email_Template/`;
|
|
||||||
try {
|
try {
|
||||||
loadingCreate.value = true;
|
loadingCreate.value = true;
|
||||||
const newTemplateData = {
|
const newTemplateData = {
|
||||||
...props.data,
|
...props.data,
|
||||||
name: templateName.value,
|
name: templateName.value,
|
||||||
};
|
};
|
||||||
const response = await axios.post(postTemplateUrl, newTemplateData);
|
const res = await $insertapi("Email_Template", {
|
||||||
if (response.status === 201 || response.status === 200) {
|
data: newTemplateData,
|
||||||
|
notify: false,
|
||||||
|
});
|
||||||
|
if (res !== "error") {
|
||||||
$snackbar("Template created successfully");
|
$snackbar("Template created successfully");
|
||||||
if (props.onClose) props.onClose();
|
if (props.onClose) props.onClose();
|
||||||
emit("close");
|
emit("close");
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a
|
|
||||||
class="has-text-link"
|
|
||||||
@click="open"
|
|
||||||
>{{ row["code"] }}</a
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import { useStore } from "~/stores/index";
|
|
||||||
const store = useStore();
|
|
||||||
const emit = defineEmits(["clickevent"]);
|
|
||||||
const props = defineProps({
|
|
||||||
row: Object,
|
|
||||||
});
|
|
||||||
function open() {
|
|
||||||
emit("clickevent", {
|
|
||||||
name: "dataevent",
|
|
||||||
data: {
|
|
||||||
modal: {
|
|
||||||
title: store.lang === "en" ? "Application" : "Đơn vay",
|
|
||||||
height: "500px",
|
|
||||||
width: "65%",
|
|
||||||
component: "application/ApplicationView",
|
|
||||||
vbind: { row: props.row },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a
|
|
||||||
class="has-text-link"
|
|
||||||
@click="open"
|
|
||||||
>{{ row["customer__code"] }}</a
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import { useStore } from "~/stores/index";
|
|
||||||
const store = useStore();
|
|
||||||
const emit = defineEmits(["clickevent"]);
|
|
||||||
const props = defineProps({
|
|
||||||
row: Object,
|
|
||||||
});
|
|
||||||
function open() {
|
|
||||||
emit("clickevent", {
|
|
||||||
name: "dataevent",
|
|
||||||
data: {
|
|
||||||
modal: {
|
|
||||||
title: store.lang === "en" ? "Customer" : "Khách hàng",
|
|
||||||
height: "500px",
|
|
||||||
width: "60%",
|
|
||||||
component: "customer/CustomerView",
|
|
||||||
vbind: { row: props.row },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -23,8 +23,6 @@ import CountWithAdd from "~/components/common/CountWithAdd.vue";
|
|||||||
|
|
||||||
// menu
|
// menu
|
||||||
import MenuAction from "~/components/menu/MenuAction.vue";
|
import MenuAction from "~/components/menu/MenuAction.vue";
|
||||||
import MenuApp from "~/components/menu/MenuApp.vue";
|
|
||||||
import MenuCust from "~/components/menu/MenuCust.vue";
|
|
||||||
import MenuPhone from "~/components/menu/MenuPhone.vue";
|
import MenuPhone from "~/components/menu/MenuPhone.vue";
|
||||||
import MenuParam from "~/components/menu/MenuParam.vue";
|
import MenuParam from "~/components/menu/MenuParam.vue";
|
||||||
import MenuAdd from "~/components/menu/MenuAdd.vue";
|
import MenuAdd from "~/components/menu/MenuAdd.vue";
|
||||||
@@ -107,8 +105,6 @@ const components = {
|
|||||||
MenuParam,
|
MenuParam,
|
||||||
FormatNumber,
|
FormatNumber,
|
||||||
FormatDate,
|
FormatDate,
|
||||||
MenuApp,
|
|
||||||
MenuCust,
|
|
||||||
MenuAdd,
|
MenuAdd,
|
||||||
MenuNote,
|
MenuNote,
|
||||||
ImageLayout,
|
ImageLayout,
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
export interface EmailContextType {
|
|
||||||
selectedEmails: string;
|
|
||||||
selectedName: string;
|
|
||||||
selectedId: string;
|
|
||||||
setSelectedEmails: (emails: string) => void;
|
|
||||||
setSelectedName: (name: string) => void;
|
|
||||||
setSelectedId: (id: string) => void;
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
export interface FormData {
|
|
||||||
id: number | undefined;
|
|
||||||
name: string;
|
|
||||||
template: string;
|
|
||||||
content: {
|
|
||||||
receiver: string;
|
|
||||||
subject: string;
|
|
||||||
content: string;
|
|
||||||
imageUrl: string | null;
|
|
||||||
linkUrl: string[];
|
|
||||||
textLinkUrl: string[];
|
|
||||||
keyword: Array<string | { keyword: string; value: string }>;
|
|
||||||
html: string;
|
|
||||||
};
|
|
||||||
// emails: string;
|
|
||||||
// subject: string;
|
|
||||||
// message: string;
|
|
||||||
// template: string;
|
|
||||||
// company?: string;
|
|
||||||
// phone?: string;
|
|
||||||
// imageUrl?: string | null;
|
|
||||||
// linkUrl?: string[];
|
|
||||||
// keyWords?: string[] | { keyword: string; value: string }[];
|
|
||||||
// textLinkUrl?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmailFormProps {
|
|
||||||
onDataChange: (data: FormData) => void;
|
|
||||||
initialData?: FormData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ModalType = "none" | "save-list" | "open-list" | "save-template" | "open-template";
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
export interface EmailSent {
|
|
||||||
id: string;
|
|
||||||
receiver: string;
|
|
||||||
subject: string;
|
|
||||||
content: string;
|
|
||||||
status: number;
|
|
||||||
create_time: string;
|
|
||||||
update_time: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EmailSentStatus = "pending" | "success" | "error" | "schedule";
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export interface ModalProps {
|
|
||||||
active: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
height?: string | number;
|
|
||||||
width?: string | number;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export interface openListGmailProps {
|
|
||||||
emails: string;
|
|
||||||
dataEmail: { id: string; email: string; name: string }[];
|
|
||||||
onClose: () => void;
|
|
||||||
onEdit?: (id: string, name: string, emails: string) => void;
|
|
||||||
loading?: boolean;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
export interface SaveListGmailProps {
|
|
||||||
emails: string;
|
|
||||||
name?: string;
|
|
||||||
id?: string;
|
|
||||||
onClose?: () => void;
|
|
||||||
onSuccess?: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DataEmail {
|
|
||||||
email: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
export interface saveListTemplateProps {
|
|
||||||
name?: string;
|
|
||||||
content?: {
|
|
||||||
receiver: string;
|
|
||||||
subject: string;
|
|
||||||
content: string;
|
|
||||||
imageUrl: string | null;
|
|
||||||
linkUrl: string[] | string;
|
|
||||||
textLinkUrl: string[] | string;
|
|
||||||
keyword: Array<string | { keyword: string; value: string }>;
|
|
||||||
html: string;
|
|
||||||
};
|
|
||||||
editMode?: boolean;
|
|
||||||
id?: number;
|
|
||||||
onClose?: () => void;
|
|
||||||
onSuccess?: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DataTemplate {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
content: {
|
|
||||||
receiver: string;
|
|
||||||
subject: string;
|
|
||||||
content: string;
|
|
||||||
imageUrl: string | null;
|
|
||||||
linkUrl: string[] | string;
|
|
||||||
textLinkUrl: string[] | string;
|
|
||||||
keyword: Array<string | { keyword: string; value: string }>;
|
|
||||||
html: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
export interface Template1Props {
|
|
||||||
content: {
|
|
||||||
subject?: string;
|
|
||||||
message?: string;
|
|
||||||
imageUrl?: string | null;
|
|
||||||
linkUrl?: string[];
|
|
||||||
textLinkUrl?: string[];
|
|
||||||
keyword: Array<string | { keyword: string; value: string }>;
|
|
||||||
};
|
|
||||||
previewMode?: boolean;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user