706 lines
23 KiB
Vue

<template>
<div>
<LayoutsBreadcrumb />
<!-- Header Section -->
<rs-card class="mb-6">
<template #header>
<div class="flex items-center">
<Icon class="mr-2 text-primary" name="ic:outline-batch-prediction"></Icon>
<h1 class="text-xl font-bold text-primary">Batch Processing</h1>
</div>
</template>
<template #body>
<p class="text-gray-600">
Schedule or trigger the processing of large groups of messages in one go.
Ideal for sending newsletters, campaigns, or system-wide alerts to thousands/millions of users.
</p>
</template>
</rs-card>
<!-- Quick Actions -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<rs-card class="cursor-pointer transition-all duration-300 hover:shadow-lg" @click="showCreateBatchModal = true">
<div class="pt-5 pb-3 px-5 flex items-center gap-4">
<div class="p-4 flex justify-center items-center bg-blue-100 rounded-2xl">
<Icon class="text-blue-600 text-2xl" name="ic:outline-add"></Icon>
</div>
<div class="flex-1">
<span class="block font-bold text-lg text-blue-600">Create New Batch</span>
<span class="text-sm text-gray-600">Start a new batch processing job</span>
</div>
</div>
</rs-card>
<rs-card class="cursor-pointer transition-all duration-300 hover:shadow-lg" @click="showScheduleModal = true">
<div class="pt-5 pb-3 px-5 flex items-center gap-4">
<div class="p-4 flex justify-center items-center bg-green-100 rounded-2xl">
<Icon class="text-green-600 text-2xl" name="ic:outline-schedule"></Icon>
</div>
<div class="flex-1">
<span class="block font-bold text-lg text-green-600">Schedule Batch</span>
<span class="text-sm text-gray-600">Schedule for later execution</span>
</div>
</div>
</rs-card>
<rs-card class="cursor-pointer transition-all duration-300 hover:shadow-lg" @click="showTemplatesModal = true">
<div class="pt-5 pb-3 px-5 flex items-center gap-4">
<div class="p-4 flex justify-center items-center bg-purple-100 rounded-2xl">
<Icon class="text-purple-600 text-2xl" name="ic:outline-library-books"></Icon>
</div>
<div class="flex-1">
<span class="block font-bold text-lg text-purple-600">Batch Templates</span>
<span class="text-sm text-gray-600">Use predefined batch configurations</span>
</div>
</div>
</rs-card>
</div>
<!-- Batch Statistics -->
<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-4 gap-6 mb-6">
<rs-card
v-for="(stat, index) in batchStats"
:key="index"
class="transition-all duration-300 hover:shadow-lg"
>
<div class="pt-5 pb-3 px-5 flex items-center gap-4">
<div
class="p-4 flex justify-center items-center rounded-2xl"
:class="stat.bgColor"
>
<Icon class="text-2xl" :class="stat.iconColor" :name="stat.icon"></Icon>
</div>
<div class="flex-1 truncate">
<span class="block font-bold text-xl leading-tight" :class="stat.textColor">
{{ stat.value }}
</span>
<span class="text-sm font-medium text-gray-600">
{{ stat.title }}
</span>
</div>
</div>
</rs-card>
</div>
<!-- Active Batches -->
<rs-card class="mb-6">
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-primary">Active Batches</h3>
<rs-button variant="outline" size="sm" @click="refreshBatches">
<Icon class="mr-1" name="ic:outline-refresh"></Icon>
Refresh
</rs-button>
</div>
</template>
<template #body>
<div class="space-y-4">
<div
v-for="(batch, index) in activeBatches"
:key="index"
class="border border-gray-200 rounded-lg p-4"
>
<div class="flex items-center justify-between mb-3">
<div class="flex items-center">
<div
class="w-3 h-3 rounded-full mr-3"
:class="{
'bg-blue-500': batch.status === 'pending',
'bg-yellow-500': batch.status === 'processing',
'bg-green-500': batch.status === 'completed',
'bg-red-500': batch.status === 'failed',
'bg-gray-500': batch.status === 'paused'
}"
></div>
<div>
<h4 class="font-semibold">{{ batch.name }}</h4>
<p class="text-sm text-gray-600">{{ batch.description }}</p>
</div>
</div>
<div class="flex items-center gap-2">
<rs-badge :variant="getBatchStatusVariant(batch.status)">
{{ batch.status }}
</rs-badge>
<rs-dropdown>
<template #trigger>
<rs-button variant="outline" size="sm">
<Icon name="ic:outline-more-vert"></Icon>
</rs-button>
</template>
<rs-dropdown-item @click="viewBatchDetails(batch)">
<Icon class="mr-2" name="ic:outline-visibility"></Icon>
View Details
</rs-dropdown-item>
<rs-dropdown-item v-if="batch.status === 'processing'" @click="pauseBatch(batch)">
<Icon class="mr-2" name="ic:outline-pause"></Icon>
Pause
</rs-dropdown-item>
<rs-dropdown-item v-if="batch.status === 'paused'" @click="resumeBatch(batch)">
<Icon class="mr-2" name="ic:outline-play-arrow"></Icon>
Resume
</rs-dropdown-item>
<rs-dropdown-item v-if="['pending', 'paused'].includes(batch.status)" @click="cancelBatch(batch)" class="text-red-600">
<Icon class="mr-2" name="ic:outline-cancel"></Icon>
Cancel
</rs-dropdown-item>
</rs-dropdown>
</div>
</div>
<!-- Progress Bar -->
<div class="mb-3">
<div class="flex justify-between text-sm text-gray-600 mb-1">
<span>Progress: {{ batch.processed }}/{{ batch.total }}</span>
<span>{{ Math.round((batch.processed / batch.total) * 100) }}%</span>
</div>
<rs-progress-bar
:value="(batch.processed / batch.total) * 100"
:variant="batch.status === 'failed' ? 'danger' : 'primary'"
/>
</div>
<!-- Batch Info -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<span class="text-gray-600">Type:</span>
<span class="ml-1 font-medium">{{ batch.type }}</span>
</div>
<div>
<span class="text-gray-600">Chunk Size:</span>
<span class="ml-1 font-medium">{{ batch.chunkSize }}</span>
</div>
<div>
<span class="text-gray-600">Started:</span>
<span class="ml-1 font-medium">{{ batch.startedAt }}</span>
</div>
<div>
<span class="text-gray-600">ETA:</span>
<span class="ml-1 font-medium">{{ batch.eta }}</span>
</div>
</div>
</div>
</div>
</template>
</rs-card>
<!-- Batch History -->
<rs-card>
<template #header>
<h3 class="text-lg font-semibold text-primary">Batch History</h3>
</template>
<template #body>
<rs-table
:field="historyTableFields"
:data="batchHistory"
:options="{ striped: true, hover: true }"
:optionsAdvanced="{ sortable: true, filterable: true }"
advanced
/>
</template>
</rs-card>
<!-- Create Batch Modal -->
<rs-modal v-model="showCreateBatchModal" size="lg">
<template #header>
<h3 class="text-lg font-semibold">Create New Batch</h3>
</template>
<template #body>
<form @submit.prevent="createBatch" class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Batch Name</label>
<input
v-model="newBatch.name"
type="text"
class="w-full p-2 border border-gray-300 rounded-md"
placeholder="Enter batch name"
required
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Message Type</label>
<select v-model="newBatch.type" class="w-full p-2 border border-gray-300 rounded-md" required>
<option value="">Select type</option>
<option value="email">Email</option>
<option value="sms">SMS</option>
<option value="push">Push Notification</option>
<option value="webhook">Webhook</option>
</select>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Description</label>
<textarea
v-model="newBatch.description"
class="w-full p-2 border border-gray-300 rounded-md"
rows="3"
placeholder="Describe this batch processing job"
></textarea>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Chunk Size</label>
<input
v-model.number="newBatch.chunkSize"
type="number"
class="w-full p-2 border border-gray-300 rounded-md"
placeholder="500"
min="1"
max="10000"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Priority</label>
<select v-model="newBatch.priority" class="w-full p-2 border border-gray-300 rounded-md">
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Delay Between Chunks (ms)</label>
<input
v-model.number="newBatch.delay"
type="number"
class="w-full p-2 border border-gray-300 rounded-md"
placeholder="1000"
min="0"
/>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Target Criteria</label>
<textarea
v-model="newBatch.criteria"
class="w-full p-2 border border-gray-300 rounded-md"
rows="3"
placeholder="JSON criteria for selecting recipients (e.g., user type, timezone, preferences)"
></textarea>
</div>
</form>
</template>
<template #footer>
<div class="flex justify-end gap-2">
<rs-button variant="outline" @click="showCreateBatchModal = false">Cancel</rs-button>
<rs-button @click="createBatch" variant="primary">Create Batch</rs-button>
</div>
</template>
</rs-modal>
<!-- Schedule Modal -->
<rs-modal v-model="showScheduleModal" size="md">
<template #header>
<h3 class="text-lg font-semibold">Schedule Batch</h3>
</template>
<template #body>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Select Batch Template</label>
<select v-model="scheduleBatch.templateId" class="w-full p-2 border border-gray-300 rounded-md">
<option value="">Choose a template</option>
<option value="newsletter">Newsletter Campaign</option>
<option value="birthday">Birthday Reminders</option>
<option value="system">System Notifications</option>
</select>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Schedule Date</label>
<input
v-model="scheduleBatch.date"
type="date"
class="w-full p-2 border border-gray-300 rounded-md"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Schedule Time</label>
<input
v-model="scheduleBatch.time"
type="time"
class="w-full p-2 border border-gray-300 rounded-md"
/>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Timezone</label>
<select v-model="scheduleBatch.timezone" class="w-full p-2 border border-gray-300 rounded-md">
<option value="UTC">UTC</option>
<option value="Asia/Kuala_Lumpur">Asia/Kuala_Lumpur</option>
<option value="America/New_York">America/New_York</option>
<option value="Europe/London">Europe/London</option>
</select>
</div>
<div class="flex items-center">
<input
v-model="scheduleBatch.recurring"
type="checkbox"
class="mr-2"
/>
<label class="text-sm font-medium text-gray-700">Recurring batch</label>
</div>
<div v-if="scheduleBatch.recurring">
<label class="block text-sm font-medium text-gray-700 mb-2">Recurrence Pattern</label>
<select v-model="scheduleBatch.recurrencePattern" class="w-full p-2 border border-gray-300 rounded-md">
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
</select>
</div>
</div>
</template>
<template #footer>
<div class="flex justify-end gap-2">
<rs-button variant="outline" @click="showScheduleModal = false">Cancel</rs-button>
<rs-button @click="scheduleNewBatch" variant="primary">Schedule</rs-button>
</div>
</template>
</rs-modal>
<!-- Templates Modal -->
<rs-modal v-model="showTemplatesModal" size="lg">
<template #header>
<h3 class="text-lg font-semibold">Batch Templates</h3>
</template>
<template #body>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div
v-for="(template, index) in batchTemplates"
:key="index"
class="border border-gray-200 rounded-lg p-4 cursor-pointer hover:border-primary transition-colors"
@click="useBatchTemplate(template)"
>
<div class="flex items-center mb-3">
<Icon class="mr-2 text-primary" :name="template.icon"></Icon>
<h4 class="font-semibold">{{ template.name }}</h4>
</div>
<p class="text-sm text-gray-600 mb-3">{{ template.description }}</p>
<div class="space-y-1 text-xs text-gray-500">
<div>Type: {{ template.type }}</div>
<div>Chunk Size: {{ template.chunkSize }}</div>
<div>Priority: {{ template.priority }}</div>
</div>
</div>
</div>
</template>
<template #footer>
<div class="flex justify-end">
<rs-button variant="outline" @click="showTemplatesModal = false">Close</rs-button>
</div>
</template>
</rs-modal>
</div>
</template>
<script setup>
definePageMeta({
title: "Batch Processing",
middleware: ["auth"],
requiresAuth: true,
breadcrumb: [
{
name: "Notification",
path: "/notification",
},
{
name: "Queue & Scheduler",
path: "/notification/queue-scheduler",
},
{
name: "Batch Processing",
path: "/notification/queue-scheduler/batch",
},
],
});
// Reactive data
const showCreateBatchModal = ref(false);
const showScheduleModal = ref(false);
const showTemplatesModal = ref(false);
// New batch form
const newBatch = ref({
name: '',
type: '',
description: '',
chunkSize: 500,
priority: 'medium',
delay: 1000,
criteria: ''
});
// Schedule batch form
const scheduleBatch = ref({
templateId: '',
date: '',
time: '',
timezone: 'UTC',
recurring: false,
recurrencePattern: 'daily'
});
// Batch statistics
const batchStats = ref([
{
title: "Active Batches",
value: "12",
icon: "ic:outline-play-circle",
bgColor: "bg-blue-100",
iconColor: "text-blue-600",
textColor: "text-blue-600"
},
{
title: "Scheduled",
value: "8",
icon: "ic:outline-schedule",
bgColor: "bg-green-100",
iconColor: "text-green-600",
textColor: "text-green-600"
},
{
title: "Completed Today",
value: "45",
icon: "ic:outline-check-circle",
bgColor: "bg-purple-100",
iconColor: "text-purple-600",
textColor: "text-purple-600"
},
{
title: "Total Messages",
value: "2.3M",
icon: "ic:outline-email",
bgColor: "bg-orange-100",
iconColor: "text-orange-600",
textColor: "text-orange-600"
}
]);
// Active batches
const activeBatches = ref([
{
id: 'batch_001',
name: 'Newsletter Campaign Q1',
description: 'Quarterly newsletter to all subscribers',
status: 'processing',
type: 'email',
processed: 15000,
total: 50000,
chunkSize: 500,
startedAt: '2024-01-15 09:00:00',
eta: '2 hours'
},
{
id: 'batch_002',
name: 'Birthday Reminders',
description: 'Daily birthday notifications',
status: 'completed',
type: 'push',
processed: 1200,
total: 1200,
chunkSize: 100,
startedAt: '2024-01-15 08:00:00',
eta: 'Completed'
},
{
id: 'batch_003',
name: 'SMS OTP Batch',
description: 'OTP messages for verification',
status: 'paused',
type: 'sms',
processed: 800,
total: 2000,
chunkSize: 200,
startedAt: '2024-01-15 10:30:00',
eta: 'Paused'
}
]);
// Batch history table fields
const historyTableFields = ref([
{ key: 'name', label: 'Batch Name', sortable: true },
{ key: 'type', label: 'Type', sortable: true },
{ key: 'status', label: 'Status', sortable: true },
{ key: 'total', label: 'Total Messages', sortable: true },
{ key: 'processed', label: 'Processed', sortable: true },
{ key: 'startedAt', label: 'Started', sortable: true },
{ key: 'completedAt', label: 'Completed', sortable: true },
{ key: 'duration', label: 'Duration', sortable: true }
]);
// Batch history data
const batchHistory = ref([
{
name: 'Welcome Email Series',
type: 'email',
status: 'completed',
total: 25000,
processed: 25000,
startedAt: '2024-01-14 14:00:00',
completedAt: '2024-01-14 16:30:00',
duration: '2h 30m'
},
{
name: 'Product Update Push',
type: 'push',
status: 'completed',
total: 100000,
processed: 98500,
startedAt: '2024-01-13 10:00:00',
completedAt: '2024-01-13 12:45:00',
duration: '2h 45m'
},
{
name: 'Security Alert SMS',
type: 'sms',
status: 'failed',
total: 5000,
processed: 2300,
startedAt: '2024-01-12 16:00:00',
completedAt: '2024-01-12 16:45:00',
duration: '45m'
}
]);
// Batch templates
const batchTemplates = ref([
{
name: 'Newsletter Campaign',
description: 'Standard newsletter template for marketing campaigns',
type: 'email',
chunkSize: 1000,
priority: 'medium',
icon: 'ic:outline-email'
},
{
name: 'Birthday Reminders',
description: 'Daily birthday notification template',
type: 'push',
chunkSize: 100,
priority: 'low',
icon: 'ic:outline-cake'
},
{
name: 'System Notifications',
description: 'Critical system alerts and updates',
type: 'push',
chunkSize: 500,
priority: 'high',
icon: 'ic:outline-notification-important'
},
{
name: 'SMS Verification',
description: 'OTP and verification SMS template',
type: 'sms',
chunkSize: 200,
priority: 'high',
icon: 'ic:outline-sms'
}
]);
// Methods
function getBatchStatusVariant(status) {
const variants = {
pending: 'info',
processing: 'warning',
completed: 'success',
failed: 'danger',
paused: 'secondary'
};
return variants[status] || 'default';
}
function viewBatchDetails(batch) {
// Navigate to batch details page or show detailed modal
console.log('Viewing batch details:', batch);
}
function pauseBatch(batch) {
batch.status = 'paused';
batch.eta = 'Paused';
}
function resumeBatch(batch) {
batch.status = 'processing';
batch.eta = '1.5 hours';
}
function cancelBatch(batch) {
batch.status = 'cancelled';
batch.eta = 'Cancelled';
}
function createBatch() {
// Mock batch creation
const batch = {
id: `batch_${Date.now()}`,
name: newBatch.value.name,
description: newBatch.value.description,
status: 'pending',
type: newBatch.value.type,
processed: 0,
total: 10000, // Mock total
chunkSize: newBatch.value.chunkSize,
startedAt: new Date().toLocaleString(),
eta: 'Pending'
};
activeBatches.value.unshift(batch);
showCreateBatchModal.value = false;
// Reset form
newBatch.value = {
name: '',
type: '',
description: '',
chunkSize: 500,
priority: 'medium',
delay: 1000,
criteria: ''
};
}
function scheduleNewBatch() {
// Mock batch scheduling
console.log('Scheduling batch:', scheduleBatch.value);
showScheduleModal.value = false;
// Reset form
scheduleBatch.value = {
templateId: '',
date: '',
time: '',
timezone: 'UTC',
recurring: false,
recurrencePattern: 'daily'
};
}
function useBatchTemplate(template) {
newBatch.value = {
name: template.name,
type: template.type,
description: template.description,
chunkSize: template.chunkSize,
priority: template.priority,
delay: 1000,
criteria: ''
};
showTemplatesModal.value = false;
showCreateBatchModal.value = true;
}
function refreshBatches() {
// Mock refresh
console.log('Refreshing batches...');
}
</script>
<style lang="scss" scoped></style>