Initial commit

This commit is contained in:
Viet An
2026-03-02 09:45:33 +07:00
commit d17a9e2588
415 changed files with 92113 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
<template>
<div class="box">
<div class="level">
<div class="level-left">
<h5 class="title is-5">Automation Jobs</h5>
</div>
<div class="level-right">
<button class="button is-primary" @click="openNewJobForm">
<SvgIcon v-bind="{ name: 'add4.svg', type: 'white', size: 18 }" />
<span class="ml-2">Add New Job</span>
</button>
</div>
</div>
<div v-if="isLoading" class="has-text-centered">
<p>Loading jobs...</p>
</div>
<div v-else-if="jobs.length === 0" class="has-text-centered">
<p>No automation jobs found for this template.</p>
</div>
<div v-else>
<div v-for="job in jobs" :key="job.id" class="mb-4 p-3 border">
<div class="level">
<div class="level-left">
<div>
<p class="has-text-weight-bold">{{ job.name }}</p>
<p class="is-size-7">Model: {{ job.model_name }}</p>
<p class="is-size-7">
Triggers:
<span v-if="job.trigger_on_create" class="tag is-info is-light mr-1">On Create</span>
<span v-if="job.trigger_on_update" class="tag is-warning is-light">On Update</span>
</p>
</div>
</div>
<div class="level-right">
<div class="field has-addons">
<div class="control">
<button class="button is-small" :class="{'is-success': job.active, 'is-light': !job.active}" @click="toggleJobStatus(job)">
{{ job.active ? 'Active' : 'Inactive' }}
</button>
</div>
<div class="control">
<button class="button is-small" @click="openEditJobForm(job)">
<SvgIcon v-bind="{ name: 'pen1.svg', type: 'primary', size: 18 }" />
</button>
</div>
<div class="control">
<button class="button is-danger is-small" @click="confirmDelete(job)">
<SvgIcon v-bind="{ name: 'trash.svg', type: 'white', size: 18 }" />
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Job Form Modal -->
<div v-if="showForm" class="modal is-active">
<div class="modal-background" @click="closeForm"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{{ isEditing ? 'Edit Job' : 'Create New Job' }}</p>
<button class="delete" @click="closeForm"></button>
</header>
<section class="modal-card-body">
<div class="field">
<label class="label">Job Name</label>
<div class="control">
<input class="input" type="text" v-model="jobForm.name" placeholder="e.g., Notify on Transaction Update">
</div>
</div>
<div class="field">
<label class="label">Model Name</label>
<div class="control">
<input class="input" type="text" v-model="jobForm.model_name" placeholder="e.g., app.Transaction_Detail">
</div>
</div>
<div class="field">
<label class="label">Triggers</label>
<div class="control">
<label class="checkbox mr-4">
<input type="checkbox" v-model="jobForm.trigger_on_create">
On Create
</label>
<label class="checkbox">
<input type="checkbox" v-model="jobForm.trigger_on_update">
On Update
</label>
</div>
</div>
<div class="field">
<label class="label">Status</label>
<div class="control">
<label class="checkbox">
<input type="checkbox" v-model="jobForm.active">
Active
</label>
</div>
</div>
</section>
<footer class="modal-card-foot">
<button class="button is-success" @click="saveJob" :disabled="isSaving">
{{ isSaving ? 'Saving...' : 'Save' }}
</button>
<button class="button" @click="closeForm">Cancel</button>
</footer>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue';
import axios from 'axios';
import { useNuxtApp } from 'nuxt/app';
import { apiUrl, putApiUrl } from '@/components/marketing/email/Email.utils';
const props = defineProps({
templateId: {
type: [Number, null],
default: null,
},
});
const nuxtApp = useNuxtApp();
const $snackbar = nuxtApp.$snackbar as (message: string) => void;
const jobs = ref<any[]>([]);
const isLoading = ref(false);
const isSaving = ref(false);
const showForm = ref(false);
const isEditing = ref(false);
const defaultJobForm = () => ({
id: null,
name: '',
model_name: 'app.Transaction_Detail',
template: props.templateId,
trigger_on_create: false,
trigger_on_update: false,
active: true,
});
const jobForm = ref(defaultJobForm());
const fetchJobs = async () => {
if (!props.templateId) {
jobs.value = [];
return;
}
isLoading.value = true;
try {
const response = await axios.get(`${apiUrl}/Email_Job/`, {
params: { template_id: props.templateId },
});
jobs.value = response.data.rows || [];
} catch (error) {
console.error("Error fetching email jobs:", error);
$snackbar(`Error fetching jobs`);
} finally {
isLoading.value = false;
}
};
watch(() => props.templateId, fetchJobs, { immediate: true });
const openNewJobForm = () => {
if (!props.templateId) {
$snackbar(`Please save the template first.`);
return;
}
isEditing.value = false;
jobForm.value = defaultJobForm();
showForm.value = true;
};
const openEditJobForm = (job: any) => {
isEditing.value = true;
jobForm.value = { ...job };
showForm.value = true;
};
const closeForm = () => {
showForm.value = false;
};
const saveJob = async () => {
isSaving.value = true;
try {
let response;
const data = { ...jobForm.value };
if (isEditing.value) {
response = await axios.put(`${putApiUrl}/Email_Job/${data.id}`, data);
} else {
response = await axios.post(`${apiUrl}/Email_Job/`, data);
}
if (response.status === 200 || response.status === 201) {
$snackbar(`Job saved successfully`);
await fetchJobs();
closeForm();
}
} catch (error) {
console.error("Error saving job:", error);
$snackbar(`Error saving job`);
} finally {
isSaving.value = false;
}
};
const toggleJobStatus = async (job: any) => {
const updatedJob = { ...job, active: !job.active };
try {
await axios.put(`${putApiUrl}/Email_Job/${job.id}`, updatedJob);
$snackbar(`Job status updated`);
await fetchJobs();
} catch (error) {
console.error("Error updating job status:", error);
$snackbar(`Error updating job status`);
}
};
const confirmDelete = (job: any) => {
if (confirm(`Are you sure you want to delete the job "${job.name}"?`)) {
deleteJob(job.id);
}
};
const deleteJob = async (jobId: number) => {
try {
await axios.delete(`${putApiUrl}/Email_Job/${jobId}`);
$snackbar(`Job deleted successfully`);
await fetchJobs();
} catch (error) {
console.error("Error deleting job:", error);
$snackbar(`Error deleting job`);
}
};
onMounted(fetchJobs);
</script>
<style scoped>
.box {
padding: 1.5rem;
}
.border {
border: 1px solid #dbdbdb;
border-radius: 4px;
}
.modal-card-foot {
justify-content: flex-end;
}
</style>