314 lines
15 KiB
Vue

<script setup>
import { ref, computed } from 'vue';
definePageMeta({
title: "Notification Triggers & Rules",
middleware: ["auth"],
requiresAuth: true,
});
const activeTab = ref('triggers'); // 'triggers', 'segments'
// Mock data
const mockEvents = ref([
{ id: 'user_registered', name: 'User Registered' },
{ id: 'payment_completed', name: 'Payment Completed' },
{ id: 'password_reset_request', name: 'Password Reset Request' },
]);
const mockTemplates = ref([
{ id: 'welcome_email', name: 'Welcome Email' },
{ id: 'payment_receipt', name: 'Payment Receipt' },
{ id: 'reset_password_instructions', name: 'Reset Password Instructions' },
]);
const triggers = ref([
{
id: 'trg_001',
name: 'Welcome New Users',
description: 'Sends a welcome email upon user registration.',
type: 'event',
eventType: 'user_registered',
actionTemplateId: 'welcome_email',
priority: 'medium',
status: 'active',
conditions: [],
targetSegments: [],
dependencies: null,
},
{
id: 'trg_002',
name: 'Daily Sales Summary',
description: 'Sends a summary of sales daily at 8 AM.',
type: 'time',
schedule: '0 8 * * *', // Cron for 8 AM daily
actionTemplateId: 'payment_receipt', // Placeholder, should be a summary template
priority: 'low',
status: 'inactive',
conditions: [],
targetSegments: [],
dependencies: null,
}
]);
const showAddEditModal = ref(false);
const isEditing = ref(false);
const currentTrigger = ref(null);
const newTriggerData = ref({
id: null,
name: '',
description: '',
type: 'event',
eventType: mockEvents.value.length > 0 ? mockEvents.value[0].id : null,
schedule: '',
webhookUrl: 'https://api.example.com/webhook/generated_id', // Placeholder
actionTemplateId: mockTemplates.value.length > 0 ? mockTemplates.value[0].id : null,
priority: 'medium',
status: 'active',
conditions: [],
targetSegments: [],
dependencies: null,
});
const priorities = ['low', 'medium', 'high'];
const triggerTypes = [
{ id: 'event', name: 'Event-Based' },
{ id: 'time', name: 'Time-Based' },
{ id: 'api', name: 'External API/Webhook' }
];
const openAddModal = () => {
isEditing.value = false;
currentTrigger.value = null;
newTriggerData.value = {
id: `trg_${Date.now().toString().slice(-3)}`, // Simple unique ID for mock
name: '',
description: '',
type: 'event',
eventType: mockEvents.value.length > 0 ? mockEvents.value[0].id : null,
schedule: '',
webhookUrl: `https://api.example.com/webhook/trg_${Date.now().toString().slice(-3)}`,
actionTemplateId: mockTemplates.value.length > 0 ? mockTemplates.value[0].id : null,
priority: 'medium',
status: 'active',
conditions: [],
targetSegments: [],
dependencies: null,
};
showAddEditModal.value = true;
};
const openEditModal = (trigger) => {
isEditing.value = true;
currentTrigger.value = trigger;
newTriggerData.value = { ...trigger };
showAddEditModal.value = true;
};
const closeModal = () => {
showAddEditModal.value = false;
currentTrigger.value = null;
};
const saveTrigger = () => {
if (isEditing.value && currentTrigger.value) {
const index = triggers.value.findIndex(t => t.id === currentTrigger.value.id);
if (index !== -1) {
triggers.value[index] = { ...newTriggerData.value };
}
} else {
triggers.value.push({ ...newTriggerData.value, id: newTriggerData.value.id || `trg_${Date.now().toString().slice(-3)}` });
}
closeModal();
};
const deleteTrigger = (triggerId) => {
if (confirm('Are you sure you want to delete this trigger?')) {
triggers.value = triggers.value.filter(t => t.id !== triggerId);
}
};
const testTrigger = (trigger) => {
alert(`Simulating test for trigger: ${trigger.name} (Not implemented yet)`);
};
const getEventName = (eventId) => mockEvents.value.find(e => e.id === eventId)?.name || eventId;
const getTemplateName = (templateId) => mockTemplates.value.find(t => t.id === templateId)?.name || templateId;
</script>
<template>
<div>
<LayoutsBreadcrumb />
<rs-card>
<template #header>
<div class="flex justify-between items-center">
<h1 class="text-xl font-semibold">Notification Triggers & Rules</h1>
<button @click="openAddModal" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-sm">
Add New Trigger
</button>
</div>
</template>
<template #body>
<!-- Tabs (Simplified for now, can be expanded later if needed) -->
<!-- <div class="mb-4 border-b border-gray-200">
<nav class="-mb-px flex space-x-8" aria-label="Tabs">
<button @click="activeTab = 'triggers'"
:class="['whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm', activeTab === 'triggers' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300']">
Triggers & Rules
</button>
<button @click="activeTab = 'segments'"
:class="['whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm', activeTab === 'segments' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300']">
User Segments (Coming Soon)
</button>
</nav>
</div> -->
<div v-if="activeTab === 'triggers'">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Details</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Priority</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr v-if="triggers.length === 0">
<td colspan="6" class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-center">No triggers defined yet.</td>
</tr>
<tr v-for="trigger in triggers" :key="trigger.id">
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">{{ trigger.name }}</div>
<div class="text-xs text-gray-500">{{ trigger.description }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 capitalize">{{ trigger.type }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<div v-if="trigger.type === 'event'">Event: {{ getEventName(trigger.eventType) }}</div>
<div v-if="trigger.type === 'time'">Schedule: {{ trigger.schedule }}</div>
<div v-if="trigger.type === 'api'" class="truncate max-w-xs" :title="trigger.webhookUrl">Webhook: {{ trigger.webhookUrl }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 capitalize">{{ trigger.priority }}</td>
<td class="px-6 py-4 whitespace-nowrap">
<span :class="['px-2 inline-flex text-xs leading-5 font-semibold rounded-full', trigger.status === 'active' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800']">
{{ trigger.status }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
<button @click="openEditModal(trigger)" class="text-indigo-600 hover:text-indigo-900">Edit</button>
<button @click="testTrigger(trigger)" class="text-yellow-600 hover:text-yellow-900">Test</button>
<button @click="deleteTrigger(trigger.id)" class="text-red-600 hover:text-red-900">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- <div v-if="activeTab === 'segments'">
<p class="text-gray-600 p-4">User Segments management will be available here soon.</p>
</div> -->
<!-- Add/Edit Modal -->
<div v-if="showAddEditModal" class="fixed inset-0 z-50 overflow-y-auto bg-gray-600 bg-opacity-50 flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-xl p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ isEditing ? 'Edit' : 'Add New' }} Trigger/Rule</h3>
<button @click="closeModal" class="text-gray-400 hover:text-gray-600">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
</button>
</div>
<form @submit.prevent="saveTrigger" class="space-y-4">
<div>
<label for="triggerName" class="block text-sm font-medium text-gray-700">Name</label>
<input type="text" v-model="newTriggerData.name" id="triggerName" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div>
<label for="triggerDescription" class="block text-sm font-medium text-gray-700">Description</label>
<textarea v-model="newTriggerData.description" id="triggerDescription" rows="2" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"></textarea>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="triggerStatus" class="block text-sm font-medium text-gray-700">Status</label>
<select v-model="newTriggerData.status" id="triggerStatus" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
<div>
<label for="triggerPriority" class="block text-sm font-medium text-gray-700">Priority</label>
<select v-model="newTriggerData.priority" id="triggerPriority" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<option v-for="p in priorities" :key="p" :value="p" class="capitalize">{{ p }}</option>
</select>
</div>
</div>
<div>
<label for="triggerType" class="block text-sm font-medium text-gray-700">Trigger Type</label>
<select v-model="newTriggerData.type" id="triggerType" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<option v-for="tt in triggerTypes" :key="tt.id" :value="tt.id">{{ tt.name }}</option>
</select>
</div>
<!-- Conditional Fields -->
<div v-if="newTriggerData.type === 'event'">
<label for="eventType" class="block text-sm font-medium text-gray-700">Event</label>
<select v-model="newTriggerData.eventType" id="eventType" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<option v-for="event in mockEvents" :key="event.id" :value="event.id">{{ event.name }}</option>
</select>
</div>
<div v-if="newTriggerData.type === 'time'">
<label for="triggerSchedule" class="block text-sm font-medium text-gray-700">Schedule (Cron Expression or Description)</label>
<input type="text" v-model="newTriggerData.schedule" id="triggerSchedule" placeholder="e.g., 0 9 * * * OR Daily at 9 AM" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div v-if="newTriggerData.type === 'api'">
<label class="block text-sm font-medium text-gray-700">Webhook URL</label>
<input type="text" :value="newTriggerData.webhookUrl" readonly class="mt-1 block w-full rounded-md border-gray-300 shadow-sm bg-gray-100 sm:text-sm cursor-not-allowed">
<p class="text-xs text-gray-500 mt-1">This URL is automatically generated. Send a POST request here to trigger.</p>
</div>
<div>
<label for="actionTemplate" class="block text-sm font-medium text-gray-700">Action: Send Notification Template</label>
<select v-model="newTriggerData.actionTemplateId" id="actionTemplate" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<option v-for="template in mockTemplates" :key="template.id" :value="template.id">{{ template.name }}</option>
</select>
</div>
<div class="mt-6 p-3 bg-gray-50 rounded-md">
<h4 class="text-sm font-medium text-gray-600 mb-2">Advanced Configuration (Coming Soon)</h4>
<p class="text-xs text-gray-500">- Conditional Logic (IF/THEN Rules)</p>
<p class="text-xs text-gray-500">- User Segmentation Targeting</p>
<p class="text-xs text-gray-500">- Rule Dependencies</p>
<p class="text-xs text-gray-500">- Rule Testing & Simulation</p>
</div>
<div class="pt-5">
<div class="flex justify-end space-x-3">
<button type="button" @click="closeModal" class="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50">Cancel</button>
<button type="submit" class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700">
{{ isEditing ? 'Save Changes' : 'Add Trigger' }}
</button>
</div>
</div>
</form>
</div>
</div>
</template>
</rs-card>
</div>
</template>
<style scoped>
/* Scoped styles if needed */
.max-h-\[90vh\] {
max-height: 90vh;
}
</style>