mirror of
https://github.com/abhinavxd/libredesk.git
synced 2025-10-29 02:53:46 +00:00
430 lines
14 KiB
Vue
430 lines
14 KiB
Vue
<template>
|
|
<form @submit="onSubmit" class="space-y-8">
|
|
<FormField v-slot="{ componentField }" name="name">
|
|
<FormItem>
|
|
<FormLabel>{{ t('form.field.name') }}</FormLabel>
|
|
<FormControl>
|
|
<Input type="text" placeholder="" v-bind="componentField" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ componentField }" name="description">
|
|
<FormItem>
|
|
<FormLabel>{{ t('form.field.description') }}</FormLabel>
|
|
<FormControl>
|
|
<Input type="text" placeholder="" v-bind="componentField" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ componentField }" name="first_response_time">
|
|
<FormItem>
|
|
<FormLabel>{{ t('admin.sla.firstResponseTime') }}</FormLabel>
|
|
<FormControl>
|
|
<Input type="text" placeholder="6h" v-bind="componentField" />
|
|
</FormControl>
|
|
<FormDescription>
|
|
{{ t('globals.messages.golangDurationHoursMinutes') }}
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ componentField }" name="resolution_time">
|
|
<FormItem>
|
|
<FormLabel>{{ t('admin.sla.resolutionTime') }}</FormLabel>
|
|
<FormControl>
|
|
<Input type="text" placeholder="24h" v-bind="componentField" />
|
|
</FormControl>
|
|
<FormDescription>{{ t('globals.messages.golangDurationHoursMinutes') }} </FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ componentField }" name="next_response_time">
|
|
<FormItem>
|
|
<FormLabel>{{ t('admin.sla.nextResponseTime') }}</FormLabel>
|
|
<FormControl>
|
|
<Input type="text" placeholder="30m" v-bind="componentField" />
|
|
</FormControl>
|
|
<FormDescription>
|
|
{{ t('globals.messages.golangDurationHoursMinutes') }}
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<!-- Notifications Section -->
|
|
<div class="space-y-6">
|
|
<div class="flex items-center justify-between pb-3 border-b">
|
|
<div class="space-y-1">
|
|
<h3 class="text-lg font-semibold text-foreground">
|
|
{{ t('admin.sla.alertConfiguration') }}
|
|
</h3>
|
|
<p class="text-sm text-muted-foreground">
|
|
{{ t('admin.sla.alertConfiguration.description') }}
|
|
</p>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<Button type="button" variant="outline" size="sm" @click="addNotification('breach')">
|
|
<Plus class="w-4 h-4 mr-2" />
|
|
{{ t('admin.sla.addBreachAlert') }}
|
|
</Button>
|
|
<Button type="button" variant="outline" size="sm" @click="addNotification('warning')">
|
|
<Plus class="w-4 h-4 mr-2" />
|
|
{{ t('admin.sla.addWarningAlert') }}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notifications List -->
|
|
<div v-if="form.values.notifications?.length > 0" class="space-y-3">
|
|
<div
|
|
v-for="(notification, index) in form.values.notifications"
|
|
:key="index"
|
|
class="group relative p-5 box bg-background transition-all hover:border-foreground/20"
|
|
>
|
|
<FormField :name="`notifications.${index}.type`" v-slot="{ componentField }">
|
|
<Input v-bind="componentField" type="hidden" />
|
|
</FormField>
|
|
|
|
<!-- Card Header -->
|
|
<div class="flex items-center justify-between mb-5">
|
|
<div class="flex items-center gap-3">
|
|
<span
|
|
class="flex items-center justify-center w-8 h-8 rounded"
|
|
:class="{
|
|
'bg-red-100/80 text-red-600': notification.type === 'breach',
|
|
'bg-amber-100/80 text-amber-600': notification.type === 'warning'
|
|
}"
|
|
>
|
|
<CircleAlert size="18" v-if="notification.type === 'warning'" />
|
|
<Timer size="18" v-else />
|
|
</span>
|
|
<div>
|
|
<div class="font-medium text-foreground">
|
|
{{
|
|
notification.type === 'warning' ? t('admin.sla.warning') : t('admin.sla.breach')
|
|
}}
|
|
{{ t('globals.terms.alert').toLowerCase() }}
|
|
</div>
|
|
<p class="text-xs text-muted-foreground">
|
|
{{ notification.type === 'warning' ? 'Pre-breach alert' : 'Post-breach alert' }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="xs"
|
|
@click.prevent="removeNotification(index)"
|
|
class="opacity-70 hover:opacity-100 text-muted-foreground hover:text-foreground"
|
|
>
|
|
<X class="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
<!-- Configuration Fields -->
|
|
<div class="grid gap-5 md:grid-cols-2">
|
|
<!-- Timing Section -->
|
|
<div class="space-y-3">
|
|
<div class="space-y-6">
|
|
<FormField
|
|
:name="`notifications.${index}.time_delay_type`"
|
|
v-slot="{ componentField }"
|
|
v-if="notification.type === 'breach'"
|
|
>
|
|
<FormItem>
|
|
<FormLabel class="flex items-center gap-1.5 text-sm font-medium">
|
|
<Clock class="w-4 h-4 text-muted-foreground" />
|
|
{{ t('admin.sla.triggerTiming') }}
|
|
</FormLabel>
|
|
<FormControl>
|
|
<Select v-bind="componentField">
|
|
<SelectTrigger class="w-full">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectGroup>
|
|
<SelectItem value="immediately">
|
|
{{ t('admin.sla.immediatelyOnBreach') }}
|
|
</SelectItem>
|
|
<SelectItem value="after">
|
|
{{ t('admin.sla.afterSpecificDuration') }}
|
|
</SelectItem>
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
</FormControl>
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField :name="`notifications.${index}.time_delay`" v-slot="{ componentField }">
|
|
<FormItem v-if="shouldShowTimeDelay(index)">
|
|
<FormLabel class="flex items-center gap-1.5 text-sm font-medium">
|
|
<Hourglass class="w-4 h-4 text-muted-foreground" />
|
|
{{
|
|
notification.type === 'warning'
|
|
? t('admin.sla.advanceWarning')
|
|
: t('admin.sla.followUpDelay')
|
|
}}
|
|
</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="text"
|
|
:placeholder="
|
|
t('globals.messages.enter', {
|
|
name: t('globals.terms.duration').toLowerCase()
|
|
})
|
|
"
|
|
v-bind="componentField"
|
|
@keydown.enter.prevent
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recipients Section -->
|
|
<div class="space-y-3">
|
|
<FormField
|
|
:name="`notifications.${index}.recipients`"
|
|
v-slot="{ componentField, handleChange }"
|
|
>
|
|
<FormItem>
|
|
<FormLabel class="flex items-center gap-1.5 text-sm font-medium">
|
|
<Users class="w-4 h-4 text-muted-foreground" />
|
|
{{ t('admin.sla.alertRecipients') }}
|
|
</FormLabel>
|
|
<FormControl>
|
|
<SelectTag
|
|
:items="
|
|
usersStore.options.concat({
|
|
label: 'Assigned user',
|
|
value: 'assigned_user'
|
|
})
|
|
"
|
|
:placeholder="t('globals.messages.startTypingToSearch')"
|
|
v-model="componentField.modelValue"
|
|
@update:modelValue="handleChange"
|
|
class="w-full hover:border-foreground/30"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
</div>
|
|
|
|
<FormField :name="`notifications.${index}.metric`" v-slot="{ componentField }">
|
|
<FormItem>
|
|
<FormLabel class="flex items-center gap-1.5 text-sm font-medium">
|
|
<SlidersHorizontal class="w-4 h-4 text-muted-foreground" />
|
|
{{ t('globals.terms.slaMetric') }}
|
|
</FormLabel>
|
|
<FormControl>
|
|
<Select v-bind="componentField">
|
|
<SelectTrigger class="w-full">
|
|
<SelectValue
|
|
:placeholder="
|
|
t('form.field.select', {
|
|
name: t('globals.terms.slaMetric').toLowerCase()
|
|
})
|
|
"
|
|
/>
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectGroup>
|
|
<SelectItem value="all">
|
|
{{ t('globals.messages.all') }}
|
|
</SelectItem>
|
|
<SelectItem value="first_response">
|
|
{{ t('admin.sla.firstResponseTime') }}
|
|
</SelectItem>
|
|
<SelectItem value="next_response">
|
|
{{ t('admin.sla.nextResponseTime') }}
|
|
</SelectItem>
|
|
<SelectItem value="resolution">
|
|
{{ t('admin.sla.resolutionTime') }}
|
|
</SelectItem>
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div
|
|
v-else
|
|
class="flex flex-col items-center justify-center p-8 space-y-3 rounded bg-muted/30 border border-dashed"
|
|
>
|
|
<Bell class="w-8 h-8 text-muted-foreground" />
|
|
<p class="text-sm text-muted-foreground">{{ t('admin.sla.noAlertsConfigured') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<Button type="submit" :disabled="isLoading" :isLoading="isLoading" class="mt-6">
|
|
{{ submitLabel }}
|
|
</Button>
|
|
</form>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { watch, computed } from 'vue'
|
|
import { useForm } from 'vee-validate'
|
|
import { toTypedSchema } from '@vee-validate/zod'
|
|
import { createFormSchema } from './formSchema'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
X,
|
|
Plus,
|
|
Timer,
|
|
CircleAlert,
|
|
Users,
|
|
Clock,
|
|
Hourglass,
|
|
Bell,
|
|
SlidersHorizontal
|
|
} from 'lucide-vue-next'
|
|
import { useUsersStore } from '@/stores/users'
|
|
import {
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
FormDescription
|
|
} from '@/components/ui/form'
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectGroup,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue
|
|
} from '@/components/ui/select'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { SelectTag } from '@/components/ui/select'
|
|
import { Input } from '@/components/ui/input'
|
|
|
|
const props = defineProps({
|
|
initialValues: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
submitForm: {
|
|
type: Function,
|
|
required: true
|
|
},
|
|
submitLabel: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
isLoading: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
})
|
|
|
|
const usersStore = useUsersStore()
|
|
const submitLabel = computed(() => {
|
|
return (
|
|
props.submitLabel ||
|
|
(props.initialValues.id ? t('globals.buttons.update') : t('globals.buttons.create'))
|
|
)
|
|
})
|
|
|
|
const { t } = useI18n()
|
|
const form = useForm({
|
|
validationSchema: toTypedSchema(createFormSchema(t)),
|
|
initialValues: {
|
|
name: '',
|
|
description: '',
|
|
first_response_time: '',
|
|
resolution_time: '',
|
|
notifications: []
|
|
}
|
|
})
|
|
|
|
const shouldShowTimeDelay = (index) => {
|
|
const notification = form.values.notifications?.[index]
|
|
if (!notification) return false
|
|
return notification.type === 'warning' || notification.time_delay_type === 'after'
|
|
}
|
|
|
|
const addNotification = (type) => {
|
|
const notifications = [...(form.values.notifications || [])]
|
|
notifications.push({
|
|
type: type,
|
|
time_delay_type: type === 'warning' ? 'before' : 'immediately',
|
|
time_delay: type === 'warning' ? '10m' : '',
|
|
recipients: [],
|
|
metric: 'all'
|
|
})
|
|
form.setFieldValue('notifications', notifications)
|
|
}
|
|
|
|
const removeNotification = (index) => {
|
|
const notifications = [...form.values.notifications]
|
|
notifications.splice(index, 1)
|
|
form.setFieldValue('notifications', notifications)
|
|
}
|
|
|
|
watch(
|
|
() => props.initialValues,
|
|
(newValues) => {
|
|
if (!newValues || Object.keys(newValues).length === 0) {
|
|
form.resetForm()
|
|
return
|
|
}
|
|
|
|
const transformedNotifications = (newValues.notifications || []).map((notification) => ({
|
|
...notification,
|
|
// Default value, notification applies to all metrics unless specified.
|
|
metric: notification.metric || 'all',
|
|
time_delay_type:
|
|
notification.type === 'warning'
|
|
? 'before'
|
|
: notification.time_delay
|
|
? 'after'
|
|
: 'immediately'
|
|
}))
|
|
|
|
form.setValues({
|
|
...newValues,
|
|
notifications: transformedNotifications
|
|
})
|
|
},
|
|
{ immediate: true, deep: true }
|
|
)
|
|
|
|
const onSubmit = form.handleSubmit((values) => {
|
|
const payload = {
|
|
...values,
|
|
notifications: values.notifications.map((notification) => ({
|
|
...notification,
|
|
time_delay: notification.time_delay_type === 'immediately' ? '' : notification.time_delay
|
|
}))
|
|
}
|
|
props.submitForm(payload)
|
|
})
|
|
|
|
// watch(
|
|
// () => form.errors,
|
|
// (errors) => {
|
|
// if (Object.keys(errors).length > 0) {
|
|
// console.log('Form has errors', errors)
|
|
// }
|
|
// },
|
|
// { deep: true }
|
|
// )
|
|
</script>
|