707 lines
24 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-schedule"></Icon>
<h1 class="text-xl font-bold text-primary">Timezone Handling</h1>
</div>
</template>
<template #body>
<p class="text-gray-600">
Ensures messages are delivered at the right local time for each recipient.
Schedule birthday messages at 9AM local time and avoid 2AM push alerts across timezones.
</p>
</template>
</rs-card>
<!-- Current Time Display -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<rs-card
v-for="(timezone, index) in majorTimezones"
:key="index"
class="transition-all duration-300 hover:shadow-lg"
>
<div class="pt-5 pb-3 px-5 text-center">
<div class="mb-2">
<Icon class="text-primary text-2xl" name="ic:outline-access-time"></Icon>
</div>
<h3 class="font-semibold text-lg">{{ timezone.name }}</h3>
<p class="text-2xl font-bold text-primary">{{ timezone.time }}</p>
<p class="text-sm text-gray-600">{{ timezone.zone }}</p>
</div>
</rs-card>
</div>
<!-- Timezone 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 timezoneStats"
: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>
<!-- Timezone Configuration -->
<rs-card class="mb-6">
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-primary">Timezone Configuration</h3>
<rs-button variant="outline" size="sm" @click="showConfigModal = true">
<Icon class="mr-1" name="ic:outline-settings"></Icon>
Configure
</rs-button>
</div>
</template>
<template #body>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="border border-gray-200 rounded-lg p-4">
<h4 class="font-semibold mb-3 flex items-center">
<Icon class="mr-2 text-primary" name="ic:outline-schedule"></Icon>
Default Delivery Times
</h4>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-gray-600">Morning Messages:</span>
<span class="font-medium">{{ config.morningTime }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Afternoon Messages:</span>
<span class="font-medium">{{ config.afternoonTime }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Evening Messages:</span>
<span class="font-medium">{{ config.eveningTime }}</span>
</div>
</div>
</div>
<div class="border border-gray-200 rounded-lg p-4">
<h4 class="font-semibold mb-3 flex items-center">
<Icon class="mr-2 text-primary" name="ic:outline-block"></Icon>
Quiet Hours
</h4>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-gray-600">Start Time:</span>
<span class="font-medium">{{ config.quietHours.start }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">End Time:</span>
<span class="font-medium">{{ config.quietHours.end }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Emergency Override:</span>
<span class="font-medium">{{ config.quietHours.allowEmergency ? 'Enabled' : 'Disabled' }}</span>
</div>
</div>
</div>
<div class="border border-gray-200 rounded-lg p-4">
<h4 class="font-semibold mb-3 flex items-center">
<Icon class="mr-2 text-primary" name="ic:outline-public"></Icon>
Timezone Detection
</h4>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-gray-600">Auto-detect:</span>
<span class="font-medium">{{ config.autoDetect ? 'Enabled' : 'Disabled' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Fallback Timezone:</span>
<span class="font-medium">{{ config.fallbackTimezone }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Update Frequency:</span>
<span class="font-medium">{{ config.updateFrequency }}</span>
</div>
</div>
</div>
</div>
</template>
</rs-card>
<!-- Scheduled Messages by Timezone -->
<rs-card class="mb-6">
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-primary">Scheduled Messages by Timezone</h3>
<div class="flex items-center gap-2">
<select v-model="selectedTimezone" class="p-2 border border-gray-300 rounded-md text-sm">
<option value="">All Timezones</option>
<option v-for="tz in availableTimezones" :key="tz.value" :value="tz.value">
{{ tz.label }}
</option>
</select>
<rs-button variant="outline" size="sm" @click="refreshScheduledMessages">
<Icon class="mr-1" name="ic:outline-refresh"></Icon>
Refresh
</rs-button>
</div>
</div>
</template>
<template #body>
<rs-table
:field="scheduledMessagesFields"
:data="filteredScheduledMessages"
:options="{ striped: true, hover: true }"
:optionsAdvanced="{ sortable: true, filterable: false }"
advanced
/>
</template>
</rs-card>
<!-- Timezone Distribution Chart -->
<rs-card class="mb-6">
<template #header>
<h3 class="text-lg font-semibold text-primary">User Distribution by Timezone</h3>
</template>
<template #body>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Chart would go here in a real implementation -->
<div class="space-y-3">
<div
v-for="(distribution, index) in timezoneDistribution"
:key="index"
class="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
>
<div class="flex items-center">
<div class="w-4 h-4 rounded mr-3" :style="{ backgroundColor: distribution.color }"></div>
<div>
<p class="font-medium">{{ distribution.timezone }}</p>
<p class="text-sm text-gray-600">{{ distribution.users }} users</p>
</div>
</div>
<div class="text-right">
<p class="font-medium">{{ distribution.percentage }}%</p>
<p class="text-sm text-gray-600">{{ distribution.currentTime }}</p>
</div>
</div>
</div>
<div class="space-y-4">
<h4 class="font-semibold">Delivery Optimization</h4>
<div class="space-y-3">
<div class="bg-green-50 border border-green-200 rounded p-3">
<div class="flex items-center mb-2">
<Icon class="mr-2 text-green-600" name="ic:outline-check-circle"></Icon>
<span class="font-medium text-green-800">Optimal Delivery Windows</span>
</div>
<p class="text-sm text-green-700">
{{ optimizationStats.optimalWindows }} messages scheduled during optimal hours
</p>
</div>
<div class="bg-yellow-50 border border-yellow-200 rounded p-3">
<div class="flex items-center mb-2">
<Icon class="mr-2 text-yellow-600" name="ic:outline-warning"></Icon>
<span class="font-medium text-yellow-800">Quiet Hours Conflicts</span>
</div>
<p class="text-sm text-yellow-700">
{{ optimizationStats.quietHoursConflicts }} messages would be sent during quiet hours
</p>
</div>
<div class="bg-blue-50 border border-blue-200 rounded p-3">
<div class="flex items-center mb-2">
<Icon class="mr-2 text-blue-600" name="ic:outline-info"></Icon>
<span class="font-medium text-blue-800">Timezone Coverage</span>
</div>
<p class="text-sm text-blue-700">
Messages will be delivered across {{ optimizationStats.timezoneCoverage }} timezones
</p>
</div>
</div>
</div>
</div>
</template>
</rs-card>
<!-- Timezone Testing Tool -->
<rs-card>
<template #header>
<h3 class="text-lg font-semibold text-primary">Timezone Testing Tool</h3>
</template>
<template #body>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Test Message</label>
<textarea
v-model="testMessage.content"
class="w-full p-2 border border-gray-300 rounded-md"
rows="3"
placeholder="Enter test message content"
></textarea>
</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">Scheduled Time (UTC)</label>
<input
v-model="testMessage.scheduledTime"
type="datetime-local"
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">Message Type</label>
<select v-model="testMessage.type" class="w-full p-2 border border-gray-300 rounded-md">
<option value="email">Email</option>
<option value="sms">SMS</option>
<option value="push">Push Notification</option>
</select>
</div>
</div>
<rs-button @click="testTimezoneDelivery" variant="primary" class="w-full">
<Icon class="mr-1" name="ic:outline-play-arrow"></Icon>
Test Timezone Delivery
</rs-button>
</div>
<div class="space-y-4">
<h4 class="font-semibold">Delivery Preview</h4>
<div v-if="deliveryPreview.length > 0" class="space-y-2 max-h-60 overflow-y-auto">
<div
v-for="(preview, index) in deliveryPreview"
:key="index"
class="border border-gray-200 rounded p-3"
>
<div class="flex justify-between items-start mb-1">
<span class="font-medium">{{ preview.timezone }}</span>
<span class="text-sm text-gray-600">{{ preview.users }} users</span>
</div>
<div class="text-sm">
<p class="text-gray-600">Local delivery time: <span class="font-medium">{{ preview.localTime }}</span></p>
<p class="text-gray-600">Status:
<span :class="{
'text-green-600': preview.status === 'optimal',
'text-yellow-600': preview.status === 'suboptimal',
'text-red-600': preview.status === 'blocked'
}" class="font-medium">{{ preview.status }}</span>
</p>
</div>
</div>
</div>
<div v-else class="text-center text-gray-500 py-8">
<Icon class="text-4xl mb-2" name="ic:outline-schedule"></Icon>
<p>Run a test to see delivery preview</p>
</div>
</div>
</div>
</template>
</rs-card>
<!-- Configuration Modal -->
<rs-modal v-model="showConfigModal" size="lg">
<template #header>
<h3 class="text-lg font-semibold">Timezone Configuration</h3>
</template>
<template #body>
<div class="space-y-6">
<div>
<h4 class="font-semibold mb-3">Default Delivery Times</h4>
<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">Morning Messages</label>
<input
v-model="config.morningTime"
type="time"
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">Afternoon Messages</label>
<input
v-model="config.afternoonTime"
type="time"
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">Evening Messages</label>
<input
v-model="config.eveningTime"
type="time"
class="w-full p-2 border border-gray-300 rounded-md"
/>
</div>
</div>
</div>
<div>
<h4 class="font-semibold mb-3">Quiet Hours</h4>
<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">Start Time</label>
<input
v-model="config.quietHours.start"
type="time"
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">End Time</label>
<input
v-model="config.quietHours.end"
type="time"
class="w-full p-2 border border-gray-300 rounded-md"
/>
</div>
</div>
<div class="mt-4">
<label class="flex items-center">
<input
v-model="config.quietHours.allowEmergency"
type="checkbox"
class="mr-2"
/>
<span class="text-sm font-medium text-gray-700">Allow emergency messages during quiet hours</span>
</label>
</div>
</div>
<div>
<h4 class="font-semibold mb-3">Timezone Detection</h4>
<div class="space-y-4">
<label class="flex items-center">
<input
v-model="config.autoDetect"
type="checkbox"
class="mr-2"
/>
<span class="text-sm font-medium text-gray-700">Auto-detect user timezones</span>
</label>
<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">Fallback Timezone</label>
<select v-model="config.fallbackTimezone" class="w-full p-2 border border-gray-300 rounded-md">
<option value="UTC">UTC</option>
<option value="America/New_York">America/New_York</option>
<option value="Europe/London">Europe/London</option>
<option value="Asia/Tokyo">Asia/Tokyo</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Update Frequency</label>
<select v-model="config.updateFrequency" 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>
</div>
</div>
</div>
</template>
<template #footer>
<div class="flex justify-end gap-2">
<rs-button variant="outline" @click="showConfigModal = false">Cancel</rs-button>
<rs-button @click="saveConfiguration" variant="primary">Save Configuration</rs-button>
</div>
</template>
</rs-modal>
</div>
</template>
<script setup>
definePageMeta({
title: "Timezone Handling",
middleware: ["auth"],
requiresAuth: true,
breadcrumb: [
{
name: "Notification",
path: "/notification",
},
{
name: "Queue & Scheduler",
path: "/notification/queue-scheduler",
},
{
name: "Timezone Handling",
path: "/notification/queue-scheduler/timezone",
},
],
});
// Reactive data
const showConfigModal = ref(false);
const selectedTimezone = ref('');
const deliveryPreview = ref([]);
// Current time for major timezones
const majorTimezones = ref([
{
name: 'New York',
zone: 'America/New_York',
time: new Date().toLocaleTimeString('en-US', { timeZone: 'America/New_York' })
},
{
name: 'London',
zone: 'Europe/London',
time: new Date().toLocaleTimeString('en-US', { timeZone: 'Europe/London' })
},
{
name: 'Tokyo',
zone: 'Asia/Tokyo',
time: new Date().toLocaleTimeString('en-US', { timeZone: 'Asia/Tokyo' })
}
]);
// Update times every second
setInterval(() => {
majorTimezones.value.forEach(tz => {
tz.time = new Date().toLocaleTimeString('en-US', { timeZone: tz.zone });
});
}, 1000);
// Statistics
const timezoneStats = ref([
{
title: "Active Timezones",
value: "24",
icon: "ic:outline-public",
bgColor: "bg-blue-100",
iconColor: "text-blue-600",
textColor: "text-blue-600"
},
{
title: "Scheduled Messages",
value: "1,847",
icon: "ic:outline-schedule",
bgColor: "bg-green-100",
iconColor: "text-green-600",
textColor: "text-green-600"
},
{
title: "Optimal Deliveries",
value: "94.2%",
icon: "ic:outline-trending-up",
bgColor: "bg-purple-100",
iconColor: "text-purple-600",
textColor: "text-purple-600"
},
{
title: "Quiet Hours Respected",
value: "99.8%",
icon: "ic:outline-nights-stay",
bgColor: "bg-orange-100",
iconColor: "text-orange-600",
textColor: "text-orange-600"
}
]);
// Configuration
const config = ref({
morningTime: '09:00',
afternoonTime: '14:00',
eveningTime: '18:00',
quietHours: {
start: '22:00',
end: '07:00',
allowEmergency: true
},
autoDetect: true,
fallbackTimezone: 'UTC',
updateFrequency: 'daily'
});
// Available timezones
const availableTimezones = ref([
{ value: 'America/New_York', label: 'America/New_York (EST/EDT)' },
{ value: 'America/Los_Angeles', label: 'America/Los_Angeles (PST/PDT)' },
{ value: 'Europe/London', label: 'Europe/London (GMT/BST)' },
{ value: 'Europe/Paris', label: 'Europe/Paris (CET/CEST)' },
{ value: 'Asia/Tokyo', label: 'Asia/Tokyo (JST)' },
{ value: 'Asia/Shanghai', label: 'Asia/Shanghai (CST)' },
{ value: 'Asia/Kolkata', label: 'Asia/Kolkata (IST)' },
{ value: 'Australia/Sydney', label: 'Australia/Sydney (AEST/AEDT)' }
]);
// Table fields for scheduled messages
const scheduledMessagesFields = ref([
{ key: 'id', label: 'Message ID', sortable: true },
{ key: 'type', label: 'Type', sortable: true },
{ key: 'timezone', label: 'Timezone', sortable: true },
{ key: 'scheduledUTC', label: 'Scheduled (UTC)', sortable: true },
{ key: 'localTime', label: 'Local Time', sortable: true },
{ key: 'recipients', label: 'Recipients', sortable: true },
{ key: 'status', label: 'Status', sortable: true }
]);
// Mock scheduled messages data
const scheduledMessages = ref([
{
id: 'msg_001',
type: 'email',
timezone: 'America/New_York',
scheduledUTC: '2024-01-15 14:00:00',
localTime: '2024-01-15 09:00:00',
recipients: 1250,
status: 'scheduled'
},
{
id: 'msg_002',
type: 'push',
timezone: 'Europe/London',
scheduledUTC: '2024-01-15 09:00:00',
localTime: '2024-01-15 09:00:00',
recipients: 890,
status: 'scheduled'
},
{
id: 'msg_003',
type: 'sms',
timezone: 'Asia/Tokyo',
scheduledUTC: '2024-01-15 00:00:00',
localTime: '2024-01-15 09:00:00',
recipients: 2100,
status: 'scheduled'
}
]);
// Timezone distribution
const timezoneDistribution = ref([
{
timezone: 'America/New_York',
users: 15420,
percentage: 32.5,
color: '#3B82F6',
currentTime: new Date().toLocaleTimeString('en-US', { timeZone: 'America/New_York' })
},
{
timezone: 'Europe/London',
users: 12890,
percentage: 27.2,
color: '#10B981',
currentTime: new Date().toLocaleTimeString('en-US', { timeZone: 'Europe/London' })
},
{
timezone: 'Asia/Tokyo',
users: 9650,
percentage: 20.3,
color: '#F59E0B',
currentTime: new Date().toLocaleTimeString('en-US', { timeZone: 'Asia/Tokyo' })
},
{
timezone: 'Australia/Sydney',
users: 5840,
percentage: 12.3,
color: '#EF4444',
currentTime: new Date().toLocaleTimeString('en-US', { timeZone: 'Australia/Sydney' })
},
{
timezone: 'Others',
users: 3700,
percentage: 7.7,
color: '#8B5CF6',
currentTime: '-'
}
]);
// Optimization stats
const optimizationStats = ref({
optimalWindows: 1654,
quietHoursConflicts: 23,
timezoneCoverage: 18
});
// Test message
const testMessage = ref({
content: '',
scheduledTime: '',
type: 'email'
});
// Computed filtered scheduled messages
const filteredScheduledMessages = computed(() => {
let filtered = scheduledMessages.value;
if (selectedTimezone.value) {
filtered = filtered.filter(msg => msg.timezone === selectedTimezone.value);
}
return filtered.map(msg => ({
...msg,
status: h('span', {
class: `px-2 py-1 rounded text-xs font-medium ${
msg.status === 'scheduled' ? 'bg-blue-100 text-blue-800' :
msg.status === 'sent' ? 'bg-green-100 text-green-800' :
'bg-gray-100 text-gray-800'
}`
}, msg.status)
}));
});
// Methods
function refreshScheduledMessages() {
// Mock refresh
console.log('Refreshing scheduled messages...');
}
function testTimezoneDelivery() {
if (!testMessage.value.content || !testMessage.value.scheduledTime) {
return;
}
// Mock delivery preview generation
deliveryPreview.value = [
{
timezone: 'America/New_York',
users: 1250,
localTime: '09:00 AM',
status: 'optimal'
},
{
timezone: 'Europe/London',
localTime: '02:00 AM',
users: 890,
status: 'blocked'
},
{
timezone: 'Asia/Tokyo',
localTime: '11:00 AM',
users: 2100,
status: 'optimal'
},
{
timezone: 'Australia/Sydney',
localTime: '01:00 AM',
users: 580,
status: 'blocked'
}
];
}
function saveConfiguration() {
// Mock save
console.log('Saving timezone configuration...');
showConfigModal.value = false;
}
</script>
<style lang="scss" scoped></style>